mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-20 23:36:53 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65b492f581 |
No files matched your search
+3
-41
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.limelight"
|
package="com.limelight"
|
||||||
android:versionCode="20"
|
android:versionCode="18"
|
||||||
android:versionName="2.5" >
|
android:versionName="2.4" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="16"
|
android:minSdkVersion="16"
|
||||||
@@ -11,10 +11,8 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -22,8 +20,7 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme" >
|
android:theme="@style/AppTheme" >
|
||||||
<activity
|
<activity
|
||||||
android:name="com.limelight.PcView"
|
android:name="com.limelight.Connection"
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
|
||||||
android:label="@string/app_name" >
|
android:label="@string/app_name" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@@ -31,35 +28,6 @@
|
|||||||
<category android:name="tv.ouya.intent.category.APP" />
|
<category android:name="tv.ouya.intent.category.APP" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name="com.limelight.AppView"
|
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
|
||||||
android:label="App List" >
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="com.limelight.PcView" />
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name="com.limelight.StreamSettings"
|
|
||||||
android:label="Streaming Settings" >
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="com.limelight.PcView" />
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name="com.limelight.AdvancedSettings"
|
|
||||||
android:label="Advanced Settings" >
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="com.limelight.StreamSettings" />
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name="com.limelight.AddComputerManually"
|
|
||||||
android:label="Add Computer Manually" >
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="com.limelight.StreamSettings" />
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.limelight.Game"
|
android:name="com.limelight.Game"
|
||||||
android:screenOrientation="sensorLandscape"
|
android:screenOrientation="sensorLandscape"
|
||||||
@@ -71,12 +39,6 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.Connection" />
|
android:value="com.limelight.Connection" />
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
|
||||||
android:name="com.limelight.discovery.DiscoveryService"
|
|
||||||
android:label="mDNS PC Auto-Discovery Service" />
|
|
||||||
<service
|
|
||||||
android:name="com.limelight.computers.ComputerManagerService"
|
|
||||||
android:label="Computer Management Service" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"SupportedControllers" : {
|
||||||
|
"Gamepad" : {},
|
||||||
|
"Remote" : "false",
|
||||||
|
"SecondScreen" : {
|
||||||
|
"DPad" : "false",
|
||||||
|
"AnalogSticks" : "0",
|
||||||
|
"DigitalButtons" : "0",
|
||||||
|
"Mouse" : "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-29
@@ -34,40 +34,28 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
|||||||
public static final class drawable {
|
public static final class drawable {
|
||||||
public static final int app_icon=0x7f020000;
|
public static final int app_icon=0x7f020000;
|
||||||
public static final int ic_launcher=0x7f020001;
|
public static final int ic_launcher=0x7f020001;
|
||||||
public static final int list_view_unselected=0x7f020002;
|
public static final int ouya_icon=0x7f020002;
|
||||||
public static final int ouya_icon=0x7f020003;
|
|
||||||
}
|
}
|
||||||
public static final class id {
|
public static final class id {
|
||||||
public static final int addPc=0x7f080001;
|
public static final int autoDec=0x7f080006;
|
||||||
public static final int advancedSettingsButton=0x7f080012;
|
public static final int bitrateLabel=0x7f08000c;
|
||||||
public static final int appListText=0x7f080009;
|
public static final int bitrateSeekBar=0x7f08000d;
|
||||||
public static final int autoDec=0x7f080004;
|
public static final int config1080p30Selected=0x7f08000a;
|
||||||
public static final int bitrateLabel=0x7f080006;
|
public static final int config1080p60Selected=0x7f08000b;
|
||||||
public static final int bitrateSeekBar=0x7f080007;
|
public static final int config720p30Selected=0x7f080008;
|
||||||
public static final int config1080p30Selected=0x7f080010;
|
public static final int config720p60Selected=0x7f080009;
|
||||||
public static final int config1080p60Selected=0x7f080011;
|
public static final int decoderConfigGroup=0x7f080003;
|
||||||
public static final int config720p30Selected=0x7f08000e;
|
public static final int hardwareDec=0x7f080007;
|
||||||
public static final int config720p60Selected=0x7f08000f;
|
|
||||||
public static final int decoderConfigGroup=0x7f080002;
|
|
||||||
public static final int discoveryText=0x7f08000b;
|
|
||||||
public static final int hardwareDec=0x7f080005;
|
|
||||||
public static final int hostTextView=0x7f080000;
|
public static final int hostTextView=0x7f080000;
|
||||||
public static final int manuallyAddPc=0x7f080013;
|
public static final int pairButton=0x7f080002;
|
||||||
public static final int pcListView=0x7f080008;
|
public static final int softwareDec=0x7f080005;
|
||||||
public static final int rowTextView=0x7f080014;
|
public static final int statusButton=0x7f080001;
|
||||||
public static final int settingsButton=0x7f08000c;
|
public static final int streamConfigGroup=0x7f080004;
|
||||||
public static final int softwareDec=0x7f080003;
|
public static final int surfaceView=0x7f08000e;
|
||||||
public static final int streamConfigGroup=0x7f08000d;
|
|
||||||
public static final int surfaceView=0x7f08000a;
|
|
||||||
}
|
}
|
||||||
public static final class layout {
|
public static final class layout {
|
||||||
public static final int activity_add_computer_manually=0x7f030000;
|
public static final int activity_connection=0x7f030000;
|
||||||
public static final int activity_advanced_settings=0x7f030001;
|
public static final int activity_game=0x7f030001;
|
||||||
public static final int activity_app_view=0x7f030002;
|
|
||||||
public static final int activity_game=0x7f030003;
|
|
||||||
public static final int activity_pc_view=0x7f030004;
|
|
||||||
public static final int activity_stream_settings=0x7f030005;
|
|
||||||
public static final int simplerow=0x7f030006;
|
|
||||||
}
|
}
|
||||||
public static final class string {
|
public static final class string {
|
||||||
public static final int app_name=0x7f060000;
|
public static final int app_name=0x7f060000;
|
||||||
|
|||||||
Binary file not shown.
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle" >
|
|
||||||
|
|
||||||
<stroke android:width="1dip" android:color="#ffffff"/>
|
|
||||||
|
|
||||||
</shape>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
tools:context=".Connection" >
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/hostTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:ems="10"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:inputType="textNoSuggestions"
|
|
||||||
android:hint="IP address of GeForce PC" >
|
|
||||||
|
|
||||||
<requestFocus />
|
|
||||||
</EditText>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/addPc"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/hostTextView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:text="Manually Add PC" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
tools:context=".Connection" >
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content" >
|
|
||||||
|
|
||||||
<RadioGroup
|
|
||||||
android:id="@+id/decoderConfigGroup"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/softwareDec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Force Software Decoding" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/autoDec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Auto-select Decoder (Recommended)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/hardwareDec"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Force Hardware Decoding" />
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/bitrateLabel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_below="@+id/decoderConfigGroup"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginTop="10dp" />
|
|
||||||
|
|
||||||
<SeekBar
|
|
||||||
android:id="@+id/bitrateSeekBar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_below="@+id/decoderConfigGroup"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:layout_toLeftOf="@+id/bitrateLabel" />
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
tools:context=".AppView" >
|
|
||||||
|
|
||||||
<ListView
|
|
||||||
android:id="@+id/pcListView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_below="@+id/appListText"
|
|
||||||
android:fastScrollEnabled="true"
|
|
||||||
android:longClickable="false"
|
|
||||||
android:background="@drawable/list_view_unselected"
|
|
||||||
android:stackFromBottom="false">
|
|
||||||
</ListView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/appListText"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="Applications" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
tools:context=".Connection" >
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/hostTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:ems="10"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:hint="IP address of GeForce PC" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/statusButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/hostTextView"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="Start Streaming Steam!" >
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pairButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/statusButton"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="Pair with PC" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/decoderConfigGroup"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/streamConfigGroup"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/softwareDec"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Force Software Decoding" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/autoDec"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Auto-select Decoder (Recommended)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/hardwareDec"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Force Hardware Decoding" />
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/streamConfigGroup"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/pairButton"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config720p30Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="720p 30 FPS (Only recommended for poor devices or networks)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config720p60Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config1080p30Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/config1080p60Selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bitrateLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/bitrateSeekBar"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup"
|
||||||
|
android:layout_toLeftOf="@+id/bitrateLabel" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
tools:context=".PcView" >
|
|
||||||
|
|
||||||
<ListView
|
|
||||||
android:id="@+id/pcListView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_below="@+id/discoveryText"
|
|
||||||
android:background="@drawable/list_view_unselected"
|
|
||||||
android:fastScrollEnabled="true"
|
|
||||||
android:longClickable="false"
|
|
||||||
android:stackFromBottom="false" >
|
|
||||||
|
|
||||||
</ListView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/discoveryText"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:layout_below="@+id/settingsButton"
|
|
||||||
android:paddingTop="20dp"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="Discovered PC List" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/settingsButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:text="Streaming Settings" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
tools:context=".Connection" >
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content" >
|
|
||||||
|
|
||||||
<RadioGroup
|
|
||||||
android:id="@+id/streamConfigGroup"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config720p30Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="720p 30 FPS (Only recommended for poor devices or networks)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config720p60Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config1080p30Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/config1080p60Selected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
|
||||||
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/advancedSettingsButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/streamConfigGroup"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:text="Advanced Settings" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/manuallyAddPc"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/advancedSettingsButton"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:text="Add PC Manually" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/rowTextView"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textIsSelectable="false"
|
|
||||||
android:textSize="16sp" >
|
|
||||||
</TextView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
import com.limelight.computers.ComputerManagerService;
|
|
||||||
import com.limelight.utils.Dialog;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
public class AddComputerManually extends Activity {
|
|
||||||
private Button addPcButton;
|
|
||||||
private TextView hostText;
|
|
||||||
private ServiceConnection serviceConnection = new ServiceConnection() {
|
|
||||||
public void onServiceConnected(ComponentName className, final IBinder binder) {
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String msg;
|
|
||||||
boolean finish = false;
|
|
||||||
try {
|
|
||||||
InetAddress addr = InetAddress.getByName(hostText.getText().toString());
|
|
||||||
|
|
||||||
if (!((ComputerManagerService.ComputerManagerBinder)binder).addComputerBlocking(addr)){
|
|
||||||
msg = "Unable to connect to the specified computer. Make sure the required ports are allowed through the firewall.";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg = "Successfully added computer";
|
|
||||||
finish = true;
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
msg = "Unable to resolve PC address. Make sure you didn't make a typo in the address.";
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean toastFinish = finish;
|
|
||||||
final String toastMsg = msg;
|
|
||||||
AddComputerManually.this.runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Unbind from this service
|
|
||||||
unbindService(AddComputerManually.this.serviceConnection);
|
|
||||||
|
|
||||||
Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show();
|
|
||||||
|
|
||||||
if (toastFinish) {
|
|
||||||
// Close the activity
|
|
||||||
AddComputerManually.this.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_add_computer_manually);
|
|
||||||
|
|
||||||
this.addPcButton = (Button) findViewById(R.id.addPc);
|
|
||||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
|
||||||
|
|
||||||
addPcButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (hostText.getText().length() == 0) {
|
|
||||||
Toast.makeText(AddComputerManually.this, "You must enter an IP address", Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(AddComputerManually.this, "Adding PC...", Toast.LENGTH_SHORT).show();
|
|
||||||
|
|
||||||
// Bind to the service which will try to add the PC
|
|
||||||
bindService(new Intent(AddComputerManually.this, ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
import com.limelight.utils.Dialog;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.widget.SeekBar;
|
|
||||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
|
||||||
|
|
||||||
public class AdvancedSettings extends Activity {
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
|
||||||
private SeekBar bitrateSlider;
|
|
||||||
private TextView bitrateLabel;
|
|
||||||
|
|
||||||
private static final int BITRATE_FLOOR = 1;
|
|
||||||
private static final int BITRATE_CEILING = 100;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
|
||||||
|
|
||||||
editor.putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress());
|
|
||||||
editor.apply();
|
|
||||||
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_advanced_settings);
|
|
||||||
|
|
||||||
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
|
||||||
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
|
||||||
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
|
||||||
this.bitrateLabel = (TextView) findViewById(R.id.bitrateLabel);
|
|
||||||
this.bitrateSlider = (SeekBar) findViewById(R.id.bitrateSeekBar);
|
|
||||||
|
|
||||||
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
|
||||||
|
|
||||||
bitrateSlider.setMax(BITRATE_CEILING);
|
|
||||||
bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE));
|
|
||||||
updateBitrateLabel();
|
|
||||||
|
|
||||||
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
|
||||||
case Game.FORCE_SOFTWARE_DECODER:
|
|
||||||
forceSoftDec.setChecked(true);
|
|
||||||
autoDec.setChecked(false);
|
|
||||||
forceHardDec.setChecked(false);
|
|
||||||
break;
|
|
||||||
case Game.AUTOSELECT_DECODER:
|
|
||||||
forceSoftDec.setChecked(false);
|
|
||||||
autoDec.setChecked(true);
|
|
||||||
forceHardDec.setChecked(false);
|
|
||||||
break;
|
|
||||||
case Game.FORCE_HARDWARE_DECODER:
|
|
||||||
forceSoftDec.setChecked(false);
|
|
||||||
autoDec.setChecked(false);
|
|
||||||
forceHardDec.setChecked(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnCheckedChangeListener occl = new OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView,
|
|
||||||
boolean isChecked) {
|
|
||||||
if (!isChecked) {
|
|
||||||
// Ignore non-checked buttons
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonView == forceSoftDec) {
|
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == forceHardDec) {
|
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_HARDWARE_DECODER).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == autoDec) {
|
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.AUTOSELECT_DECODER).commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
forceSoftDec.setOnCheckedChangeListener(occl);
|
|
||||||
forceHardDec.setOnCheckedChangeListener(occl);
|
|
||||||
autoDec.setOnCheckedChangeListener(occl);
|
|
||||||
|
|
||||||
this.bitrateSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress,
|
|
||||||
boolean fromUser) {
|
|
||||||
|
|
||||||
// Verify the user's selection
|
|
||||||
if (fromUser) {
|
|
||||||
if (progress < BITRATE_FLOOR) {
|
|
||||||
seekBar.setProgress(BITRATE_FLOOR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBitrateLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBitrateLabel() {
|
|
||||||
bitrateLabel.setText("Max Bitrate: "+bitrateSlider.getProgress()+" Mbps");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
|
||||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
|
||||||
import com.limelight.nvstream.http.NvApp;
|
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
|
||||||
import com.limelight.utils.Dialog;
|
|
||||||
import com.limelight.utils.SpinnerDialog;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
||||||
|
|
||||||
public class AppView extends Activity {
|
|
||||||
private ListView appList;
|
|
||||||
private ArrayAdapter<AppObject> appListAdapter;
|
|
||||||
private InetAddress ipAddress;
|
|
||||||
private String uniqueId;
|
|
||||||
private boolean remote;
|
|
||||||
|
|
||||||
private final static int RESUME_ID = 1;
|
|
||||||
private final static int QUIT_ID = 2;
|
|
||||||
|
|
||||||
public final static String ADDRESS_EXTRA = "Address";
|
|
||||||
public final static String UNIQUEID_EXTRA = "UniqueId";
|
|
||||||
public final static String NAME_EXTRA = "Name";
|
|
||||||
public final static String REMOTE_EXTRA = "Remote";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_app_view);
|
|
||||||
|
|
||||||
byte[] address = getIntent().getByteArrayExtra(ADDRESS_EXTRA);
|
|
||||||
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
|
|
||||||
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
|
|
||||||
if (address == null || uniqueId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitle("App List for "+getIntent().getStringExtra(NAME_EXTRA));
|
|
||||||
|
|
||||||
try {
|
|
||||||
ipAddress = InetAddress.getByAddress(address);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the list view
|
|
||||||
appList = (ListView)findViewById(R.id.pcListView);
|
|
||||||
appListAdapter = new ArrayAdapter<AppObject>(this, R.layout.simplerow, R.id.rowTextView);
|
|
||||||
appListAdapter.setNotifyOnChange(false);
|
|
||||||
appList.setAdapter(appListAdapter);
|
|
||||||
appList.setItemsCanFocus(true);
|
|
||||||
appList.setOnItemClickListener(new OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
|
||||||
long id) {
|
|
||||||
AppObject app = (AppObject) appListAdapter.getItem(pos);
|
|
||||||
if (app == null || app.app == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only open the context menu if it's running, otherwise start it
|
|
||||||
if (app.app.getIsRunning()) {
|
|
||||||
openContextMenu(arg1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
doStart(app.app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registerForContextMenu(appList);
|
|
||||||
updateAppList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
SpinnerDialog.closeDialogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
updateAppList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
|
||||||
|
|
||||||
menu.add(Menu.NONE, RESUME_ID, 1, "Resume Session");
|
|
||||||
menu.add(Menu.NONE, QUIT_ID, 2, "Quit Session");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onContextMenuClosed(Menu menu) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
|
||||||
AppObject app = (AppObject) appListAdapter.getItem(info.position);
|
|
||||||
switch (item.getItemId())
|
|
||||||
{
|
|
||||||
case RESUME_ID:
|
|
||||||
// Resume is the same as start for us
|
|
||||||
doStart(app.app);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case QUIT_ID:
|
|
||||||
doQuit(app.app);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateString(NvApp app) {
|
|
||||||
StringBuilder str = new StringBuilder();
|
|
||||||
str.append(app.getAppName());
|
|
||||||
if (app.getIsRunning()) {
|
|
||||||
str.append(" - Running");
|
|
||||||
}
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addListPlaceholder() {
|
|
||||||
appListAdapter.add(new AppObject("No apps found. Try rescanning for games in GeForce Experience.", null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAppList() {
|
|
||||||
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, "App List", "Refreshing app list...", true);
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
NvHTTP httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this));
|
|
||||||
|
|
||||||
try {
|
|
||||||
final List<NvApp> appList = httpConn.getAppList();
|
|
||||||
|
|
||||||
AppView.this.runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
appListAdapter.clear();
|
|
||||||
if (appList.isEmpty()) {
|
|
||||||
addListPlaceholder();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (NvApp app : appList) {
|
|
||||||
appListAdapter.add(new AppObject(generateString(app), app));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appListAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Success case
|
|
||||||
return;
|
|
||||||
} catch (GfeHttpResponseException e) {
|
|
||||||
} catch (IOException e) {
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
} finally {
|
|
||||||
spinner.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog.displayDialog(AppView.this, "Error", "Failed to get app list", true);
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doStart(NvApp app) {
|
|
||||||
Intent intent = new Intent(this, Game.class);
|
|
||||||
intent.putExtra(Game.EXTRA_HOST, ipAddress.getHostAddress());
|
|
||||||
intent.putExtra(Game.EXTRA_APP, app.getAppName());
|
|
||||||
intent.putExtra(Game.EXTRA_UNIQUEID, uniqueId);
|
|
||||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, remote);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doQuit(final NvApp app) {
|
|
||||||
Toast.makeText(AppView.this, "Quitting "+app.getAppName()+"...", Toast.LENGTH_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
NvHTTP httpConn;
|
|
||||||
String message;
|
|
||||||
try {
|
|
||||||
httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this));
|
|
||||||
if (httpConn.quitApp()) {
|
|
||||||
message = "Successfully quit "+app.getAppName();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Failed to quit "+app.getAppName();
|
|
||||||
}
|
|
||||||
updateAppList();
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
message = "Failed to resolve host";
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
|
||||||
+ "Try rebooting your machine or reinstalling GFE.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String toastMessage = message;
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AppObject {
|
|
||||||
public String text;
|
|
||||||
public NvApp app;
|
|
||||||
|
|
||||||
public AppObject(String text, NvApp app) {
|
|
||||||
this.text = text;
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
package com.limelight;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import com.limelight.binding.PlatformBinding;
|
||||||
|
import com.limelight.nvstream.NvConnection;
|
||||||
|
import com.limelight.nvstream.http.NvHTTP;
|
||||||
|
import com.limelight.nvstream.http.PairingManager;
|
||||||
|
import com.limelight.utils.Dialog;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
public class Connection extends Activity {
|
||||||
|
private Button statusButton, pairButton;
|
||||||
|
private TextView hostText;
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
||||||
|
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
||||||
|
private SeekBar bitrateSlider;
|
||||||
|
private TextView bitrateLabel;
|
||||||
|
|
||||||
|
private static final String DEFAULT_HOST = "";
|
||||||
|
public static final String HOST_KEY = "hostText";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
|
editor.putString(Connection.HOST_KEY, this.hostText.getText().toString());
|
||||||
|
editor.putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress());
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_connection);
|
||||||
|
|
||||||
|
// Hide the keyboard by default
|
||||||
|
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
|
|
||||||
|
this.statusButton = (Button) findViewById(R.id.statusButton);
|
||||||
|
this.pairButton = (Button) findViewById(R.id.pairButton);
|
||||||
|
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
||||||
|
this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected);
|
||||||
|
this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected);
|
||||||
|
this.rbutton1080p30 = (RadioButton) findViewById(R.id.config1080p30Selected);
|
||||||
|
this.rbutton1080p60 = (RadioButton) findViewById(R.id.config1080p60Selected);
|
||||||
|
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
||||||
|
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
||||||
|
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
||||||
|
this.bitrateLabel = (TextView) findViewById(R.id.bitrateLabel);
|
||||||
|
this.bitrateSlider = (SeekBar) findViewById(R.id.bitrateSeekBar);
|
||||||
|
|
||||||
|
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
|
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
||||||
|
|
||||||
|
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
||||||
|
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
||||||
|
|
||||||
|
bitrateSlider.setMax(Game.BITRATE_CEILING);
|
||||||
|
bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE));
|
||||||
|
updateBitrateLabel();
|
||||||
|
|
||||||
|
rbutton720p30.setChecked(false);
|
||||||
|
rbutton720p60.setChecked(false);
|
||||||
|
rbutton1080p30.setChecked(false);
|
||||||
|
rbutton1080p60.setChecked(false);
|
||||||
|
if (res720p) {
|
||||||
|
if (fps30) {
|
||||||
|
rbutton720p30.setChecked(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rbutton720p60.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (fps30) {
|
||||||
|
rbutton1080p30.setChecked(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rbutton1080p60.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
||||||
|
case Game.FORCE_SOFTWARE_DECODER:
|
||||||
|
forceSoftDec.setChecked(true);
|
||||||
|
autoDec.setChecked(false);
|
||||||
|
forceHardDec.setChecked(false);
|
||||||
|
break;
|
||||||
|
case Game.AUTOSELECT_DECODER:
|
||||||
|
forceSoftDec.setChecked(false);
|
||||||
|
autoDec.setChecked(true);
|
||||||
|
forceHardDec.setChecked(false);
|
||||||
|
break;
|
||||||
|
case Game.FORCE_HARDWARE_DECODER:
|
||||||
|
forceSoftDec.setChecked(false);
|
||||||
|
autoDec.setChecked(false);
|
||||||
|
forceHardDec.setChecked(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnCheckedChangeListener occl = new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView,
|
||||||
|
boolean isChecked) {
|
||||||
|
if (!isChecked) {
|
||||||
|
// Ignore non-checked buttons
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonView == rbutton720p30) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_30).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_720_30);
|
||||||
|
}
|
||||||
|
else if (buttonView == rbutton720p60) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_60).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_720_60);
|
||||||
|
}
|
||||||
|
else if (buttonView == rbutton1080p30) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_30).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_1080_30);
|
||||||
|
}
|
||||||
|
else if (buttonView == rbutton1080p60) {
|
||||||
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_60).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_1080_60);
|
||||||
|
}
|
||||||
|
else if (buttonView == forceSoftDec) {
|
||||||
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == forceHardDec) {
|
||||||
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_HARDWARE_DECODER).commit();
|
||||||
|
}
|
||||||
|
else if (buttonView == autoDec) {
|
||||||
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.AUTOSELECT_DECODER).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rbutton720p30.setOnCheckedChangeListener(occl);
|
||||||
|
rbutton720p60.setOnCheckedChangeListener(occl);
|
||||||
|
rbutton1080p30.setOnCheckedChangeListener(occl);
|
||||||
|
rbutton1080p60.setOnCheckedChangeListener(occl);
|
||||||
|
forceSoftDec.setOnCheckedChangeListener(occl);
|
||||||
|
forceHardDec.setOnCheckedChangeListener(occl);
|
||||||
|
autoDec.setOnCheckedChangeListener(occl);
|
||||||
|
|
||||||
|
this.bitrateSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress,
|
||||||
|
boolean fromUser) {
|
||||||
|
|
||||||
|
// Verify the user's selection
|
||||||
|
if (fromUser) {
|
||||||
|
int floor;
|
||||||
|
if (rbutton720p30.isChecked()) {
|
||||||
|
floor = Game.BITRATE_FLOOR_720_30;
|
||||||
|
}
|
||||||
|
else if (rbutton720p60.isChecked()){
|
||||||
|
floor = Game.BITRATE_FLOOR_720_60;
|
||||||
|
}
|
||||||
|
else if (rbutton1080p30.isChecked()){
|
||||||
|
floor = Game.BITRATE_FLOOR_1080_30;
|
||||||
|
}
|
||||||
|
else /*if (rbutton1080p60.isChecked())*/ {
|
||||||
|
floor = Game.BITRATE_FLOOR_1080_60;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress < floor) {
|
||||||
|
seekBar.setProgress(floor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBitrateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.statusButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View arg0) {
|
||||||
|
if (Connection.this.hostText.getText().length() == 0) {
|
||||||
|
Toast.makeText(Connection.this, "Please enter the target PC's IP address in the text box at the top of the screen.", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the bitrate preference is up to date before
|
||||||
|
// starting the game activity
|
||||||
|
prefs.edit().
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress()).
|
||||||
|
commit();
|
||||||
|
|
||||||
|
Intent intent = new Intent(Connection.this, Game.class);
|
||||||
|
intent.putExtra("host", Connection.this.hostText.getText().toString());
|
||||||
|
Connection.this.startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pairButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View arg0) {
|
||||||
|
if (Connection.this.hostText.getText().length() == 0) {
|
||||||
|
Toast.makeText(Connection.this, "Please enter the target PC's IP address in the text box at the top of the screen.", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_SHORT).show();
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String macAddress;
|
||||||
|
try {
|
||||||
|
macAddress = NvConnection.getMacAddressString();
|
||||||
|
} catch (SocketException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (macAddress == null) {
|
||||||
|
LimeLog.severe("Couldn't find a MAC address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvHTTP httpConn;
|
||||||
|
String message;
|
||||||
|
try {
|
||||||
|
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()),
|
||||||
|
macAddress, PlatformBinding.getDeviceName(), PlatformBinding.getCryptoProvider(Connection.this));
|
||||||
|
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||||
|
message = "Already paired";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final String pinStr = PairingManager.generatePinString();
|
||||||
|
|
||||||
|
// Spin the dialog off in a thread because it blocks
|
||||||
|
Dialog.displayDialog(Connection.this, "Pairing", "Please enter the following PIN on the target PC: "+pinStr, false);
|
||||||
|
|
||||||
|
PairingManager.PairState pairState = httpConn.pair(pinStr);
|
||||||
|
if (pairState == PairingManager.PairState.PIN_WRONG) {
|
||||||
|
message = "Incorrect PIN";
|
||||||
|
}
|
||||||
|
else if (pairState == PairingManager.PairState.FAILED) {
|
||||||
|
message = "Pairing failed";
|
||||||
|
}
|
||||||
|
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||||
|
message = "Paired successfully";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Should be no other values
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
message = "Failed to resolve host";
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
||||||
|
+ "Try rebooting your machine or reinstalling GFE.";
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
|
||||||
|
final String toastMessage = message;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(Connection.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBitrateLabel() {
|
||||||
|
bitrateLabel.setText("Max Bitrate: "+bitrateSlider.getProgress()+" Mbps");
|
||||||
|
}
|
||||||
|
}
|
||||||
+117
-33
@@ -1,5 +1,11 @@
|
|||||||
package com.limelight;
|
package com.limelight;
|
||||||
|
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InterfaceAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
import com.limelight.binding.PlatformBinding;
|
||||||
import com.limelight.binding.input.ControllerHandler;
|
import com.limelight.binding.input.ControllerHandler;
|
||||||
@@ -20,6 +26,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -58,17 +65,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
private boolean connecting = false;
|
private boolean connecting = false;
|
||||||
private boolean connected = false;
|
private boolean connected = false;
|
||||||
|
|
||||||
private ConfigurableDecoderRenderer decoderRenderer;
|
|
||||||
|
|
||||||
private WifiManager.WifiLock wifiLock;
|
private WifiManager.WifiLock wifiLock;
|
||||||
|
|
||||||
private int drFlags = 0;
|
private int drFlags = 0;
|
||||||
|
|
||||||
public static final String EXTRA_HOST = "Host";
|
|
||||||
public static final String EXTRA_APP = "App";
|
|
||||||
public static final String EXTRA_UNIQUEID = "UniqueId";
|
|
||||||
public static final String EXTRA_STREAMING_REMOTE = "Remote";
|
|
||||||
|
|
||||||
public static final String PREFS_FILE_NAME = "gameprefs";
|
public static final String PREFS_FILE_NAME = "gameprefs";
|
||||||
|
|
||||||
public static final String WIDTH_PREF_STRING = "ResH";
|
public static final String WIDTH_PREF_STRING = "ResH";
|
||||||
@@ -77,11 +77,18 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
public static final String DECODER_PREF_STRING = "Decoder";
|
public static final String DECODER_PREF_STRING = "Decoder";
|
||||||
public static final String BITRATE_PREF_STRING = "Bitrate";
|
public static final String BITRATE_PREF_STRING = "Bitrate";
|
||||||
|
|
||||||
|
public static final int BITRATE_FLOOR_720_30 = 2;
|
||||||
|
public static final int BITRATE_FLOOR_720_60 = 4;
|
||||||
|
public static final int BITRATE_FLOOR_1080_30 = 4;
|
||||||
|
public static final int BITRATE_FLOOR_1080_60 = 10;
|
||||||
|
|
||||||
public static final int BITRATE_DEFAULT_720_30 = 5;
|
public static final int BITRATE_DEFAULT_720_30 = 5;
|
||||||
public static final int BITRATE_DEFAULT_720_60 = 10;
|
public static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
public static final int BITRATE_DEFAULT_1080_30 = 10;
|
public static final int BITRATE_DEFAULT_1080_30 = 10;
|
||||||
public static final int BITRATE_DEFAULT_1080_60 = 30;
|
public static final int BITRATE_DEFAULT_1080_60 = 30;
|
||||||
|
|
||||||
|
public static final int BITRATE_CEILING = 50;
|
||||||
|
|
||||||
public static final int DEFAULT_WIDTH = 1280;
|
public static final int DEFAULT_WIDTH = 1280;
|
||||||
public static final int DEFAULT_HEIGHT = 720;
|
public static final int DEFAULT_HEIGHT = 720;
|
||||||
public static final int DEFAULT_REFRESH_RATE = 60;
|
public static final int DEFAULT_REFRESH_RATE = 60;
|
||||||
@@ -163,24 +170,118 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
wifiLock.setReferenceCounted(false);
|
wifiLock.setReferenceCounted(false);
|
||||||
wifiLock.acquire();
|
wifiLock.acquire();
|
||||||
|
|
||||||
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
String host = Game.this.getIntent().getStringExtra("host");
|
||||||
String app = Game.this.getIntent().getStringExtra(EXTRA_APP);
|
InetAddress addr;
|
||||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
boolean enableLargePackets;
|
||||||
boolean enableLargePackets = !Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, true);
|
try {
|
||||||
|
// If this does a DNS lookup, it could cause a NetworkOnMainThread exception
|
||||||
|
// Chances are if it has to do this, we're not on the same network anyways
|
||||||
|
addr = InetAddress.getByName(host);
|
||||||
|
|
||||||
|
// Check if we can enable large packets if we get this far
|
||||||
|
enableLargePackets = shouldEnableLargePackets(addr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We don't want to deal with any exceptions here. The user will be notified
|
||||||
|
// when the connection fails
|
||||||
|
enableLargePackets = false;
|
||||||
|
}
|
||||||
|
|
||||||
LimeLog.info("Using large packets? "+enableLargePackets);
|
LimeLog.info("Using large packets? "+enableLargePackets);
|
||||||
|
|
||||||
// Start the connection
|
// Start the connection
|
||||||
conn = new NvConnection(host, uniqueId, Game.this,
|
conn = new NvConnection(host, Game.this,
|
||||||
new StreamConfiguration(app, width, height, refreshRate, bitrate * 1000,
|
new StreamConfiguration("Steam", width, height, refreshRate, bitrate * 1000,
|
||||||
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
|
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
|
||||||
keybTranslator = new KeyboardTranslator(conn);
|
keybTranslator = new KeyboardTranslator(conn);
|
||||||
controllerHandler = new ControllerHandler(conn);
|
controllerHandler = new ControllerHandler(conn);
|
||||||
decoderRenderer = new ConfigurableDecoderRenderer();
|
|
||||||
|
|
||||||
// The connection will be started when the surface gets created
|
// The connection will be started when the surface gets created
|
||||||
sh.addCallback(this);
|
sh.addCallback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldEnableLargePackets(InetAddress targetAddress) {
|
||||||
|
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
|
||||||
|
String matchingPrefix;
|
||||||
|
|
||||||
|
if (activeNetworkInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (activeNetworkInfo.getType())
|
||||||
|
{
|
||||||
|
case ConnectivityManager.TYPE_ETHERNET:
|
||||||
|
matchingPrefix = "eth";
|
||||||
|
break;
|
||||||
|
case ConnectivityManager.TYPE_WIFI:
|
||||||
|
matchingPrefix = "wlan";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Must be on Ethernet or Wifi to consider that we can send large packets
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the interface that corresponds to the active network
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> ifaceList = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (ifaceList.hasMoreElements()) {
|
||||||
|
NetworkInterface iface = ifaceList.nextElement();
|
||||||
|
|
||||||
|
// Look for an interface that matches the prefix we expect
|
||||||
|
if (iface.isUp() && iface.getName().startsWith(matchingPrefix)) {
|
||||||
|
// Find the IPv4 address for the interface
|
||||||
|
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
|
||||||
|
if (!(addr.getAddress() instanceof Inet4Address)) {
|
||||||
|
// Skip non-IPv4 addresses
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the right address on the right interface
|
||||||
|
return isOnSameSubnet(targetAddress, addr.getAddress(), addr.getNetworkPrefixLength());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SocketException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find the interface or something else went wrong
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnSameSubnet(InetAddress targetAddress, InetAddress localAddress, short networkPrefixLength) {
|
||||||
|
byte[] targetBytes = targetAddress.getAddress();
|
||||||
|
byte[] localBytes = localAddress.getAddress();
|
||||||
|
|
||||||
|
for (int byteIndex = 0; networkPrefixLength > 0; byteIndex++) {
|
||||||
|
byte target = targetBytes[byteIndex];
|
||||||
|
byte local = localBytes[byteIndex];
|
||||||
|
|
||||||
|
if (networkPrefixLength >= 8) {
|
||||||
|
// Do a full byte comparison
|
||||||
|
if (target != local) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkPrefixLength -= 8;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target &= (byte)(0xFF << (8 - networkPrefixLength));
|
||||||
|
local &= (byte)(0xFF << (8 - networkPrefixLength));
|
||||||
|
|
||||||
|
// Do a masked comparison
|
||||||
|
if (target != local) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkPrefixLength = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void checkDataConnection()
|
private void checkDataConnection()
|
||||||
{
|
{
|
||||||
ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
@@ -229,23 +330,6 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
conn.stop();
|
conn.stop();
|
||||||
|
|
||||||
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
|
||||||
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
|
||||||
String message = null;
|
|
||||||
if (averageEndToEndLat > 0) {
|
|
||||||
message = "Average total frame latency: "+averageEndToEndLat+" ms";
|
|
||||||
if (averageDecoderLat > 0) {
|
|
||||||
message += " (hardware decoder latency: "+averageDecoderLat+" ms)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (averageDecoderLat > 0) {
|
|
||||||
message = "Average hardware decoder latency: "+averageDecoderLat+" ms";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message != null) {
|
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,7 +647,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
|||||||
if (!connected && !connecting) {
|
if (!connected && !connecting) {
|
||||||
connecting = true;
|
connecting = true;
|
||||||
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
|
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
|
||||||
PlatformBinding.getAudioRenderer(), decoderRenderer);
|
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,545 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
|
||||||
import com.limelight.computers.ComputerManagerListener;
|
|
||||||
import com.limelight.computers.ComputerManagerService;
|
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
|
||||||
import com.limelight.nvstream.http.PairingManager;
|
|
||||||
import com.limelight.nvstream.http.PairingManager.PairState;
|
|
||||||
import com.limelight.nvstream.wol.WakeOnLanSender;
|
|
||||||
import com.limelight.utils.Dialog;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
||||||
|
|
||||||
public class PcView extends Activity {
|
|
||||||
private Button settingsButton;
|
|
||||||
private ListView pcList;
|
|
||||||
private ArrayAdapter<ComputerObject> pcListAdapter;
|
|
||||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
|
||||||
private boolean freezeUpdates, runningPolling;
|
|
||||||
private ServiceConnection serviceConnection = new ServiceConnection() {
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
|
||||||
final ComputerManagerService.ComputerManagerBinder localBinder =
|
|
||||||
((ComputerManagerService.ComputerManagerBinder)binder);
|
|
||||||
|
|
||||||
// Wait in a separate thread to avoid stalling the UI
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Wait for the binder to be ready
|
|
||||||
localBinder.waitForReady();
|
|
||||||
|
|
||||||
// Now make the binder visible
|
|
||||||
managerBinder = localBinder;
|
|
||||||
|
|
||||||
// Start updates
|
|
||||||
startComputerUpdates();
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
managerBinder = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final static int APP_LIST_ID = 1;
|
|
||||||
private final static int PAIR_ID = 2;
|
|
||||||
private final static int UNPAIR_ID = 3;
|
|
||||||
private final static int WOL_ID = 4;
|
|
||||||
private final static int DELETE_ID = 5;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_pc_view);
|
|
||||||
|
|
||||||
// Bind to the computer manager service
|
|
||||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
|
||||||
Service.BIND_AUTO_CREATE);
|
|
||||||
|
|
||||||
// Setup the list view
|
|
||||||
settingsButton = (Button)findViewById(R.id.settingsButton);
|
|
||||||
pcList = (ListView)findViewById(R.id.pcListView);
|
|
||||||
pcListAdapter = new ArrayAdapter<ComputerObject>(this, R.layout.simplerow, R.id.rowTextView);
|
|
||||||
pcListAdapter.setNotifyOnChange(false);
|
|
||||||
pcList.setAdapter(pcListAdapter);
|
|
||||||
pcList.setItemsCanFocus(true);
|
|
||||||
pcList.setOnItemClickListener(new OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
|
||||||
long id) {
|
|
||||||
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(pos);
|
|
||||||
if (computer.details == null) {
|
|
||||||
// Placeholder item; no context menu for it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
// Open the context menu if a PC is offline
|
|
||||||
openContextMenu(arg1);
|
|
||||||
}
|
|
||||||
else if (computer.details.pairState != PairState.PAIRED) {
|
|
||||||
// Pair an unpaired machine by default
|
|
||||||
doPair(computer.details);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
doAppList(computer.details);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registerForContextMenu(pcList);
|
|
||||||
settingsButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
startActivity(new Intent(PcView.this, StreamSettings.class));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addListPlaceholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startComputerUpdates() {
|
|
||||||
if (managerBinder != null) {
|
|
||||||
if (runningPolling) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
freezeUpdates = false;
|
|
||||||
managerBinder.startPolling(new ComputerManagerListener() {
|
|
||||||
@Override
|
|
||||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
|
||||||
if (!freezeUpdates) {
|
|
||||||
PcView.this.runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateListView(details);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
runningPolling = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopComputerUpdates() {
|
|
||||||
freezeUpdates = true;
|
|
||||||
managerBinder.stopPolling();
|
|
||||||
runningPolling = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
|
|
||||||
if (managerBinder != null) {
|
|
||||||
unbindService(serviceConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
startComputerUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
stopComputerUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
||||||
stopComputerUpdates();
|
|
||||||
|
|
||||||
// Call superclass
|
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
|
||||||
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(info.position);
|
|
||||||
if (computer == null || computer.details == null) {
|
|
||||||
startComputerUpdates();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inflate the context menu
|
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
menu.add(Menu.NONE, WOL_ID, 1, "Send Wake-On-LAN request");
|
|
||||||
menu.add(Menu.NONE, DELETE_ID, 2, "Delete PC");
|
|
||||||
}
|
|
||||||
else if (computer.details.pairState != PairState.PAIRED) {
|
|
||||||
menu.add(Menu.NONE, PAIR_ID, 1, "Pair with PC");
|
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.REMOTE) {
|
|
||||||
menu.add(Menu.NONE, DELETE_ID, 2, "Delete PC");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
menu.add(Menu.NONE, APP_LIST_ID, 1, "View Game List");
|
|
||||||
menu.add(Menu.NONE, UNPAIR_ID, 2, "Unpair");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onContextMenuClosed(Menu menu) {
|
|
||||||
startComputerUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doPair(final ComputerDetails computer) {
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (computer.runningGameId != 0) {
|
|
||||||
Toast.makeText(PcView.this, "Computer is currently in a game. " +
|
|
||||||
"You must close the game before pairing.", Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(PcView.this, "Pairing...", Toast.LENGTH_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
NvHTTP httpConn;
|
|
||||||
String message;
|
|
||||||
try {
|
|
||||||
InetAddress addr = null;
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
addr = computer.localIp;
|
|
||||||
}
|
|
||||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
|
||||||
addr = computer.remoteIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpConn = new NvHTTP(addr,
|
|
||||||
managerBinder.getUniqueId(),
|
|
||||||
PlatformBinding.getDeviceName(),
|
|
||||||
PlatformBinding.getCryptoProvider(PcView.this));
|
|
||||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
|
||||||
message = "Already paired";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final String pinStr = PairingManager.generatePinString();
|
|
||||||
|
|
||||||
// Spin the dialog off in a thread because it blocks
|
|
||||||
Dialog.displayDialog(PcView.this, "Pairing", "Please enter the following PIN on the target PC: "+pinStr, false);
|
|
||||||
|
|
||||||
PairingManager.PairState pairState = httpConn.pair(pinStr);
|
|
||||||
if (pairState == PairingManager.PairState.PIN_WRONG) {
|
|
||||||
message = "Incorrect PIN";
|
|
||||||
}
|
|
||||||
else if (pairState == PairingManager.PairState.FAILED) {
|
|
||||||
message = "Pairing failed";
|
|
||||||
}
|
|
||||||
else if (pairState == PairingManager.PairState.PAIRED) {
|
|
||||||
message = "Paired successfully";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Should be no other values
|
|
||||||
message = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
message = "Failed to resolve host";
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
|
||||||
+ "Try rebooting your machine or reinstalling GFE.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
|
|
||||||
final String toastMessage = message;
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doWakeOnLan(final ComputerDetails computer) {
|
|
||||||
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
Toast.makeText(PcView.this, "Computer is online", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(PcView.this, "Waking PC...", Toast.LENGTH_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String message;
|
|
||||||
try {
|
|
||||||
WakeOnLanSender.sendWolPacket(computer);
|
|
||||||
message = "It may take a few seconds for your PC to wake up. " +
|
|
||||||
"If it doesn't, make sure it's configured properly for Wake-On-LAN.";
|
|
||||||
} catch (IOException e) {
|
|
||||||
message = "Failed to send Wake-On-LAN packets";
|
|
||||||
}
|
|
||||||
|
|
||||||
final String toastMessage = message;
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUnpair(final ComputerDetails computer) {
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(PcView.this, "Unpairing...", Toast.LENGTH_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
NvHTTP httpConn;
|
|
||||||
String message;
|
|
||||||
try {
|
|
||||||
InetAddress addr = null;
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
addr = computer.localIp;
|
|
||||||
}
|
|
||||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
|
||||||
addr = computer.remoteIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpConn = new NvHTTP(addr,
|
|
||||||
managerBinder.getUniqueId(),
|
|
||||||
PlatformBinding.getDeviceName(),
|
|
||||||
PlatformBinding.getCryptoProvider(PcView.this));
|
|
||||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
|
||||||
httpConn.unpair();
|
|
||||||
if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) {
|
|
||||||
message = "Unpaired successfully";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Failed to unpair";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message = "Device was not paired";
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
message = "Failed to resolve host";
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
|
|
||||||
+ "Try rebooting your machine or reinstalling GFE.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String toastMessage = message;
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doAppList(ComputerDetails computer) {
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
|
||||||
Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent i = new Intent(this, AppView.class);
|
|
||||||
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
|
||||||
i.putExtra(AppView.UNIQUEID_EXTRA, managerBinder.getUniqueId());
|
|
||||||
|
|
||||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
i.putExtra(AppView.ADDRESS_EXTRA, computer.localIp.getAddress());
|
|
||||||
i.putExtra(AppView.REMOTE_EXTRA, false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
i.putExtra(AppView.ADDRESS_EXTRA, computer.remoteIp.getAddress());
|
|
||||||
i.putExtra(AppView.REMOTE_EXTRA, true);
|
|
||||||
}
|
|
||||||
startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
|
||||||
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(info.position);
|
|
||||||
switch (item.getItemId())
|
|
||||||
{
|
|
||||||
case PAIR_ID:
|
|
||||||
doPair(computer.details);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case UNPAIR_ID:
|
|
||||||
doUnpair(computer.details);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case WOL_ID:
|
|
||||||
doWakeOnLan(computer.details);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case DELETE_ID:
|
|
||||||
if (managerBinder != null) {
|
|
||||||
managerBinder.removeComputer(computer.details.name);
|
|
||||||
}
|
|
||||||
removeListView(computer.details);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case APP_LIST_ID:
|
|
||||||
doAppList(computer.details);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateString(ComputerDetails details) {
|
|
||||||
StringBuilder str = new StringBuilder();
|
|
||||||
str.append(details.name).append(" - ");
|
|
||||||
if (details.state == ComputerDetails.State.ONLINE) {
|
|
||||||
str.append("Online ");
|
|
||||||
if (details.reachability == ComputerDetails.Reachability.LOCAL) {
|
|
||||||
str.append("(Local) - ");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
str.append("(Remote) - ");
|
|
||||||
}
|
|
||||||
if (details.pairState == PairState.PAIRED) {
|
|
||||||
if (details.runningGameId == 0) {
|
|
||||||
str.append("Available");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
str.append("In Game");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
str.append("Not Paired");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
str.append("Offline");
|
|
||||||
}
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addListPlaceholder() {
|
|
||||||
pcListAdapter.add(new ComputerObject("Discovery is running. No computers found yet. " +
|
|
||||||
"If your PC doesn't show up in about 15 seconds, " +
|
|
||||||
"make sure your computer is running GFE or add your PC manually on the settings page.", null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeListView(ComputerDetails details) {
|
|
||||||
for (int i = 0; i < pcListAdapter.getCount(); i++) {
|
|
||||||
ComputerObject computer = pcListAdapter.getItem(i);
|
|
||||||
|
|
||||||
if (details.equals(computer.details)) {
|
|
||||||
pcListAdapter.remove(computer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pcListAdapter.getCount() == 0) {
|
|
||||||
// Add the placeholder if we're down to 0 computers
|
|
||||||
addListPlaceholder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateListView(ComputerDetails details) {
|
|
||||||
String computerString = generateString(details);
|
|
||||||
ComputerObject existingEntry = null;
|
|
||||||
boolean placeholderPresent = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < pcListAdapter.getCount(); i++) {
|
|
||||||
ComputerObject computer = pcListAdapter.getItem(i);
|
|
||||||
|
|
||||||
// If there's a placeholder, there's nothing else
|
|
||||||
if (computer.details == null) {
|
|
||||||
placeholderPresent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is the same computer
|
|
||||||
if (details.equals(computer.details)) {
|
|
||||||
existingEntry = computer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingEntry != null) {
|
|
||||||
// Replace the information in the existing entry
|
|
||||||
existingEntry.text = computerString;
|
|
||||||
existingEntry.details = details;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If the placeholder is the only object, remove it
|
|
||||||
if (placeholderPresent) {
|
|
||||||
pcListAdapter.remove(pcListAdapter.getItem(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new entry
|
|
||||||
pcListAdapter.add(new ComputerObject(computerString, details));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify the view that the data has changed
|
|
||||||
pcListAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ComputerObject {
|
|
||||||
public String text;
|
|
||||||
public ComputerDetails details;
|
|
||||||
|
|
||||||
public ComputerObject(String text, ComputerDetails details) {
|
|
||||||
this.text = text;
|
|
||||||
this.details = details;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
import com.limelight.utils.Dialog;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
public class StreamSettings extends Activity {
|
|
||||||
private Button advancedSettingsButton, addComputerButton;
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_stream_settings);
|
|
||||||
|
|
||||||
this.advancedSettingsButton = (Button) findViewById(R.id.advancedSettingsButton);
|
|
||||||
this.addComputerButton = (Button) findViewById(R.id.manuallyAddPc);
|
|
||||||
this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected);
|
|
||||||
this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected);
|
|
||||||
this.rbutton1080p30 = (RadioButton) findViewById(R.id.config1080p30Selected);
|
|
||||||
this.rbutton1080p60 = (RadioButton) findViewById(R.id.config1080p60Selected);
|
|
||||||
|
|
||||||
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
|
||||||
|
|
||||||
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
|
||||||
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
|
||||||
|
|
||||||
rbutton720p30.setChecked(false);
|
|
||||||
rbutton720p60.setChecked(false);
|
|
||||||
rbutton1080p30.setChecked(false);
|
|
||||||
rbutton1080p60.setChecked(false);
|
|
||||||
if (res720p) {
|
|
||||||
if (fps30) {
|
|
||||||
rbutton720p30.setChecked(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rbutton720p60.setChecked(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (fps30) {
|
|
||||||
rbutton1080p30.setChecked(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rbutton1080p60.setChecked(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnCheckedChangeListener occl = new OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView,
|
|
||||||
boolean isChecked) {
|
|
||||||
if (!isChecked) {
|
|
||||||
// Ignore non-checked buttons
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonView == rbutton720p30) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
|
||||||
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_30).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == rbutton720p60) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
|
||||||
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_60).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == rbutton1080p30) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
|
||||||
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_30).commit();
|
|
||||||
}
|
|
||||||
else if (buttonView == rbutton1080p60) {
|
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
|
||||||
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_60).commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
rbutton720p30.setOnCheckedChangeListener(occl);
|
|
||||||
rbutton720p60.setOnCheckedChangeListener(occl);
|
|
||||||
rbutton1080p30.setOnCheckedChangeListener(occl);
|
|
||||||
rbutton1080p60.setOnCheckedChangeListener(occl);
|
|
||||||
|
|
||||||
advancedSettingsButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
Intent i = new Intent(StreamSettings.this, AdvancedSettings.class);
|
|
||||||
startActivity(i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addComputerButton.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
Intent i = new Intent(StreamSettings.this, AddComputerManually.class);
|
|
||||||
startActivity(i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,6 @@ import org.bouncycastle.openssl.PEMWriter;
|
|||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
@@ -113,10 +112,10 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Loaded key pair from disk");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("TrulyRandom")
|
|
||||||
private boolean generateCertKeyPair() {
|
private boolean generateCertKeyPair() {
|
||||||
byte[] snBytes = new byte[8];
|
byte[] snBytes = new byte[8];
|
||||||
new SecureRandom().nextBytes(snBytes);
|
new SecureRandom().nextBytes(snBytes);
|
||||||
|
|||||||
@@ -222,14 +222,4 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
public int getCapabilities() {
|
public int getCapabilities() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAverageDecoderLatency() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAverageEndToEndLatency() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,9 +9,7 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
if (decoderRenderer != null) {
|
decoderRenderer.release();
|
||||||
decoderRenderer.release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,23 +40,4 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
return decoderRenderer.getCapabilities();
|
return decoderRenderer.getCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAverageDecoderLatency() {
|
|
||||||
if (decoderRenderer != null) {
|
|
||||||
return decoderRenderer.getAverageDecoderLatency();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAverageEndToEndLatency() {
|
|
||||||
if (decoderRenderer != null) {
|
|
||||||
return decoderRenderer.getAverageEndToEndLatency();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -28,12 +28,6 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
private boolean needsSpsNumRefFixup;
|
private boolean needsSpsNumRefFixup;
|
||||||
private VideoDepacketizer depacketizer;
|
private VideoDepacketizer depacketizer;
|
||||||
|
|
||||||
private long totalTimeMs;
|
|
||||||
private long decoderTimeMs;
|
|
||||||
private int totalFrames;
|
|
||||||
|
|
||||||
private final static byte[] BITSTREAM_RESTRICTIONS = new byte[] {(byte) 0xF1, (byte) 0x83, 0x2A, 0x00};
|
|
||||||
|
|
||||||
public static final List<String> blacklistedDecoderPrefixes;
|
public static final List<String> blacklistedDecoderPrefixes;
|
||||||
public static final List<String> spsFixupBitsreamFixupDecoderPrefixes;
|
public static final List<String> spsFixupBitsreamFixupDecoderPrefixes;
|
||||||
public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
|
public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
|
||||||
@@ -183,13 +177,6 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
if (outIndex >= 0) {
|
if (outIndex >= 0) {
|
||||||
int lastIndex = outIndex;
|
int lastIndex = outIndex;
|
||||||
|
|
||||||
// Add delta time to the totals (excluding probable outliers)
|
|
||||||
long delta = System.currentTimeMillis()-(info.presentationTimeUs/1000);
|
|
||||||
if (delta > 5 && delta < 300) {
|
|
||||||
decoderTimeMs += delta;
|
|
||||||
totalTimeMs += delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the last output buffer in the queue
|
// Get the last output buffer in the queue
|
||||||
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
||||||
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
||||||
@@ -229,7 +216,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
rendererThread.interrupt();
|
rendererThread.interrupt();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rendererThread.join();
|
rendererThread.join();
|
||||||
} catch (InterruptedException e) { }
|
} catch (InterruptedException e) { }
|
||||||
@@ -248,13 +235,6 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
{
|
{
|
||||||
ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
|
ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
|
||||||
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
long delta = currentTime-decodeUnit.getReceiveTimestamp();
|
|
||||||
if (delta >= 0 && delta < 300) {
|
|
||||||
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
|
|
||||||
totalFrames++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear old input data
|
// Clear old input data
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
@@ -271,8 +251,6 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
(needsSpsBitstreamFixup || needsSpsNumRefFixup)) {
|
(needsSpsBitstreamFixup || needsSpsNumRefFixup)) {
|
||||||
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
||||||
if (header.data[header.offset+4] == 0x67) {
|
if (header.data[header.offset+4] == 0x67) {
|
||||||
byte last = header.data[header.length+header.offset-1];
|
|
||||||
|
|
||||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully
|
// TI OMAP4 requires a reference frame count of 1 to decode successfully
|
||||||
if (needsSpsNumRefFixup) {
|
if (needsSpsNumRefFixup) {
|
||||||
LimeLog.info("Fixing up num ref frames");
|
LimeLog.info("Fixing up num ref frames");
|
||||||
@@ -284,41 +262,31 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
|
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
|
||||||
int spsLength;
|
int spsLength;
|
||||||
if (needsSpsBitstreamFixup) {
|
if (needsSpsBitstreamFixup) {
|
||||||
if (!needsSpsNumRefFixup) {
|
switch (header.length) {
|
||||||
switch (header.length) {
|
case 26:
|
||||||
case 26:
|
LimeLog.info("Adding bitstream restrictions to SPS (26)");
|
||||||
LimeLog.info("Adding bitstream restrictions to SPS (26)");
|
buf.put(header.data, header.offset, 24);
|
||||||
buf.put(header.data, header.offset, 24);
|
buf.put((byte) 0x11);
|
||||||
buf.put((byte) 0x11);
|
buf.put((byte) 0xe3);
|
||||||
buf.put((byte) 0xe3);
|
buf.put((byte) 0x06);
|
||||||
buf.put((byte) 0x06);
|
buf.put((byte) 0x50);
|
||||||
buf.put((byte) 0x50);
|
spsLength = header.length + 2;
|
||||||
spsLength = header.length + 2;
|
break;
|
||||||
break;
|
case 27:
|
||||||
case 27:
|
LimeLog.info("Adding bitstream restrictions to SPS (27)");
|
||||||
LimeLog.info("Adding bitstream restrictions to SPS (27)");
|
buf.put(header.data, header.offset, 25);
|
||||||
buf.put(header.data, header.offset, 25);
|
buf.put((byte) 0x04);
|
||||||
buf.put((byte) 0x04);
|
buf.put((byte) 0x78);
|
||||||
buf.put((byte) 0x78);
|
buf.put((byte) 0xc1);
|
||||||
buf.put((byte) 0xc1);
|
buf.put((byte) 0x94);
|
||||||
buf.put((byte) 0x94);
|
spsLength = header.length + 2;
|
||||||
spsLength = header.length + 2;
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
LimeLog.warning("Unknown SPS of length "+header.length);
|
||||||
LimeLog.warning("Unknown SPS of length "+header.length);
|
|
||||||
buf.put(header.data, header.offset, header.length);
|
|
||||||
spsLength = header.length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Set bitstream restrictions to only buffer single frame
|
|
||||||
// (starts 9 bits before stop bit and 6 bits earlier because of the shortening above)
|
|
||||||
this.replace(header, header.length*8+Integer.numberOfLeadingZeros(last & - last)%8-9-6, 2, BITSTREAM_RESTRICTIONS, 3*8);
|
|
||||||
buf.put(header.data, header.offset, header.length);
|
buf.put(header.data, header.offset, header.length);
|
||||||
spsLength = header.length;
|
spsLength = header.length;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
buf.put(header.data, header.offset, header.length);
|
buf.put(header.data, header.offset, header.length);
|
||||||
@@ -327,7 +295,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
|
|
||||||
videoDecoder.queueInputBuffer(inputIndex,
|
videoDecoder.queueInputBuffer(inputIndex,
|
||||||
0, spsLength,
|
0, spsLength,
|
||||||
currentTime * 1000, codecFlags);
|
0, codecFlags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,7 +308,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
|
|
||||||
videoDecoder.queueInputBuffer(inputIndex,
|
videoDecoder.queueInputBuffer(inputIndex,
|
||||||
0, decodeUnit.getDataLength(),
|
0, decodeUnit.getDataLength(),
|
||||||
currentTime * 1000, codecFlags);
|
0, codecFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -429,20 +397,4 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
source.offset = offset;
|
source.offset = offset;
|
||||||
source.length = length;
|
source.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAverageDecoderLatency() {
|
|
||||||
if (totalFrames == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (int)(decoderTimeMs / totalFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAverageEndToEndLatency() {
|
|
||||||
if (totalFrames == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (int)(totalTimeMs / totalFrames);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
package com.limelight.computers;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
|
||||||
|
|
||||||
public class ComputerDatabaseManager {
|
|
||||||
private static final String COMPUTER_DB_NAME = "computers.db";
|
|
||||||
private static final String COMPUTER_TABLE_NAME = "Computers";
|
|
||||||
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
|
||||||
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
|
||||||
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp";
|
|
||||||
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
|
||||||
private static final String MAC_COLUMN_NAME = "Mac";
|
|
||||||
|
|
||||||
private SQLiteDatabase computerDb;
|
|
||||||
|
|
||||||
public ComputerDatabaseManager(Context c) {
|
|
||||||
try {
|
|
||||||
// Create or open an existing DB
|
|
||||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
|
||||||
} catch (SQLiteException e) {
|
|
||||||
// Delete the DB and try again
|
|
||||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
|
||||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
|
||||||
}
|
|
||||||
computerDb.enableWriteAheadLogging();
|
|
||||||
initializeDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
computerDb.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeDb() {
|
|
||||||
// Create tables if they aren't already there
|
|
||||||
computerDb.execSQL(String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," +
|
|
||||||
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)",
|
|
||||||
COMPUTER_TABLE_NAME,
|
|
||||||
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME,
|
|
||||||
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteComputer(String name) {
|
|
||||||
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean updateComputer(ComputerDetails details) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
|
||||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
|
||||||
values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress());
|
|
||||||
values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress());
|
|
||||||
values.put(MAC_COLUMN_NAME, details.macAddress);
|
|
||||||
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ComputerDetails> getAllComputers() {
|
|
||||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
|
||||||
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
ComputerDetails details = new ComputerDetails();
|
|
||||||
|
|
||||||
details.name = c.getString(0);
|
|
||||||
|
|
||||||
String uuidStr = c.getString(1);
|
|
||||||
try {
|
|
||||||
details.uuid = UUID.fromString(uuidStr);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
details.macAddress = c.getString(4);
|
|
||||||
|
|
||||||
// This signifies we don't have dynamic state (like pair state)
|
|
||||||
details.state = ComputerDetails.State.UNKNOWN;
|
|
||||||
|
|
||||||
// If a field is corrupt or missing, skip the database entry
|
|
||||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
|
||||||
details.macAddress == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
computerList.add(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
return computerList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComputerDetails getComputerByName(String name) {
|
|
||||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
|
||||||
ComputerDetails details = new ComputerDetails();
|
|
||||||
if (!c.moveToFirst()) {
|
|
||||||
// No matching computer
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
details.name = c.getString(0);
|
|
||||||
|
|
||||||
String uuidStr = c.getString(1);
|
|
||||||
try {
|
|
||||||
details.uuid = UUID.fromString(uuidStr);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// We'll delete this entry
|
|
||||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
details.macAddress = c.getString(4);
|
|
||||||
|
|
||||||
// If a field is corrupt or missing, delete the database entry
|
|
||||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
|
||||||
details.macAddress == null) {
|
|
||||||
deleteComputer(details.name);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return details;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.limelight.computers;
|
|
||||||
|
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
|
||||||
|
|
||||||
public interface ComputerManagerListener {
|
|
||||||
public void notifyComputerUpdated(ComputerDetails details);
|
|
||||||
}
|
|
||||||
@@ -1,424 +0,0 @@
|
|||||||
package com.limelight.computers;
|
|
||||||
|
|
||||||
import java.net.Inet4Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InterfaceAddress;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
|
||||||
import com.limelight.binding.PlatformBinding;
|
|
||||||
import com.limelight.discovery.DiscoveryService;
|
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
|
||||||
import com.limelight.nvstream.http.NvHTTP;
|
|
||||||
import com.limelight.nvstream.mdns.MdnsComputer;
|
|
||||||
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.IBinder;
|
|
||||||
|
|
||||||
public class ComputerManagerService extends Service {
|
|
||||||
private static final int MAX_CONCURRENT_REQUESTS = 4;
|
|
||||||
private static final int POLLING_PERIOD_MS = 5000;
|
|
||||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
|
||||||
|
|
||||||
private ComputerManagerBinder binder = new ComputerManagerBinder();
|
|
||||||
|
|
||||||
private ComputerDatabaseManager dbManager;
|
|
||||||
private IdentityManager idManager;
|
|
||||||
private ThreadPoolExecutor pollingPool;
|
|
||||||
private Timer pollingTimer;
|
|
||||||
private ComputerManagerListener listener = null;
|
|
||||||
|
|
||||||
private DiscoveryService.DiscoveryBinder discoveryBinder;
|
|
||||||
private ServiceConnection discoveryServiceConnection = new ServiceConnection() {
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
|
||||||
synchronized (discoveryServiceConnection) {
|
|
||||||
discoveryBinder = ((DiscoveryService.DiscoveryBinder)binder);
|
|
||||||
|
|
||||||
// Set us as the event listener
|
|
||||||
discoveryBinder.setListener(createDiscoveryListener());
|
|
||||||
|
|
||||||
// Signal a possible waiter that we're all setup
|
|
||||||
discoveryServiceConnection.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
discoveryBinder = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public class ComputerManagerBinder extends Binder {
|
|
||||||
public void startPolling(ComputerManagerListener listener) {
|
|
||||||
// Set the listener
|
|
||||||
ComputerManagerService.this.listener = listener;
|
|
||||||
|
|
||||||
// Start mDNS autodiscovery too
|
|
||||||
discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS);
|
|
||||||
|
|
||||||
// Start polling known machines
|
|
||||||
pollingTimer = new Timer();
|
|
||||||
pollingTimer.schedule(getTimerTask(), 0, POLLING_PERIOD_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForReady() {
|
|
||||||
synchronized (discoveryServiceConnection) {
|
|
||||||
try {
|
|
||||||
while (discoveryBinder == null) {
|
|
||||||
// Wait for the bind notification
|
|
||||||
discoveryServiceConnection.wait(1000);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addComputerBlocking(InetAddress addr) {
|
|
||||||
return ComputerManagerService.this.addComputerBlocking(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addComputer(InetAddress addr) {
|
|
||||||
ComputerManagerService.this.addComputer(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeComputer(String name) {
|
|
||||||
ComputerManagerService.this.removeComputer(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopPolling() {
|
|
||||||
// Just call the unbind handler to cleanup
|
|
||||||
ComputerManagerService.this.onUnbind(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUniqueId() {
|
|
||||||
return idManager.getUniqueId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onUnbind(Intent intent) {
|
|
||||||
// Stop mDNS autodiscovery
|
|
||||||
discoveryBinder.stopDiscovery();
|
|
||||||
|
|
||||||
// Stop polling
|
|
||||||
if (pollingTimer != null) {
|
|
||||||
pollingTimer.cancel();
|
|
||||||
pollingTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the listener
|
|
||||||
listener = null;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MdnsDiscoveryListener createDiscoveryListener() {
|
|
||||||
return new MdnsDiscoveryListener() {
|
|
||||||
@Override
|
|
||||||
public void notifyComputerAdded(MdnsComputer computer) {
|
|
||||||
// Kick off a serverinfo poll on this machine
|
|
||||||
addComputer(computer.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
|
||||||
// Nothing to do here
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyDiscoveryFailure(Exception e) {
|
|
||||||
LimeLog.severe("mDNS discovery failed");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addComputer(InetAddress addr) {
|
|
||||||
// Setup a placeholder
|
|
||||||
ComputerDetails fakeDetails = new ComputerDetails();
|
|
||||||
fakeDetails.localIp = addr;
|
|
||||||
fakeDetails.remoteIp = addr;
|
|
||||||
|
|
||||||
// Put it in the thread pool to process later
|
|
||||||
pollingPool.execute(getPollingRunnable(fakeDetails));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addComputerBlocking(InetAddress addr) {
|
|
||||||
// Setup a placeholder
|
|
||||||
ComputerDetails fakeDetails = new ComputerDetails();
|
|
||||||
fakeDetails.localIp = addr;
|
|
||||||
fakeDetails.remoteIp = addr;
|
|
||||||
|
|
||||||
// Block while we try to fill the details
|
|
||||||
getPollingRunnable(fakeDetails).run();
|
|
||||||
|
|
||||||
// If the machine is reachable, it was successful
|
|
||||||
return fakeDetails.state == ComputerDetails.State.ONLINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeComputer(String name) {
|
|
||||||
// Remove it from the database
|
|
||||||
dbManager.deleteComputer(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TimerTask getTimerTask() {
|
|
||||||
return new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
List<ComputerDetails> computerList = dbManager.getAllComputers();
|
|
||||||
for (ComputerDetails computer : computerList) {
|
|
||||||
pollingPool.execute(getPollingRunnable(computer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getActiveNetworkType() {
|
|
||||||
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
|
|
||||||
if (activeNetworkInfo == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return activeNetworkInfo.getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
private InterfaceAddress getActiveInterfaceAddress() {
|
|
||||||
String matchingPrefix;
|
|
||||||
|
|
||||||
switch (getActiveNetworkType())
|
|
||||||
{
|
|
||||||
case ConnectivityManager.TYPE_ETHERNET:
|
|
||||||
matchingPrefix = "eth";
|
|
||||||
break;
|
|
||||||
case ConnectivityManager.TYPE_WIFI:
|
|
||||||
matchingPrefix = "wlan";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Must be on Ethernet or Wifi to consider that we can send large packets
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find the interface that corresponds to the active network
|
|
||||||
try {
|
|
||||||
Enumeration<NetworkInterface> ifaceList = NetworkInterface.getNetworkInterfaces();
|
|
||||||
while (ifaceList.hasMoreElements()) {
|
|
||||||
NetworkInterface iface = ifaceList.nextElement();
|
|
||||||
|
|
||||||
// Look for an interface that matches the prefix we expect
|
|
||||||
if (iface.isUp() && iface.getName().startsWith(matchingPrefix)) {
|
|
||||||
// Find the IPv4 address for the interface
|
|
||||||
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
|
|
||||||
if (!(addr.getAddress() instanceof Inet4Address)) {
|
|
||||||
// Skip non-IPv4 addresses
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Found the right address on the right interface
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SocketException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't find the interface or something else went wrong
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isOnSameSubnet(InetAddress targetAddress, InetAddress localAddress, short networkPrefixLength) {
|
|
||||||
byte[] targetBytes = targetAddress.getAddress();
|
|
||||||
byte[] localBytes = localAddress.getAddress();
|
|
||||||
|
|
||||||
for (int byteIndex = 0; networkPrefixLength > 0; byteIndex++) {
|
|
||||||
byte target = targetBytes[byteIndex];
|
|
||||||
byte local = localBytes[byteIndex];
|
|
||||||
|
|
||||||
if (networkPrefixLength >= 8) {
|
|
||||||
// Do a full byte comparison
|
|
||||||
if (target != local) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
networkPrefixLength -= 8;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
target &= (byte)(0xFF << (8 - networkPrefixLength));
|
|
||||||
local &= (byte)(0xFF << (8 - networkPrefixLength));
|
|
||||||
|
|
||||||
// Do a masked comparison
|
|
||||||
if (target != local) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
networkPrefixLength = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ComputerDetails tryPollIp(InetAddress ipAddr) {
|
|
||||||
try {
|
|
||||||
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
|
|
||||||
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
|
||||||
|
|
||||||
return http.getComputerDetails();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean pollComputer(ComputerDetails details, boolean localFirst) {
|
|
||||||
ComputerDetails polledDetails;
|
|
||||||
|
|
||||||
if (localFirst) {
|
|
||||||
polledDetails = tryPollIp(details.localIp);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
polledDetails = tryPollIp(details.remoteIp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (polledDetails == null) {
|
|
||||||
// Failed, so let's try the fallback
|
|
||||||
if (!localFirst) {
|
|
||||||
polledDetails = tryPollIp(details.localIp);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
polledDetails = tryPollIp(details.remoteIp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The fallback poll worked
|
|
||||||
if (polledDetails != null) {
|
|
||||||
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL :
|
|
||||||
ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL :
|
|
||||||
ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Machine was unreachable both tries
|
|
||||||
if (polledDetails == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got here, it's reachable
|
|
||||||
details.update(polledDetails);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean doPollMachine(ComputerDetails details) {
|
|
||||||
// Get the network type
|
|
||||||
int networkType = getActiveNetworkType();
|
|
||||||
switch (networkType) {
|
|
||||||
// We'll check local first on these if we find
|
|
||||||
// we're on the same subnet
|
|
||||||
case ConnectivityManager.TYPE_ETHERNET:
|
|
||||||
case ConnectivityManager.TYPE_WIFI:
|
|
||||||
InterfaceAddress ifaceAddr = getActiveInterfaceAddress();
|
|
||||||
if (ifaceAddr != null) {
|
|
||||||
if (isOnSameSubnet(details.localIp, ifaceAddr.getAddress(), ifaceAddr.getNetworkPrefixLength())) {
|
|
||||||
// It's on the same subnet, so poll local first
|
|
||||||
LimeLog.info("Machine looks local; trying local IP first");
|
|
||||||
return pollComputer(details, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fall through to remote first
|
|
||||||
default:
|
|
||||||
LimeLog.info("Machine looks remote; trying remote IP first");
|
|
||||||
return pollComputer(details, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Runnable getPollingRunnable(final ComputerDetails details) {
|
|
||||||
return new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
boolean newPc = (details.name == null);
|
|
||||||
|
|
||||||
// Poll the machine
|
|
||||||
if (!doPollMachine(details)) {
|
|
||||||
details.state = ComputerDetails.State.OFFLINE;
|
|
||||||
details.reachability = ComputerDetails.Reachability.OFFLINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's online, update our persistent state
|
|
||||||
if (details.state == ComputerDetails.State.ONLINE) {
|
|
||||||
if (!newPc) {
|
|
||||||
// Check if it's in the database because it could have been
|
|
||||||
// removed after this was issued
|
|
||||||
if (dbManager.getComputerByName(details.name) == null) {
|
|
||||||
// It's gone
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dbManager.updateComputer(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update anyone listening
|
|
||||||
if (listener != null) {
|
|
||||||
listener.notifyComputerUpdated(details);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
// Bind to the discovery service
|
|
||||||
bindService(new Intent(this, DiscoveryService.class),
|
|
||||||
discoveryServiceConnection, Service.BIND_AUTO_CREATE);
|
|
||||||
|
|
||||||
// Create the thread pool for updating computer state
|
|
||||||
pollingPool = new ThreadPoolExecutor(1, MAX_CONCURRENT_REQUESTS, Long.MAX_VALUE, TimeUnit.DAYS,
|
|
||||||
new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.DiscardPolicy());
|
|
||||||
|
|
||||||
// Lookup or generate this device's UID
|
|
||||||
idManager = new IdentityManager(this);
|
|
||||||
|
|
||||||
// Initialize the DB
|
|
||||||
dbManager = new ComputerDatabaseManager(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
if (discoveryBinder != null) {
|
|
||||||
// Unbind from the discovery service
|
|
||||||
unbindService(discoveryServiceConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the thread pool
|
|
||||||
pollingPool.shutdownNow();
|
|
||||||
try {
|
|
||||||
pollingPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
|
|
||||||
} catch (InterruptedException e) {}
|
|
||||||
|
|
||||||
// Close the DB
|
|
||||||
dbManager.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return binder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package com.limelight.computers;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
public class IdentityManager {
|
|
||||||
private static final String UNIQUE_ID_FILE_NAME = "uniqueid";
|
|
||||||
private static final int UID_SIZE_IN_BYTES = 8;
|
|
||||||
|
|
||||||
private String uniqueId;
|
|
||||||
|
|
||||||
public IdentityManager(Context c) {
|
|
||||||
uniqueId = loadUniqueId(c);
|
|
||||||
if (uniqueId == null) {
|
|
||||||
uniqueId = generateNewUniqueId(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
LimeLog.info("UID is now: "+uniqueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUniqueId() {
|
|
||||||
return uniqueId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String loadUniqueId(Context c) {
|
|
||||||
// 2 Hex digits per byte
|
|
||||||
char[] uid = new char[UID_SIZE_IN_BYTES * 2];
|
|
||||||
InputStreamReader reader = null;
|
|
||||||
LimeLog.info("Reading UID from disk");
|
|
||||||
try {
|
|
||||||
reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME));
|
|
||||||
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2)
|
|
||||||
{
|
|
||||||
LimeLog.severe("UID file data is truncated");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new String(uid);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
LimeLog.info("No UID file found");
|
|
||||||
return null;
|
|
||||||
} catch (IOException e) {
|
|
||||||
LimeLog.severe("Error while reading UID file");
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
if (reader != null) {
|
|
||||||
try {
|
|
||||||
reader.close();
|
|
||||||
} catch (IOException e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateNewUniqueId(Context c) {
|
|
||||||
// Generate a new UID hex string
|
|
||||||
LimeLog.info("Generating new UID");
|
|
||||||
String uidStr = String.format("%016x", new Random().nextLong());
|
|
||||||
|
|
||||||
OutputStreamWriter writer = null;
|
|
||||||
try {
|
|
||||||
writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0));
|
|
||||||
writer.write(uidStr);
|
|
||||||
LimeLog.info("UID written to disk");
|
|
||||||
} catch (IOException e) {
|
|
||||||
LimeLog.severe("Error while writing UID file");
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
if (writer != null) {
|
|
||||||
try {
|
|
||||||
writer.close();
|
|
||||||
} catch (IOException e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can return a UID even if I/O fails
|
|
||||||
return uidStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package com.limelight.discovery;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.limelight.nvstream.mdns.MdnsComputer;
|
|
||||||
import com.limelight.nvstream.mdns.MdnsDiscoveryAgent;
|
|
||||||
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
import android.net.wifi.WifiManager.MulticastLock;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.IBinder;
|
|
||||||
|
|
||||||
public class DiscoveryService extends Service {
|
|
||||||
|
|
||||||
private MdnsDiscoveryAgent discoveryAgent;
|
|
||||||
private MdnsDiscoveryListener boundListener;
|
|
||||||
private MulticastLock multicastLock;
|
|
||||||
|
|
||||||
public class DiscoveryBinder extends Binder {
|
|
||||||
public void setListener(MdnsDiscoveryListener listener) {
|
|
||||||
boundListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startDiscovery(int queryIntervalMs) {
|
|
||||||
multicastLock.acquire();
|
|
||||||
discoveryAgent.startDiscovery(queryIntervalMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopDiscovery() {
|
|
||||||
discoveryAgent.stopDiscovery();
|
|
||||||
multicastLock.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MdnsComputer> getComputerSet() {
|
|
||||||
return discoveryAgent.getComputerSet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
|
||||||
multicastLock = wifiMgr.createMulticastLock("Limelight mDNS");
|
|
||||||
multicastLock.setReferenceCounted(false);
|
|
||||||
|
|
||||||
discoveryAgent = new MdnsDiscoveryAgent(new MdnsDiscoveryListener() {
|
|
||||||
@Override
|
|
||||||
public void notifyComputerAdded(MdnsComputer computer) {
|
|
||||||
if (boundListener != null) {
|
|
||||||
boundListener.notifyComputerAdded(computer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
|
||||||
if (boundListener != null) {
|
|
||||||
boundListener.notifyComputerRemoved(computer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyDiscoveryFailure(Exception e) {
|
|
||||||
if (boundListener != null) {
|
|
||||||
boundListener.notifyDiscoveryFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private DiscoveryBinder binder = new DiscoveryBinder();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onUnbind(Intent intent) {
|
|
||||||
// Stop any discovery session
|
|
||||||
discoveryAgent.stopDiscovery();
|
|
||||||
multicastLock.release();
|
|
||||||
|
|
||||||
// Unbind the listener
|
|
||||||
boundListener = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user