Compare commits

..
47 Commits
Author SHA1 Message Date
Cameron Gutman 1d9efb30e2 Update to version 3.1.10 2015-10-11 17:11:48 -07:00
Cameron Gutman ed7be00881 Update IML file 2015-10-11 17:11:08 -07:00
Cameron Gutman a6003f6bff Remove MediaTek decoders from the decoders that need bitstream restrictions. The correct fix was to lower level_idc to reduce the required buffering. On newer MediaTek chipsets, sending bitstream restrictions actually slows down decoding by a factor of 3. 2015-10-11 16:57:37 -07:00
Cameron Gutman 4619045375 Revert "Update common to increase SSL handshake timeout"
This reverts commit 57b0da1a3a.
2015-10-11 14:54:45 -07:00
Cameron Gutman e61b8f1b34 Try a TCP connection before trying HTTPS to quickly eliminate transport layer connectivity issues 2015-10-11 14:39:02 -07:00
Cameron Gutman 79b6ec839a Fix machines becoming unreachable after they report IP addresses that they can't be contacted with 2015-10-11 14:16:38 -07:00
Cameron Gutman fd12e30c53 Set constraint flags corresponding to Constrained High Profile on KitKat and higher. Fixes Nexus Player high latency on Android 6.0. 2015-10-10 23:28:48 -07:00
Cameron Gutman 87a9ca4318 Make touchscreen and stylus support more robust (supporting Bluetooth stylus in 6.0 and hopefully fixing broken touchscreen input on some devices) 2015-10-10 19:17:19 -07:00
Cameron Gutman 3f64411174 Only reload the PcView activity if UI settings were changed 2015-10-10 18:53:10 -07:00
Cameron Gutman 57b0da1a3a Update common to increase SSL handshake timeout 2015-10-10 18:15:01 -07:00
Cameron Gutman 7d3e74a67f Allow the offline context menu to be opened when the PC state is unknown 2015-10-10 18:10:57 -07:00
Cameron Gutman d704e322df Use RGB_565 for box art to reduce image size in memory 2015-10-10 18:09:51 -07:00
Cameron Gutman f598153818 Small improvements to Media Codec DR 2015-10-10 15:17:24 -07:00
Cameron Gutman f395a0c170 Fix warning 2015-10-10 15:04:49 -07:00
Cameron Gutman 654b33d27f Update build tools version to 23.0.1 2015-10-10 15:04:16 -07:00
Cameron Gutman 6c12da96c9 Add patched Jcodec library built from master a5d138efec2e940897e7e3d91a63a1f58abedd95 with changes from https://github.com/jcodec/jcodec/pull/90 2015-10-10 15:03:47 -07:00
Cameron Gutman 1a6f639b81 Fix discovery issues when adding a PC 2015-10-10 14:43:29 -07:00
Cameron Gutman 59a00a38c9 Limit box art assets to 5 MB each to prevent OOM crashes 2015-10-10 14:43:17 -07:00
Cameron Gutman 2beee168e3 Update README.md with additional download links 2015-08-30 12:37:23 -07:00
Cameron Gutman a92bbc7e5a Increment version to 3.1.9 2015-08-18 00:44:01 -07:00
Cameron Gutman fbc921dd07 Update build files 2015-08-18 00:39:01 -07:00
Cameron Gutman 59c6c3d777 Target Android 6.0 SDK 2015-08-18 00:38:49 -07:00
Cameron Gutman e7ab61c8d0 Use jmDNS 3.4.2 which works properly on Android 6.0 2015-08-17 23:14:20 -07:00
Cameron Gutman 7023760782 Use monotonic system time for rendering timestamps. This is required now in Android 6.0 since these timestamps are propagated to the codec. 2015-08-17 18:40:25 -07:00
Cameron Gutman 932ce435b5 Remove unnecessary buffer clear 2015-08-17 18:26:33 -07:00
Cameron Gutman af384d88f7 Handle potentially poisoned bitmap cache caused by truncated images from the server 2015-08-17 17:59:15 -07:00
Cameron Gutman 792846ddad Update MediaCodec renderer to avoid deprecated features on Lollipop 2015-08-17 17:52:57 -07:00
Cameron Gutman 1187d9c78c Update libraries 2015-08-14 09:29:56 -07:00
Cameron Gutman 37db9ab072 Update common with latest fixes 2015-08-12 00:39:07 -07:00
Cameron Gutman fb40060560 Upgrade common jar to incorporate IDR frame fix 2015-08-04 23:46:20 -07:00
Cameron Gutman a4f4887647 Upgrade build tools and libraries 2015-08-04 23:46:03 -07:00
Cameron Gutman f1d7f556fd Bump to version 3.1.8 2015-07-21 18:03:37 -07:00
Cameron Gutman 1e70e1d329 GFE 2.5.11 update to fix black screen on Fire TV Stick 2015-07-18 17:06:41 -07:00
Cameron Gutman e02a009635 Add support for the Razer Serval controller. The start and select buttons are manually handled for devices without a mapping for them. The back button is ignored so it can be used to exit the stream. 2015-07-18 00:46:25 -07:00
Cameron Gutman bd6ff35603 Update to 3.1.7 2015-06-15 10:37:58 -07:00
Cameron Gutman 1cb7727dc7 Update common 2015-06-15 10:28:31 -07:00
Cameron Gutman 0c73e3d0ae Only propagate a decoder exception if it happens at the beginning of a stream 2015-06-15 10:28:09 -07:00
Cameron Gutman 6371d364e1 Lint warning cleanup 2015-05-29 23:22:40 -05:00
Cameron Gutman ded9c9140d Handle being online but not having a known reachability 2015-05-29 23:20:04 -05:00
Cameron Gutman 7c8a108e28 Use the leanback feature on API 21+ devices 2015-05-29 23:18:56 -05:00
Cameron Gutman 2a18ffcdba Update to Gradle 1.2.3 2015-05-19 10:10:18 -05:00
Cameron Gutman 381d0d5e81 Add support for multi-window functionality on Samsung devices 2015-05-10 00:02:04 -05:00
Cameron Gutman be126acfd1 Update version info to 3.1.6 2015-05-05 20:52:53 -04:00
Cameron Gutman fc2f5cfe4d Manually pass through Samsung capacitive buttons 2015-05-05 20:20:37 -04:00
Cameron Gutman 9878902a89 Use IDs to track controllers instead of descriptors. Fixes #64 2015-05-05 20:08:58 -04:00
Cameron Gutman f1230d46f3 Android Studio 1.2 and Grade 1.2.2 update 2015-05-05 20:02:53 -04:00
Michelle Bergeron d8822392f1 Link to site 2015-05-03 23:36:19 -07:00
26 changed files with 403 additions and 217 deletions

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
#Moonlight #Moonlight
Moonlight is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. [Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
We reverse engineered the Shield streaming software and created a version that can be run on any Android device. We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device, Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
@@ -19,7 +19,7 @@ Check our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki) for
##Installation ##Installation
* Download and install Moonlight for Android from * Download and install Moonlight for Android from
[Google Play](https://play.google.com/store/apps/details?id=com.limelight) [Google Play](https://play.google.com/store/apps/details?id=com.limelight), [Amazon App Store](http://www.amazon.com/gp/product/B00JK4MFN2), or directly from the [releases page](https://github.com/moonlight-stream/moonlight-android/releases)
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC * Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
##Requirements ##Requirements
+17 -15
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="moonlight-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> <module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="moonlight-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager"> <component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle"> <facet type="android-gradle" name="Android-Gradle">
<configuration> <configuration>
@@ -13,8 +13,11 @@
<option name="ASSEMBLE_TASK_NAME" value="assembleNonRootDebug" /> <option name="ASSEMBLE_TASK_NAME" value="assembleNonRootDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileNonRootDebugSources" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileNonRootDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleNonRootDebugAndroidTest" /> <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleNonRootDebugAndroidTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateNonRootDebugSources" /> <option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileNonRootDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateNonRootDebugAndroidTestSources" /> <afterSyncTasks>
<task>generateNonRootDebugAndroidTestSources</task>
<task>generateNonRootDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
@@ -23,7 +26,7 @@
</configuration> </configuration>
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager" inherit-compiler-output="false"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/nonRoot/debug" /> <output url="file://$MODULE_DIR$/build/intermediates/classes/nonRoot/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/nonRoot/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/nonRoot/debug" />
<exclude-output /> <exclude-output />
@@ -33,7 +36,7 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/nonRoot/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" />
@@ -46,7 +49,7 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/nonRoot/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/nonRoot/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/nonRoot/debug" type="java-test-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/nonRoot/debug" type="java-test-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" />
@@ -103,16 +106,15 @@
<excludeFolder url="file://$MODULE_DIR$/build/outputs" /> <excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" /> <excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content> </content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.51" level="project" /> <orderEntry type="library" exported="" name="bcprov-jdk15on-1.52" level="project" />
<orderEntry type="library" exported="" name="jmdns-fixed" level="project" /> <orderEntry type="library" exported="" name="bcpkix-jdk15on-1.52" level="project" />
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.51" level="project" />
<orderEntry type="library" exported="" name="tinyrtsp" level="project" /> <orderEntry type="library" exported="" name="tinyrtsp" level="project" />
<orderEntry type="library" exported="" name="limelight-common" level="project" /> <orderEntry type="library" exported="" name="limelight-common" level="project" />
<orderEntry type="library" exported="" name="okhttp-2.2.0" level="project" /> <orderEntry type="library" exported="" name="jmdns-3.4.2" level="project" />
<orderEntry type="library" exported="" name="jcodec-0.1.9" level="project" /> <orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" />
<orderEntry type="library" exported="" name="okio-1.2.0" level="project" /> <orderEntry type="library" exported="" name="jcodec-0.1.9-patched" level="project" />
<orderEntry type="library" exported="" name="okio-1.5.0" level="project" />
</component> </component>
</module> </module>
+11 -12
View File
@@ -4,15 +4,15 @@ import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 22 compileSdkVersion 23
buildToolsVersion "21.1.2" buildToolsVersion "23.0.1"
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 22 targetSdkVersion 23
versionName "3.1.5" versionName "3.1.10"
versionCode = 60 versionCode = 65
} }
productFlavors { productFlavors {
@@ -62,15 +62,14 @@ android {
} }
dependencies { dependencies {
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.9' compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.52'
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.52'
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.51' compile group: 'com.squareup.okhttp', name: 'okhttp', version:'2.4.0'
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.51' compile group: 'com.squareup.okio', name:'okio', version:'1.5.0'
compile group: 'com.squareup.okhttp', name: 'okhttp', version:'2.2.0' compile files('libs/jmdns-3.4.2.jar')
compile group: 'com.squareup.okio', name:'okio', version:'1.2.0'
compile files('libs/jmdns-fixed.jar')
compile files('libs/limelight-common.jar') compile files('libs/limelight-common.jar')
compile files('libs/tinyrtsp.jar') compile files('libs/tinyrtsp.jar')
compile files('libs/jcodec-0.1.9-patched.jar')
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -16,6 +16,10 @@
android:allowBackup="true" android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme" > android:theme="@style/AppTheme" >
<!-- Samsung multi-window support -->
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<!-- Launcher for traditional devices --> <!-- Launcher for traditional devices -->
<activity <activity
@@ -24,6 +28,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
<category android:name="tv.ouya.intent.category.APP" /> <category android:name="tv.ouya.intent.category.APP" />
</intent-filter> </intent-filter>
</activity> </activity>
@@ -1,19 +1,14 @@
package com.limelight; package com.limelight;
import java.io.FileNotFoundException;
import java.io.StringReader; import java.io.StringReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import com.limelight.binding.PlatformBinding;
import com.limelight.computers.ComputerManagerListener; import com.limelight.computers.ComputerManagerListener;
import com.limelight.computers.ComputerManagerService; import com.limelight.computers.ComputerManagerService;
import com.limelight.grid.AppGridAdapter; import com.limelight.grid.AppGridAdapter;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.preferences.PreferenceConfiguration; import com.limelight.preferences.PreferenceConfiguration;
@@ -26,10 +21,8 @@ import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper; import com.limelight.utils.UiHelper;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.res.Configuration; import android.content.res.Configuration;
+49 -55
View File
@@ -520,9 +520,56 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
{ {
// This case is for mice
if (event.getSource() == InputDevice.SOURCE_MOUSE)
{
int changedButtons = event.getButtonState() ^ lastButtonState;
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
// Send the vertical scroll packet
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
conn.sendMouseScroll(vScrollClicks);
}
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
}
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
}
}
// First process the history
for (int i = 0; i < event.getHistorySize(); i++) {
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
}
// Now process the current values
updateMousePosition((int)event.getX(), (int)event.getY());
lastButtonState = event.getButtonState();
}
// This case is for touch-based input devices // This case is for touch-based input devices
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN || else
event.getSource() == InputDevice.SOURCE_STYLUS)
{ {
int actionIndex = event.getActionIndex(); int actionIndex = event.getActionIndex();
@@ -601,59 +648,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false; return false;
} }
} }
// This case is for mice
else if (event.getSource() == InputDevice.SOURCE_MOUSE)
{
int changedButtons = event.getButtonState() ^ lastButtonState;
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
// Send the vertical scroll packet
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
conn.sendMouseScroll(vScrollClicks);
}
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
}
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
}
}
// First process the history
for (int i = 0; i < event.getHistorySize(); i++) {
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
}
// Now process the current values
updateMousePosition((int)event.getX(), (int)event.getY());
lastButtonState = event.getButtonState();
}
else
{
// Unknown source
return false;
}
// Handled a known source // Handled a known source
return true; return true;
+15 -11
View File
@@ -241,13 +241,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
startComputerUpdates();
return;
}
// Inflate the context menu // Inflate the context menu
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE ||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol)); menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc)); menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
} }
@@ -306,6 +303,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
addr = computer.remoteIp; addr = computer.remoteIp;
} }
else {
LimeLog.warning("Unknown reachability - using local IP");
addr = computer.localIp;
}
httpConn = new NvHTTP(addr, httpConn = new NvHTTP(addr,
managerBinder.getUniqueId(), managerBinder.getUniqueId(),
@@ -374,7 +375,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void doWakeOnLan(final ComputerDetails computer) { private void doWakeOnLan(final ComputerDetails computer) {
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) { if (computer.state == ComputerDetails.State.ONLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
return; return;
} }
@@ -431,6 +432,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
addr = computer.remoteIp; addr = computer.remoteIp;
} }
else {
LimeLog.warning("Unknown reachability - using local IP");
addr = computer.localIp;
}
httpConn = new NvHTTP(addr, httpConn = new NvHTTP(addr,
managerBinder.getUniqueId(), managerBinder.getUniqueId(),
@@ -606,10 +611,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
public void onItemClick(AdapterView<?> arg0, View arg1, int pos, public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) { long id) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) { if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN ||
// Do nothing computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) { // Open the context menu if a PC is offline or refreshing
// Open the context menu if a PC is offline
openContextMenu(arg1); openContextMenu(arg1);
} else if (computer.details.pairState != PairState.PAIRED) { } else if (computer.details.pairState != PairState.PAIRED) {
// Pair an unpaired machine by default // Pair an unpaired machine by default
@@ -1,10 +1,8 @@
package com.limelight.binding.input; package com.limelight.binding.input;
import java.util.HashMap;
import java.util.Map;
import android.hardware.input.InputManager; import android.hardware.input.InputManager;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.SparseArray;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
@@ -31,7 +29,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
private final Vector2d inputVector = new Vector2d(); private final Vector2d inputVector = new Vector2d();
private final HashMap<String, ControllerContext> contexts = new HashMap<String, ControllerContext>(); private final SparseArray<ControllerContext> contexts = new SparseArray<ControllerContext>();
private final NvConnection conn; private final NvConnection conn;
private final double stickDeadzone; private final double stickDeadzone;
@@ -52,8 +50,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
deadzonePercentage = 10; deadzonePercentage = 10;
int[] ids = InputDevice.getDeviceIds(); int[] ids = InputDevice.getDeviceIds();
for (int i = 0; i < ids.length; i++) { for (int id : ids) {
InputDevice dev = InputDevice.getDevice(ids[i]); InputDevice dev = InputDevice.getDevice(id);
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 || if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
(dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) { (dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
// This looks like a gamepad, but we'll check X and Y to be sure // This looks like a gamepad, but we'll check X and Y to be sure
@@ -104,13 +102,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
@Override @Override
public void onInputDeviceRemoved(int deviceId) { public void onInputDeviceRemoved(int deviceId) {
for (Map.Entry<String, ControllerContext> device : contexts.entrySet()) { ControllerContext context = contexts.get(deviceId);
if (device.getValue().id == deviceId) { if (context != null) {
LimeLog.info("Removed controller: "+device.getValue().name); LimeLog.info("Removed controller: "+context.name+" ("+deviceId+")");
releaseControllerNumber(device.getValue()); releaseControllerNumber(context);
contexts.remove(device.getKey()); contexts.remove(deviceId);
return;
}
} }
} }
@@ -135,7 +131,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
return; return;
} }
LimeLog.info(context.name+" needs a controller number assigned"); LimeLog.info(context.name+" ("+context.id+") needs a controller number assigned");
if (context.name != null && context.name.contains("gpio-keys")) { if (context.name != null && context.name.contains("gpio-keys")) {
// This is the back button on Shield portable consoles // This is the back button on Shield portable consoles
LimeLog.info("Built-in buttons hardcoded as controller 0"); LimeLog.info("Built-in buttons hardcoded as controller 0");
@@ -277,6 +273,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
} }
} }
// Ignore the back buttonn if a controller has both buttons
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
boolean[] hasSelectKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BACK, 0);
if (hasSelectKey[0] && hasSelectKey[1]) {
LimeLog.info("Ignoring back button because select is present");
context.ignoreBack = true;
}
}
if (devName != null) { if (devName != null) {
// For the Nexus Player (and probably other ATV devices), we should // For the Nexus Player (and probably other ATV devices), we should
// use the back button as start since it doesn't have a start/menu button // use the back button as start since it doesn't have a start/menu button
@@ -300,7 +305,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
else if (devName.contains("Fire TV Remote") || devName.contains("Nexus Remote")) { else if (devName.contains("Fire TV Remote") || devName.contains("Nexus Remote")) {
// It's only a remote if it doesn't any sticks // It's only a remote if it doesn't any sticks
if (!context.hasJoystickAxes) { if (!context.hasJoystickAxes) {
context.isRemote = true; context.ignoreBack = true;
} }
} }
// SHIELD controllers will use small stick deadzones // SHIELD controllers will use small stick deadzones
@@ -308,6 +313,17 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
context.leftStickDeadzoneRadius = 0.07f; context.leftStickDeadzoneRadius = 0.07f;
context.rightStickDeadzoneRadius = 0.07f; context.rightStickDeadzoneRadius = 0.07f;
} }
// Samsung's face buttons appear as a non-virtual button so we'll explicitly ignore
// back presses on this device
else if (devName.equals("sec_touchscreen")) {
context.ignoreBack = true;
}
// The Serval has a couple of unknown buttons that are start and select. It also has
// a back button which we want to ignore since there's already a select button.
else if (devName.contains("Razer Serval")) {
context.isServal = true;
context.ignoreBack = true;
}
} }
LimeLog.info("Analog stick deadzone: "+context.leftStickDeadzoneRadius+" "+context.rightStickDeadzoneRadius); LimeLog.info("Analog stick deadzone: "+context.leftStickDeadzoneRadius+" "+context.rightStickDeadzoneRadius);
@@ -322,17 +338,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
return defaultContext; return defaultContext;
} }
String descriptor = dev.getDescriptor();
// Return the existing context if it exists // Return the existing context if it exists
ControllerContext context = contexts.get(descriptor); ControllerContext context = contexts.get(dev.getId());
if (context != null) { if (context != null) {
return context; return context;
} }
// Otherwise create a new context // Otherwise create a new context
context = createContextForDevice(dev); context = createContextForDevice(dev);
contexts.put(descriptor, context); contexts.put(dev.getId(), context);
return context; return context;
} }
@@ -348,8 +362,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
// Return a valid keycode, 0 to consume, or -1 to not consume the event // Return a valid keycode, 0 to consume, or -1 to not consume the event
// Device MAY BE NULL // Device MAY BE NULL
private int handleRemapping(ControllerContext context, KeyEvent event) { private int handleRemapping(ControllerContext context, KeyEvent event) {
// For remotes, don't capture the back button // Don't capture the back button if configured
if (context.isRemote) { if (context.ignoreBack) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return -1; return -1;
} }
@@ -393,6 +407,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
return 0; return 0;
} }
} }
// If this is a Serval controller sending an unknown key code, it's probably
// the start and select buttons
else if (context.isServal && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
switch (event.getScanCode()) {
case 314:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case 315:
return KeyEvent.KEYCODE_BUTTON_START;
}
}
if (context.hatXAxis != -1 && context.hatYAxis != -1) { if (context.hatXAxis != -1 && context.hatYAxis != -1) {
switch (event.getKeyCode()) { switch (event.getKeyCode()) {
@@ -814,9 +838,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
public boolean isDualShock4; public boolean isDualShock4;
public boolean isXboxController; public boolean isXboxController;
public boolean isServal;
public boolean backIsStart; public boolean backIsStart;
public boolean modeIsSelect; public boolean modeIsSelect;
public boolean isRemote; public boolean ignoreBack;
public boolean hasJoystickAxes; public boolean hasJoystickAxes;
public boolean assignedControllerNumber; public boolean assignedControllerNumber;
@@ -177,10 +177,10 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
rendererThread = new Thread() { rendererThread = new Thread() {
@Override @Override
public void run() { public void run() {
long nextFrameTime = System.currentTimeMillis(); long nextFrameTime = MediaCodecHelper.getMonotonicMillis();
while (!isInterrupted()) while (!isInterrupted())
{ {
long diff = nextFrameTime - System.currentTimeMillis(); long diff = nextFrameTime - MediaCodecHelper.getMonotonicMillis();
if (diff > WAIT_CEILING_MS) { if (diff > WAIT_CEILING_MS) {
try { try {
@@ -203,7 +203,7 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
} }
private long computePresentationTimeMs(int frameRate) { private long computePresentationTimeMs(int frameRate) {
return System.currentTimeMillis() + (1000 / frameRate); return MediaCodecHelper.getMonotonicMillis() + (1000 / frameRate);
} }
@Override @Override
@@ -251,7 +251,7 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0); boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0);
if (success) { if (success) {
long timeAfterDecode = System.currentTimeMillis(); long timeAfterDecode = MediaCodecHelper.getMonotonicMillis();
// Add delta time to the totals (excluding probable outliers) // Add delta time to the totals (excluding probable outliers)
long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp(); long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp();
@@ -13,7 +13,6 @@ import com.limelight.nvstream.av.DecodeUnit;
import com.limelight.nvstream.av.video.VideoDecoderRenderer; import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.av.video.VideoDepacketizer; import com.limelight.nvstream.av.video.VideoDepacketizer;
import android.annotation.TargetApi;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
@@ -22,10 +21,11 @@ import android.media.MediaCodec.CodecException;
import android.os.Build; import android.os.Build;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@SuppressWarnings("unused")
public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private ByteBuffer[] videoDecoderInputBuffers; // Used on versions < 5.0
private ByteBuffer[] legacyInputBuffers;
private MediaCodec videoDecoder; private MediaCodec videoDecoder;
private Thread rendererThread; private Thread rendererThread;
private final boolean needsSpsBitstreamFixup, isExynos4; private final boolean needsSpsBitstreamFixup, isExynos4;
@@ -46,7 +46,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private int numPpsIn; private int numPpsIn;
private int numIframeIn; private int numIframeIn;
@TargetApi(Build.VERSION_CODES.KITKAT)
public MediaCodecDecoderRenderer() { public MediaCodecDecoderRenderer() {
//dumpDecoders(); //dumpDecoders();
@@ -118,7 +117,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
return true; return true;
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void handleDecoderException(Exception e, ByteBuffer buf, int codecFlags) { private void handleDecoderException(Exception e, ByteBuffer buf, int codecFlags) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (e instanceof CodecException) { if (e instanceof CodecException) {
@@ -134,18 +132,20 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} }
} }
if (buf != null || codecFlags != 0) { // Only throw if this happens at the beginning of a stream
throw new RendererException(this, e, buf, codecFlags); if (totalFrames < 60) {
} if (buf != null || codecFlags != 0) {
else { throw new RendererException(this, e, buf, codecFlags);
throw new RendererException(this, e); }
else {
throw new RendererException(this, e);
}
} }
} }
private void startDirectSubmitRendererThread() private void startDirectSubmitRendererThread()
{ {
rendererThread = new Thread() { rendererThread = new Thread() {
@SuppressWarnings("deprecation")
@Override @Override
public void run() { public void run() {
BufferInfo info = new BufferInfo(); BufferInfo info = new BufferInfo();
@@ -169,7 +169,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
videoDecoder.releaseOutputBuffer(lastIndex, true); videoDecoder.releaseOutputBuffer(lastIndex, true);
// Add delta time to the totals (excluding probable outliers) // Add delta time to the totals (excluding probable outliers)
long delta = System.currentTimeMillis() - (presentationTimeUs / 1000); long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
if (delta >= 0 && delta < 1000) { if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta; decoderTimeMs += delta;
totalTimeMs += delta; totalTimeMs += delta;
@@ -178,9 +178,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
switch (outIndex) { switch (outIndex) {
case MediaCodec.INFO_TRY_AGAIN_LATER: case MediaCodec.INFO_TRY_AGAIN_LATER:
break; break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
LimeLog.info("Output buffers changed");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
LimeLog.info("Output format changed"); LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat()); LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
@@ -204,17 +201,17 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
int index; int index;
long startTime, queueTime; long startTime, queueTime;
startTime = System.currentTimeMillis(); startTime = MediaCodecHelper.getMonotonicMillis();
index = videoDecoder.dequeueInputBuffer(wait ? (infiniteWait ? -1 : 3000) : 0); index = videoDecoder.dequeueInputBuffer(wait ? (infiniteWait ? -1 : 3000) : 0);
if (index < 0) { if (index < 0) {
return index; return index;
} }
queueTime = System.currentTimeMillis(); queueTime = MediaCodecHelper.getMonotonicMillis();
if (queueTime - startTime >= 20) { if (queueTime - startTime >= 20) {
LimeLog.warning("Queue input buffer ran long: "+(queueTime - startTime)+" ms"); LimeLog.warning("Queue input buffer ran long: " + (queueTime - startTime) + " ms");
} }
return index; return index;
@@ -223,7 +220,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private void startLegacyRendererThread() private void startLegacyRendererThread()
{ {
rendererThread = new Thread() { rendererThread = new Thread() {
@SuppressWarnings("deprecation")
@Override @Override
public void run() { public void run() {
BufferInfo info = new BufferInfo(); BufferInfo info = new BufferInfo();
@@ -240,7 +236,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
inputIndex = dequeueInputBuffer(false, false); inputIndex = dequeueInputBuffer(false, false);
du = depacketizer.pollNextDecodeUnit(); du = depacketizer.pollNextDecodeUnit();
if (du != null) { if (du != null) {
lastDuDequeueTime = System.currentTimeMillis(); lastDuDequeueTime = MediaCodecHelper.getMonotonicMillis();
notifyDuReceived(du); notifyDuReceived(du);
} }
@@ -249,7 +245,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
break; break;
} }
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); submitDecodeUnit(du, inputIndex);
du = null; du = null;
inputIndex = -1; inputIndex = -1;
@@ -282,7 +278,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
if (du == null) { if (du == null) {
du = depacketizer.pollNextDecodeUnit(); du = depacketizer.pollNextDecodeUnit();
if (du != null) { if (du != null) {
lastDuDequeueTime = System.currentTimeMillis(); lastDuDequeueTime = MediaCodecHelper.getMonotonicMillis();
notifyDuReceived(du); notifyDuReceived(du);
} }
} }
@@ -290,12 +286,12 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// If we've got both a decode unit and an input buffer, we'll // If we've got both a decode unit and an input buffer, we'll
// submit now. Otherwise, we wait until we have one. // submit now. Otherwise, we wait until we have one.
if (du != null && inputIndex >= 0) { if (du != null && inputIndex >= 0) {
long submissionTime = System.currentTimeMillis(); long submissionTime = MediaCodecHelper.getMonotonicMillis();
if (submissionTime - lastDuDequeueTime >= 20) { if (submissionTime - lastDuDequeueTime >= 20) {
LimeLog.warning("Receiving an input buffer took too long: "+(submissionTime - lastDuDequeueTime)+" ms"); LimeLog.warning("Receiving an input buffer took too long: "+(submissionTime - lastDuDequeueTime)+" ms");
} }
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); submitDecodeUnit(du, inputIndex);
// DU and input buffer have both been consumed // DU and input buffer have both been consumed
du = null; du = null;
@@ -321,7 +317,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
videoDecoder.releaseOutputBuffer(lastIndex, true); videoDecoder.releaseOutputBuffer(lastIndex, true);
// Add delta time to the totals (excluding probable outliers) // Add delta time to the totals (excluding probable outliers)
long delta = System.currentTimeMillis()-(presentationTimeUs/1000); long delta = MediaCodecHelper.getMonotonicMillis()-(presentationTimeUs/1000);
if (delta >= 0 && delta < 1000) { if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta; decoderTimeMs += delta;
totalTimeMs += delta; totalTimeMs += delta;
@@ -335,9 +331,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
LockSupport.parkNanos(1); LockSupport.parkNanos(1);
} }
break; break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
LimeLog.info("Output buffers changed");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
LimeLog.info("Output format changed"); LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat()); LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
@@ -365,7 +358,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Start the decoder // Start the decoder
videoDecoder.start(); videoDecoder.start();
videoDecoderInputBuffers = videoDecoder.getInputBuffers(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
legacyInputBuffers = videoDecoder.getInputBuffers();
}
if (directSubmit) { if (directSubmit) {
startDirectSubmitRendererThread(); startDirectSubmitRendererThread();
@@ -406,7 +401,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
for (i = 0; i < 25; i++) { for (i = 0; i < 25; i++) {
try { try {
videoDecoder.queueInputBuffer(inputBufferIndex, videoDecoder.queueInputBuffer(inputBufferIndex,
0, length, offset, length,
timestampUs, codecFlags); timestampUs, codecFlags);
break; break;
} catch (Exception e) { } catch (Exception e) {
@@ -420,9 +415,27 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} }
} }
// Using the new getInputBuffer() API on Lollipop allows
// the framework to do some performance optimizations for us
private ByteBuffer getEmptyInputBuffer(int inputBufferIndex) {
ByteBuffer buf;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
buf = videoDecoder.getInputBuffer(inputBufferIndex);
}
else {
buf = legacyInputBuffers[inputBufferIndex];
// Clear old input data pre-Lollipop
buf.clear();
}
return buf;
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) { private void submitDecodeUnit(DecodeUnit decodeUnit, int inputBufferIndex) {
long timestampUs = System.currentTimeMillis() * 1000; long timestampUs = System.nanoTime() / 1000;
if (timestampUs <= lastTimestampUs) { if (timestampUs <= lastTimestampUs) {
// We can't submit multiple buffers with the same timestamp // We can't submit multiple buffers with the same timestamp
// so bump it up by one before queuing // so bump it up by one before queuing
@@ -430,8 +443,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} }
lastTimestampUs = timestampUs; lastTimestampUs = timestampUs;
// Clear old input data ByteBuffer buf = getEmptyInputBuffer(inputBufferIndex);
buf.clear();
int codecFlags = 0; int codecFlags = 0;
int decodeUnitFlags = decodeUnit.getFlags(); int decodeUnitFlags = decodeUnit.getFlags();
@@ -483,19 +495,63 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
LimeLog.info("Patching num_ref_frames in SPS"); LimeLog.info("Patching num_ref_frames in SPS");
sps.num_ref_frames = 1; sps.num_ref_frames = 1;
// GFE 2.5.11 changed the SPS to add additional extensions
// Some devices don't like these so we remove them here.
sps.vuiParams.video_signal_type_present_flag = false;
sps.vuiParams.colour_description_present_flag = false;
sps.vuiParams.chroma_loc_info_present_flag = false;
if (needsSpsBitstreamFixup || isExynos4) { if (needsSpsBitstreamFixup || isExynos4) {
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag // The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
// or max_dec_frame_buffering which increases decoding latency on Tegra. // or max_dec_frame_buffering which increases decoding latency on Tegra.
LimeLog.info("Adding bitstream restrictions");
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction(); // GFE 2.5.11 started sending bitstream restrictions
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true; if (sps.vuiParams.bitstreamRestriction == null) {
LimeLog.info("Adding bitstream restrictions");
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
}
else {
LimeLog.info("Patching bitstream restrictions");
}
// Some devices throw errors if max_dec_frame_buffering < num_ref_frames
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = sps.num_ref_frames;
// These values are the defaults for the fields, but they are more aggressive
// than what GFE sends in 2.5.11, but it doesn't seem to cause picture problems.
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2; sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2;
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1; sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16; // log2_max_mv_length_horizontal and log2_max_mv_length_vertical are set to more
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0; // conservative values by GFE 2.5.11. We'll let those values stand.
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1; }
else {
// Devices that didn't/couldn't get bitstream restrictions before GFE 2.5.11
// will continue to not receive them now
sps.vuiParams.bitstreamRestriction = null;
}
// Set constraint flags 4 & 5 to make this Constrained High Profile
// which allows the decoder to assume there will be no B-frames and
// reduce delay and buffering accordingly.
//
// This profile is fairly new (standardized in H264 revision 2012-06) and
// it's known that at least some devices don't like these previously unused
// constraints being set. To minimize the chance of interfering with old devices,
// I'm only setting these on KitKat or higher. It's an arbitrary limitation and could
// change if it causes problems.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
sps.constraint_set_4_flag = true;
sps.constraint_set_5_flag = true;
}
else {
// Force the constraints unset for < 4.4 (some may be set by default)
sps.constraint_set_4_flag = false;
sps.constraint_set_5_flag = false;
} }
// If we need to hack this SPS to say we're baseline, do so now // If we need to hack this SPS to say we're baseline, do so now
@@ -549,9 +605,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private void replaySps() { private void replaySps() {
int inputIndex = dequeueInputBuffer(true, true); int inputIndex = dequeueInputBuffer(true, true);
ByteBuffer inputBuffer = videoDecoderInputBuffers[inputIndex]; ByteBuffer inputBuffer = getEmptyInputBuffer(inputIndex);
inputBuffer.clear();
// Write the Annex B header // Write the Annex B header
inputBuffer.put(new byte[]{0x00, 0x00, 0x00, 0x01, 0x67}); inputBuffer.put(new byte[]{0x00, 0x00, 0x00, 0x01, 0x67});
@@ -568,7 +622,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Queue the new SPS // Queue the new SPS
queueInputBuffer(inputIndex, queueInputBuffer(inputIndex,
0, inputBuffer.position(), 0, inputBuffer.position(),
System.currentTimeMillis() * 1000, System.nanoTime() / 1000,
MediaCodec.BUFFER_FLAG_CODEC_CONFIG); MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
LimeLog.info("SPS replay complete"); LimeLog.info("SPS replay complete");
@@ -609,7 +663,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} }
private void notifyDuReceived(DecodeUnit du) { private void notifyDuReceived(DecodeUnit du) {
long currentTime = System.currentTimeMillis(); long currentTime = MediaCodecHelper.getMonotonicMillis();
long delta = currentTime-du.getReceiveTimestamp(); long delta = currentTime-du.getReceiveTimestamp();
if (delta >= 0 && delta < 1000) { if (delta >= 0 && delta < 1000) {
totalTimeMs += currentTime-du.getReceiveTimestamp(); totalTimeMs += currentTime-du.getReceiveTimestamp();
@@ -633,7 +687,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} }
if (inputIndex >= 0) { if (inputIndex >= 0) {
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); submitDecodeUnit(du, inputIndex);
} }
} }
@@ -664,6 +718,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n"; str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n"; str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
str += "Total frames: "+renderer.totalFrames+"\n"; str += "Total frames: "+renderer.totalFrames+"\n";
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
if (currentBuffer != null) { if (currentBuffer != null) {
str += "Current buffer: "; str += "Current buffer: ";
@@ -58,7 +58,6 @@ public class MediaCodecHelper {
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>(); spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia"); spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom"); spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm"); spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
baselineProfileHackPrefixes = new LinkedList<String>(); baselineProfileHackPrefixes = new LinkedList<String>();
@@ -83,6 +82,10 @@ public class MediaCodecHelper {
return false; return false;
} }
public static long getMonotonicMillis() {
return System.nanoTime() / 1000000L;
}
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) { public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) {
@@ -368,6 +368,11 @@ public class ComputerManagerService extends Service {
} }
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) { private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
// Fast poll this address first to determine if we can connect at the TCP layer
if (!fastPollIp(ipAddr)) {
return null;
}
try { try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(), NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
@@ -393,7 +398,7 @@ public class ComputerManagerService extends Service {
private boolean fastPollIp(InetAddress addr) { private boolean fastPollIp(InetAddress addr) {
Socket s = new Socket(); Socket s = new Socket();
try { try {
s.connect(new InetSocketAddress(addr, NvHTTP.PORT), FAST_POLL_TIMEOUT); s.connect(new InetSocketAddress(addr, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
s.close(); s.close();
return true; return true;
} catch (IOException e) { } catch (IOException e) {
@@ -451,7 +456,7 @@ public class ComputerManagerService extends Service {
return ComputerDetails.Reachability.OFFLINE; return ComputerDetails.Reachability.OFFLINE;
} }
private boolean pollComputer(ComputerDetails details) throws InterruptedException { private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails; ComputerDetails polledDetails;
ComputerDetails.Reachability reachability; ComputerDetails.Reachability reachability;
@@ -465,11 +470,11 @@ public class ComputerManagerService extends Service {
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localIp+", "+details.remoteIp+")"); LimeLog.info("Starting fast poll for "+details.name+" ("+details.localIp+", "+details.remoteIp+")");
reachability = fastPollPc(details.localIp, details.remoteIp); reachability = fastPollPc(details.localIp, details.remoteIp);
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString()); LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
}
// If no connection could be established to either IP address, there's nothing we can do // If no connection could be established to either IP address, there's nothing we can do
if (reachability == ComputerDetails.Reachability.OFFLINE) { if (reachability == ComputerDetails.Reachability.OFFLINE) {
return false; return null;
}
} }
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL); boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
@@ -481,6 +486,7 @@ public class ComputerManagerService extends Service {
polledDetails = tryPollIp(details, details.remoteIp); polledDetails = tryPollIp(details, details.remoteIp);
} }
InetAddress reachableAddr = null;
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) { if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
// Failed, so let's try the fallback // Failed, so let's try the fallback
if (!localFirst) { if (!localFirst) {
@@ -490,27 +496,59 @@ public class ComputerManagerService extends Service {
polledDetails = tryPollIp(details, details.remoteIp); polledDetails = tryPollIp(details, details.remoteIp);
} }
// The fallback poll worked
if (polledDetails != null) { if (polledDetails != null) {
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL : // The fallback poll worked
ComputerDetails.Reachability.REMOTE; reachableAddr = !localFirst ? details.localIp : details.remoteIp;
} }
} }
else if (polledDetails != null) { else if (polledDetails != null) {
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL : reachableAddr = localFirst ? details.localIp : details.remoteIp;
ComputerDetails.Reachability.REMOTE;
} }
// Machine was unreachable both tries if (reachableAddr == null) {
if (polledDetails == null) { return null;
}
if (polledDetails.remoteIp.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.localIp.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
}
else {
polledDetails.reachability = ComputerDetails.Reachability.UNKNOWN;
}
return new ReachabilityTuple(polledDetails, reachableAddr);
}
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
ReachabilityTuple initialReachTuple = pollForReachability(details);
if (initialReachTuple == null) {
return false; return false;
} }
if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) {
// Neither IP address reported in the serverinfo response was the one we used.
// Poll again to see if we can contact this machine on either of its reported addresses.
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer);
if (confirmationReachTuple == null) {
// Neither of those seem to work, so we'll hold onto the address that did work
initialReachTuple.computer.localIp = initialReachTuple.reachableAddress;
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL;
}
else {
// We got it on one of the returned addresses; replace the original reach tuple
// with the new one
initialReachTuple = confirmationReachTuple;
}
}
// Save the old MAC address // Save the old MAC address
String savedMacAddress = details.macAddress; String savedMacAddress = details.macAddress;
// If we got here, it's reachable // If we got here, it's reachable
details.update(polledDetails); details.update(initialReachTuple.computer);
// If the new MAC address is empty, restore the old one (workaround for GFE bug) // If the new MAC address is empty, restore the old one (workaround for GFE bug)
if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) { if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) {
@@ -639,7 +677,7 @@ public class ComputerManagerService extends Service {
if (cacheOut != null) { if (cacheOut != null) {
cacheOut.close(); cacheOut.close();
} }
} catch (IOException e) {} } catch (IOException ignored) {}
} }
// Update the computer // Update the computer
@@ -685,4 +723,14 @@ class PollingTuple {
this.computer = computer; this.computer = computer;
this.thread = thread; this.thread = thread;
} }
}
class ReachabilityTuple {
public final InetAddress reachableAddress;
public final ComputerDetails computer;
public ReachabilityTuple(ComputerDetails computer, InetAddress reachableAddress) {
this.computer = computer;
this.reachableAddress = reachableAddress;
}
} }
@@ -27,7 +27,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
private final CachedAppAssetLoader loader; private final CachedAppAssetLoader loader;
public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) throws KeyManagementException, NoSuchAlgorithmException { public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) {
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading); super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading);
int dpi = activity.getResources().getDisplayMetrics().densityDpi; int dpi = activity.getResources().getDisplayMetrics().densityDpi;
@@ -105,7 +105,11 @@ public class CachedAppAssetLoader {
// If there's a task associated with this load, we should return the bitmap // If there's a task associated with this load, we should return the bitmap
if (task != null) { if (task != null) {
return diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider); // If the cached bitmap is valid, return it. Otherwise, we'll try the load again
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
if (bmp != null) {
return bmp;
}
} }
else { else {
// Otherwise it's a background load and we return nothing // Otherwise it's a background load and we return nothing
@@ -13,6 +13,9 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
public class DiskAssetLoader { public class DiskAssetLoader {
// 5 MB
private final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
private final File cacheDir; private final File cacheDir;
public DiskAssetLoader(File cacheDir) { public DiskAssetLoader(File cacheDir) {
@@ -27,13 +30,19 @@ public class DiskAssetLoader {
InputStream in = null; InputStream in = null;
Bitmap bmp = null; Bitmap bmp = null;
try { try {
// Make sure the cached asset doesn't exceed the maximum size
if (CacheHelper.getFileSize(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png") > MAX_ASSET_SIZE) {
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
return null;
}
in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png"); in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize; options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
bmp = BitmapFactory.decodeStream(in, null, options); bmp = BitmapFactory.decodeStream(in, null, options);
} catch (FileNotFoundException ignored) { } catch (IOException ignored) {
} catch (IOException e) {
e.printStackTrace();
} finally { } finally {
if (in != null) { if (in != null) {
try { try {
@@ -51,9 +60,11 @@ public class DiskAssetLoader {
public void populateCacheWithStream(CachedAppAssetLoader.LoaderTuple tuple, InputStream input) { public void populateCacheWithStream(CachedAppAssetLoader.LoaderTuple tuple, InputStream input) {
OutputStream out = null; OutputStream out = null;
boolean success = false;
try { try {
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png"); out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
CacheHelper.writeInputStreamToOutputStream(input, out); CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
success = true;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@@ -62,6 +73,11 @@ public class DiskAssetLoader {
out.close(); out.close();
} catch (IOException ignored) {} } catch (IOException ignored) {}
} }
if (!success) {
LimeLog.warning("Unable to populate cache with tuple: "+tuple);
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
}
} }
} }
} }
@@ -26,7 +26,7 @@ public class NetworkAssetLoader {
InputStream in = null; InputStream in = null;
try { try {
in = http.getBoxArt(tuple.app); in = http.getBoxArt(tuple.app);
} catch (IOException e) {} } catch (IOException ignored) {}
if (in != null) { if (in != null) {
LimeLog.info("Network asset load complete: " + tuple); LimeLog.info("Network asset load complete: " + tuple);
@@ -3,6 +3,7 @@ package com.limelight.preferences;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
public class PreferenceConfiguration { public class PreferenceConfiguration {
@@ -69,9 +70,18 @@ public class PreferenceConfiguration {
public static boolean getDefaultSmallMode(Context context) { public static boolean getDefaultSmallMode(Context context) {
PackageManager manager = context.getPackageManager(); PackageManager manager = context.getPackageManager();
if (manager != null && manager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)) { if (manager != null) {
// TVs shouldn't use small mode by default // TVs shouldn't use small mode by default
return false; if (manager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
return false;
}
// API 21 uses LEANBACK instead of TELEVISION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
if (manager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
return false;
}
}
} }
// Use small mode on anything smaller than a 7" tablet // Use small mode on anything smaller than a 7" tablet
@@ -16,14 +16,17 @@ import com.limelight.utils.UiHelper;
import java.util.Locale; import java.util.Locale;
public class StreamSettings extends Activity { public class StreamSettings extends Activity {
private PreferenceConfiguration previousPrefs;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language; previousPrefs = PreferenceConfiguration.readPreferences(this);
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
if (!previousPrefs.language.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration()); Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale); config.locale = new Locale(previousPrefs.language);
getResources().updateConfiguration(config, getResources().getDisplayMetrics()); getResources().updateConfiguration(config, getResources().getDisplayMetrics());
} }
@@ -39,10 +42,16 @@ public class StreamSettings extends Activity {
public void onBackPressed() { public void onBackPressed() {
finish(); finish();
// Restart the PC view to apply UI changes // Check for changes that require a UI reload to take effect
Intent intent = new Intent(this, PcView.class); PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); if (newPrefs.listMode != previousPrefs.listMode ||
startActivity(intent, null); newPrefs.smallIconMode != previousPrefs.smallIconMode ||
!newPrefs.language.equals(previousPrefs.language)) {
// Restart the PC view to apply UI changes
Intent intent = new Intent(this, PcView.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent, null);
}
} }
public static class SettingsFragment extends PreferenceFragment { public static class SettingsFragment extends PreferenceFragment {
@@ -30,6 +30,14 @@ public class CacheHelper {
return f; return f;
} }
public static long getFileSize(File root, String... path) {
return openPath(false, root, path).length();
}
public static boolean deleteCacheFile(File root, String... path) {
return openPath(false, root, path).delete();
}
public static boolean cacheFileExists(File root, String... path) { public static boolean cacheFileExists(File root, String... path) {
return openPath(false, root, path).exists(); return openPath(false, root, path).exists();
} }
@@ -42,11 +50,15 @@ public class CacheHelper {
return new BufferedOutputStream(new FileOutputStream(openPath(true, root, path))); return new BufferedOutputStream(new FileOutputStream(openPath(true, root, path)));
} }
public static void writeInputStreamToOutputStream(InputStream in, OutputStream out) throws IOException { public static void writeInputStreamToOutputStream(InputStream in, OutputStream out, long maxLength) throws IOException {
byte[] buf = new byte[4096]; byte[] buf = new byte[4096];
int bytesRead; int bytesRead;
while ((bytesRead = in.read(buf)) != -1) { while ((bytesRead = in.read(buf)) != -1) {
maxLength -= bytesRead;
if (maxLength <= 0) {
throw new IOException("Stream exceeded max size");
}
out.write(buf, 0, bytesRead); out.write(buf, 0, bytesRead);
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:1.1.0' classpath 'com.android.tools.build:gradle:1.3.0'
} }
} }
+10 -4
View File
@@ -12,8 +12,8 @@ This file serves to document some of the decoder errata when using MediaCodec ha
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS on first I-frame 4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS on first I-frame
- Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4 at 1080p only - Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4 at 1080p only
5. Some decoders will hang if max_dec_frame_buffering is not present 5. Some decoders will hang or crash if max_dec_frame_buffering is not present and level_idc is >= 50
- Affected decoders: MediaTek decoder in Fire HD 7 (2014) - Affected decoders: MediaTek decoder in Fire HD 6/7 (2014)
6. Some decoders will hang if max_dec_frame_buffering IS present 6. Some decoders will hang if max_dec_frame_buffering IS present
- Affected decoders: Exynos 5 in Galaxy Note 10.1 (2014) - Affected decoders: Exynos 5 in Galaxy Note 10.1 (2014)
@@ -21,5 +21,11 @@ This file serves to document some of the decoder errata when using MediaCodec ha
7. Some decoders will not enter low latency mode if adaptive playback is enabled 7. Some decoders will not enter low latency mode if adaptive playback is enabled
- Affected decoders: Intel decoder in Nexus Player - Affected decoders: Intel decoder in Nexus Player
8. Some decoders will not enter low latency mode if the profile isn't baseline in the first SPS. 8. Some decoders will not enter low latency mode if the profile isn't baseline in the first SPS because B-frames may be present.
- Affected decoders: Intel decoder in Nexus Player - Affected decoders: Intel decoder in Nexus Player (prior to Android 6.0)
9. Some decoders will not enter low latency mode if the profile isn't constrained high profile because B-frames may be present.
- Affected decoders: Intel decoder in Nexus Player (after Android 6.0)
10. Some decoders actually suffer increased latency when max_dec_frame_buffering=1
- Affected decoders: MediaTek decoder in Fire TV 2015
+4 -4
View File
@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> <module external.linked.project.id="moonlight-android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager"> <component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle"> <facet type="java-gradle" name="Java-Gradle">
<configuration> <configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" /> <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration> </configuration>
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" /> <excludeFolder url="file://$MODULE_DIR$/.gradle" />
@@ -15,5 +16,4 @@
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>