Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 964d2ce59c | |||
| dc52684cbc | |||
| 191bedc56f | |||
| 47b2ace7fd | |||
| 9fb7359a3e | |||
| 4a5de26406 | |||
| 6fa18e126f | |||
| 1149002e0c | |||
| d704cb0b50 | |||
| d59e5ae9cf | |||
| 4587c1550d | |||
| b5bd329ada | |||
| beccd7a4ac | |||
| 61262fa939 | |||
| 7c6b006631 | |||
| dbd149354a | |||
| 4306ba5004 | |||
| 6de370b82f | |||
| 45781666b8 | |||
| 538231eb6f | |||
| eb74f87f2c | |||
| 59d71ffdcf | |||
| d1b93d4011 | |||
| d8ddf2e740 | |||
| 581327dc8e | |||
| 76e4512a0c | |||
| efdd55beca | |||
| 2c115649b9 | |||
| 2ddcc31a93 | |||
| 3bcce5b749 | |||
| 80dac27214 | |||
| 4a1177d048 | |||
| 4725d8f270 | |||
| 07b3528515 | |||
| d2d1b1ea26 | |||
| 232b897abc | |||
| efd076bc6c | |||
| cc877480ff | |||
| 363145a284 | |||
| 755571ad33 | |||
| 39edb55721 | |||
| 15aa7ecc2e | |||
| ce9e91153e | |||
| 9ee0a46606 | |||
| 20dc351f4c | |||
| c30c54d562 | |||
| 45ff51c0d2 | |||
| 5b86e99138 | |||
| 0c72910eb7 |
+3
-3
@@ -8,11 +8,11 @@ android:
|
|||||||
components:
|
components:
|
||||||
- tools
|
- tools
|
||||||
- platform-tools
|
- platform-tools
|
||||||
- build-tools-29.0.3
|
- build-tools-30.0.0
|
||||||
- android-29
|
- android-30
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sdkmanager --list
|
- sdkmanager --list
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- yes | sdkmanager "ndk;20.0.5594570"
|
- yes | sdkmanager "ndk;21.0.6113669"
|
||||||
|
|||||||
@@ -2,55 +2,26 @@
|
|||||||
|
|
||||||
[](https://travis-ci.org/moonlight-stream/moonlight-android)
|
[](https://travis-ci.org/moonlight-stream/moonlight-android)
|
||||||
|
|
||||||
[Moonlight](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
[Moonlight for Android](https://moonlight-stream.org) 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.
|
|
||||||
|
|
||||||
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
|
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
|
||||||
whether in your own home or over the internet.
|
whether in your own home or over the internet.
|
||||||
|
|
||||||
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
|
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [iOS/tvOS client](https://github.com/moonlight-stream/moonlight-ios).
|
||||||
|
|
||||||
## Features
|
Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed project information, setup guide, or troubleshooting steps.
|
||||||
|
|
||||||
* Streams any of your games from your PC to your Android device
|
## Downloads
|
||||||
* Full gamepad support for MOGA, Xbox 360, PS3, OUYA, and Shield
|
* [Google Play Store](https://play.google.com/store/apps/details?id=com.limelight)
|
||||||
* Automatically finds GameStream-compatible PCs on your network
|
* [Amazon App Store](https://www.amazon.com/gp/product/B00JK4MFN2)
|
||||||
|
* [F-Droid](https://f-droid.org/packages/com.limelight)
|
||||||
## Installation
|
* [APK](https://github.com/moonlight-stream/moonlight-android/releases)
|
||||||
|
|
||||||
* Download and install Moonlight for Android from
|
|
||||||
[Google Play](https://play.google.com/store/apps/details?id=com.limelight), [F-Droid](https://f-droid.org/packages/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
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
* [GameStream compatible](http://shield.nvidia.com/play-pc-games/) computer with an NVIDIA GeForce GTX 600 series or higher desktop or mobile GPU (GT-series and AMD GPUs not supported)
|
|
||||||
* Android device running 4.1 (Jelly Bean) or higher
|
|
||||||
* High-end wireless router (802.11n dual-band recommended)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
* Turn on GameStream in the GFE settings
|
|
||||||
* If you are connecting from outside the same network, turn on internet
|
|
||||||
streaming
|
|
||||||
* When on the same network as your PC, open Moonlight and tap on your PC in the list
|
|
||||||
* Accept the pairing confirmation on your PC and add the PIN if needed
|
|
||||||
* Tap your PC again to view the list of apps to stream
|
|
||||||
* Play games!
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
|
|
||||||
|
|
||||||
1. Fork us
|
|
||||||
2. Write code
|
|
||||||
3. Send Pull Requests
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
* Install Android Studio and the Android NDK
|
* Install Android Studio and the Android NDK
|
||||||
* Run ‘git submodule update --init --recursive’ from within moonlight-android/
|
* Run ‘git submodule update --init --recursive’ from within moonlight-android/
|
||||||
* In moonlight-android/, create a file called ‘local.properties’. Add an ‘ndk.dir=’ property to the local.properties file and set it equal to your NDK directory.
|
* In moonlight-android/, create a file called ‘local.properties’. Add an ‘ndk.dir=’ property to the local.properties file and set it equal to your NDK directory.
|
||||||
* Build the APK using Android Studio
|
* Build the APK using Android Studio or gradle
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -1,14 +1,14 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
|
|
||||||
versionName "9.2"
|
versionName "9.6"
|
||||||
versionCode = 222
|
versionCode = 231
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "root"
|
flavorDimensions "root"
|
||||||
|
|||||||
@@ -120,7 +120,8 @@
|
|||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:theme="@style/StreamTheme">
|
android:theme="@style/StreamTheme"
|
||||||
|
android:preferMinimalPostProcessing="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="com.limelight.AppView" />
|
android:value="com.limelight.AppView" />
|
||||||
|
|||||||
@@ -434,8 +434,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case VIEW_DETAILS_ID:
|
case VIEW_DETAILS_ID:
|
||||||
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details),
|
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details), app.app.toString(), false);
|
||||||
getResources().getString(R.string.applist_details_id) + " " + app.app.getAppId(), false);
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case CREATE_SHORTCUT_ID:
|
case CREATE_SHORTCUT_ID:
|
||||||
@@ -565,9 +564,8 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAdapterFragmentLayoutId() {
|
public int getAdapterFragmentLayoutId() {
|
||||||
return PreferenceConfiguration.readPreferences(this).listMode ?
|
return PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
|
||||||
R.layout.list_view : (PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
|
R.layout.app_grid_view_small : R.layout.app_grid_view;
|
||||||
R.layout.app_grid_view_small : R.layout.app_grid_view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import com.limelight.binding.input.ControllerHandler;
|
|||||||
import com.limelight.binding.input.KeyboardTranslator;
|
import com.limelight.binding.input.KeyboardTranslator;
|
||||||
import com.limelight.binding.input.capture.InputCaptureManager;
|
import com.limelight.binding.input.capture.InputCaptureManager;
|
||||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||||
import com.limelight.binding.input.TouchContext;
|
import com.limelight.binding.input.touch.AbsoluteTouchContext;
|
||||||
|
import com.limelight.binding.input.touch.RelativeTouchContext;
|
||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
|
import com.limelight.binding.input.touch.TouchContext;
|
||||||
import com.limelight.binding.input.virtual_controller.VirtualController;
|
import com.limelight.binding.input.virtual_controller.VirtualController;
|
||||||
import com.limelight.binding.video.CrashListener;
|
import com.limelight.binding.video.CrashListener;
|
||||||
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
import com.limelight.binding.video.MediaCodecDecoderRenderer;
|
||||||
@@ -61,6 +63,7 @@ import android.view.Display;
|
|||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnGenericMotionListener;
|
import android.view.View.OnGenericMotionListener;
|
||||||
@@ -95,8 +98,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private static final int REFERENCE_HORIZ_RES = 1280;
|
private static final int REFERENCE_HORIZ_RES = 1280;
|
||||||
private static final int REFERENCE_VERT_RES = 720;
|
private static final int REFERENCE_VERT_RES = 720;
|
||||||
|
|
||||||
private static final int STYLUS_DEAD_ZONE_DELAY = 250;
|
private static final int STYLUS_DOWN_DEAD_ZONE_DELAY = 100;
|
||||||
private static final int STYLUS_DEAD_ZONE_RADIUS = 50;
|
private static final int STYLUS_DOWN_DEAD_ZONE_RADIUS = 20;
|
||||||
|
|
||||||
|
private static final int STYLUS_UP_DEAD_ZONE_DELAY = 150;
|
||||||
|
private static final int STYLUS_UP_DEAD_ZONE_RADIUS = 50;
|
||||||
|
|
||||||
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
||||||
|
|
||||||
@@ -119,8 +125,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private boolean grabbedInput = true;
|
private boolean grabbedInput = true;
|
||||||
private boolean grabComboDown = false;
|
private boolean grabComboDown = false;
|
||||||
private StreamView streamView;
|
private StreamView streamView;
|
||||||
private long stylusDownTime = 0;
|
private long lastAbsTouchUpTime = 0;
|
||||||
private float stylusDownX, stylusDownY;
|
private long lastAbsTouchDownTime = 0;
|
||||||
|
private float lastAbsTouchUpX, lastAbsTouchUpY;
|
||||||
|
private float lastAbsTouchDownX, lastAbsTouchDownY;
|
||||||
|
|
||||||
private boolean isHidingOverlays;
|
private boolean isHidingOverlays;
|
||||||
private TextView notificationOverlayView;
|
private TextView notificationOverlayView;
|
||||||
@@ -205,11 +213,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||||
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && prefConfig.stretchVideo) {
|
if (prefConfig.stretchVideo) {
|
||||||
// Allow the activity to layout under notches if the fill-screen option
|
// Allow the activity to layout under notches if the fill-screen option
|
||||||
// was turned on by the user
|
// was turned on by the user
|
||||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||||
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||||
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for events on the game surface
|
// Listen for events on the game surface
|
||||||
@@ -469,9 +483,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Initialize touch contexts
|
// Initialize touch contexts
|
||||||
for (int i = 0; i < touchContextMap.length; i++) {
|
for (int i = 0; i < touchContextMap.length; i++) {
|
||||||
touchContextMap[i] = new TouchContext(conn, i,
|
if (!prefConfig.touchscreenTrackpad) {
|
||||||
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
|
touchContextMap[i] = new AbsoluteTouchContext(conn, i, streamView);
|
||||||
streamView);
|
}
|
||||||
|
else {
|
||||||
|
touchContextMap[i] = new RelativeTouchContext(conn, i,
|
||||||
|
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
|
||||||
|
streamView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use sustained performance mode on N+ to ensure consistent
|
// Use sustained performance mode on N+ to ensure consistent
|
||||||
@@ -597,29 +616,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Remove when Android R SDK is finalized
|
|
||||||
private static void setPreferMinimalPostProcessingWithReflection(WindowManager.LayoutParams windowLayoutParams, boolean isPreferred) {
|
|
||||||
// Build.VERSION.PREVIEW_SDK_INT was added in M
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && Build.VERSION.PREVIEW_SDK_INT == 0) {
|
|
||||||
// Don't attempt this reflection unless on Android R Developer Preview
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Field field = windowLayoutParams.getClass().getDeclaredField("preferMinimalPostProcessing");
|
|
||||||
field.set(windowLayoutParams, isPreferred);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float prepareDisplayForRendering() {
|
private float prepareDisplayForRendering() {
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||||
@@ -697,7 +693,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable HDMI ALLM (game mode) on Android R
|
// Enable HDMI ALLM (game mode) on Android R
|
||||||
setPreferMinimalPostProcessingWithReflection(windowLayoutParams, true);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
windowLayoutParams.preferMinimalPostProcessing = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the display mode change
|
// Apply the display mode change
|
||||||
getWindow().setAttributes(windowLayoutParams);
|
getWindow().setAttributes(windowLayoutParams);
|
||||||
@@ -853,34 +851,36 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
stopConnection();
|
stopConnection();
|
||||||
|
|
||||||
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
if (prefConfig.enableLatencyToast) {
|
||||||
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
||||||
String message = null;
|
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
||||||
if (averageEndToEndLat > 0) {
|
String message = null;
|
||||||
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
|
if (averageEndToEndLat > 0) {
|
||||||
if (averageDecoderLat > 0) {
|
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
|
||||||
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
|
if (averageDecoderLat > 0) {
|
||||||
|
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (averageDecoderLat > 0) {
|
||||||
|
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (averageDecoderLat > 0) {
|
|
||||||
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the video codec to the post-stream toast
|
// Add the video codec to the post-stream toast
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||||
message += " [H.265 HDR]";
|
message += " [H.265 HDR]";
|
||||||
|
}
|
||||||
|
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
||||||
|
message += " [H.265]";
|
||||||
|
}
|
||||||
|
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
||||||
|
message += " [H.264]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
|
||||||
message += " [H.265]";
|
|
||||||
}
|
|
||||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
|
||||||
message += " [H.264]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the tombstone count if we terminated normally
|
// Clear the tombstone count if we terminated normally
|
||||||
@@ -1176,6 +1176,23 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always update the position before sending any button events. If we're
|
||||||
|
// dealing with a stylus without hover support, our position might be
|
||||||
|
// significantly different than before.
|
||||||
|
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
|
||||||
|
// Send the deltas straight from the motion event
|
||||||
|
short deltaX = (short)inputCaptureProvider.getRelativeAxisX(event);
|
||||||
|
short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event);
|
||||||
|
|
||||||
|
if (deltaX != 0 || deltaY != 0) {
|
||||||
|
conn.sendMouseMove(deltaX, deltaY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (view != null) {
|
||||||
|
// Otherwise send absolute position
|
||||||
|
updateMousePosition(view, event);
|
||||||
|
}
|
||||||
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||||
// Send the vertical scroll packet
|
// Send the vertical scroll packet
|
||||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||||
@@ -1235,16 +1252,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
if (event.getPointerCount() == 1 && event.getActionIndex() == 0) {
|
if (event.getPointerCount() == 1 && event.getActionIndex() == 0) {
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||||
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) {
|
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) {
|
||||||
stylusDownTime = SystemClock.uptimeMillis();
|
lastAbsTouchDownTime = SystemClock.uptimeMillis();
|
||||||
stylusDownX = event.getX(0);
|
lastAbsTouchDownX = event.getX(0);
|
||||||
stylusDownY = event.getY(0);
|
lastAbsTouchDownY = event.getY(0);
|
||||||
|
|
||||||
// Stylus is left click
|
// Stylus is left click
|
||||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
||||||
} else if (event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) {
|
} else if (event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) {
|
||||||
stylusDownTime = SystemClock.uptimeMillis();
|
lastAbsTouchDownTime = SystemClock.uptimeMillis();
|
||||||
stylusDownX = event.getX(0);
|
lastAbsTouchDownX = event.getX(0);
|
||||||
stylusDownY = event.getY(0);
|
lastAbsTouchDownY = event.getY(0);
|
||||||
|
|
||||||
// Eraser is right click
|
// Eraser is right click
|
||||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
@@ -1252,18 +1269,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
|
else if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
|
||||||
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) {
|
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) {
|
||||||
// It looks odd to set these on ACTION_UP, but it makes sense.
|
lastAbsTouchUpTime = SystemClock.uptimeMillis();
|
||||||
// The last "down" position is actually when we come up.
|
lastAbsTouchUpX = event.getX(0);
|
||||||
stylusDownTime = SystemClock.uptimeMillis();
|
lastAbsTouchUpY = event.getY(0);
|
||||||
stylusDownX = event.getX(0);
|
|
||||||
stylusDownY = event.getY(0);
|
|
||||||
|
|
||||||
// Stylus is left click
|
// Stylus is left click
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
} else if (event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) {
|
} else if (event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) {
|
||||||
stylusDownTime = SystemClock.uptimeMillis();
|
lastAbsTouchUpTime = SystemClock.uptimeMillis();
|
||||||
stylusDownX = event.getX(0);
|
lastAbsTouchUpX = event.getX(0);
|
||||||
stylusDownY = event.getY(0);
|
lastAbsTouchUpY = event.getY(0);
|
||||||
|
|
||||||
// Eraser is right click
|
// Eraser is right click
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
@@ -1271,17 +1286,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get relative axis values if we can
|
|
||||||
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
|
|
||||||
// Send the deltas straight from the motion event
|
|
||||||
conn.sendMouseMove((short) inputCaptureProvider.getRelativeAxisX(event),
|
|
||||||
(short) inputCaptureProvider.getRelativeAxisY(event));
|
|
||||||
}
|
|
||||||
else if (view != null) {
|
|
||||||
// Otherwise send absolute position
|
|
||||||
updateMousePosition(view, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastButtonState = event.getButtonState();
|
lastButtonState = event.getButtonState();
|
||||||
}
|
}
|
||||||
// This case is for fingers
|
// This case is for fingers
|
||||||
@@ -1294,6 +1298,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (view == null && !prefConfig.touchscreenTrackpad) {
|
||||||
|
// Absolute touch events should be dropped outside our view.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int actionIndex = event.getActionIndex();
|
int actionIndex = event.getActionIndex();
|
||||||
|
|
||||||
int eventX = (int)event.getX(actionIndex);
|
int eventX = (int)event.getX(actionIndex);
|
||||||
@@ -1323,7 +1332,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
{
|
{
|
||||||
case MotionEvent.ACTION_POINTER_DOWN:
|
case MotionEvent.ACTION_POINTER_DOWN:
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
context.touchDownEvent(eventX, eventY);
|
for (TouchContext touchContext : touchContextMap) {
|
||||||
|
touchContext.setPointerCount(event.getPointerCount());
|
||||||
|
}
|
||||||
|
context.touchDownEvent(eventX, eventY, true);
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_POINTER_UP:
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
@@ -1336,9 +1348,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.touchUpEvent(eventX, eventY);
|
context.touchUpEvent(eventX, eventY);
|
||||||
|
for (TouchContext touchContext : touchContextMap) {
|
||||||
|
touchContext.setPointerCount(event.getPointerCount() - 1);
|
||||||
|
}
|
||||||
if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) {
|
if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) {
|
||||||
// The original secondary touch now becomes primary
|
// The original secondary touch now becomes primary
|
||||||
context.touchDownEvent((int)event.getX(1), (int)event.getY(1));
|
context.touchDownEvent((int)event.getX(1), (int)event.getY(1), false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
@@ -1370,6 +1385,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
case MotionEvent.ACTION_CANCEL:
|
case MotionEvent.ACTION_CANCEL:
|
||||||
for (TouchContext aTouchContext : touchContextMap) {
|
for (TouchContext aTouchContext : touchContextMap) {
|
||||||
aTouchContext.cancelTouch();
|
aTouchContext.cancelTouch();
|
||||||
|
aTouchContext.setPointerCount(0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1403,13 +1419,29 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
float eventY = event.getY(0);
|
float eventY = event.getY(0);
|
||||||
|
|
||||||
if (event.getPointerCount() == 1 && event.getActionIndex() == 0 &&
|
if (event.getPointerCount() == 1 && event.getActionIndex() == 0 &&
|
||||||
(event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER) ||
|
(event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER ||
|
||||||
event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS)
|
event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS))
|
||||||
{
|
{
|
||||||
if (SystemClock.uptimeMillis() - stylusDownTime <= STYLUS_DEAD_ZONE_DELAY &&
|
switch (event.getActionMasked()) {
|
||||||
Math.sqrt(Math.pow(eventX - stylusDownX, 2) + Math.pow(eventY - stylusDownY, 2)) <= STYLUS_DEAD_ZONE_RADIUS) {
|
case MotionEvent.ACTION_DOWN:
|
||||||
// Ignore small inputs shortly after the stylus has been pressed. This ensures users can reliably double click.
|
case MotionEvent.ACTION_HOVER_ENTER:
|
||||||
return;
|
case MotionEvent.ACTION_HOVER_EXIT:
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
if (SystemClock.uptimeMillis() - lastAbsTouchUpTime <= STYLUS_UP_DEAD_ZONE_DELAY &&
|
||||||
|
Math.sqrt(Math.pow(eventX - lastAbsTouchUpX, 2) + Math.pow(eventY - lastAbsTouchUpY, 2)) <= STYLUS_UP_DEAD_ZONE_RADIUS) {
|
||||||
|
// Enforce a small deadzone between touch up and hover or touch down to allow more precise double-clicking
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
if (SystemClock.uptimeMillis() - lastAbsTouchDownTime <= STYLUS_DOWN_DEAD_ZONE_DELAY &&
|
||||||
|
Math.sqrt(Math.pow(eventX - lastAbsTouchDownX, 2) + Math.pow(eventY - lastAbsTouchDownY, 2)) <= STYLUS_DOWN_DEAD_ZONE_RADIUS) {
|
||||||
|
// Enforce a small deadzone between touch down and move or touch up to allow more precise double-clicking
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1513,9 +1545,21 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Display the error dialog if it was an unexpected termination.
|
// Display the error dialog if it was an unexpected termination.
|
||||||
// Otherwise, just finish the activity immediately.
|
// Otherwise, just finish the activity immediately.
|
||||||
if (errorCode != 0) {
|
if (errorCode != MoonBridge.ML_ERROR_GRACEFUL_TERMINATION) {
|
||||||
|
String message;
|
||||||
|
|
||||||
|
switch (errorCode) {
|
||||||
|
case MoonBridge.ML_ERROR_NO_VIDEO_TRAFFIC:
|
||||||
|
message = getResources().getString(R.string.no_video_received_error);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
message = getResources().getString(R.string.conn_terminated_msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
||||||
getResources().getString(R.string.conn_terminated_msg), true);
|
message, true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
finish();
|
finish();
|
||||||
@@ -1635,6 +1679,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
@Override
|
@Override
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
surfaceCreated = true;
|
surfaceCreated = true;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// Tell the OS about our frame rate to allow it to adapt the display refresh rate appropriately
|
||||||
|
holder.getSurface().setFrameRate(prefConfig.fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -692,9 +692,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAdapterFragmentLayoutId() {
|
public int getAdapterFragmentLayoutId() {
|
||||||
return PreferenceConfiguration.readPreferences(this).listMode ?
|
return R.layout.pc_grid_view;
|
||||||
R.layout.list_view : (PreferenceConfiguration.readPreferences(this).smallIconMode ?
|
|
||||||
R.layout.pc_grid_view_small : R.layout.pc_grid_view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -827,6 +827,43 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override mode button for 8BitDo controllers
|
||||||
|
if (context.vendorId == 0x2dc8 && event.getScanCode() == 306) {
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((context.vendorId == 0x057e && context.productId == 0x2009) || // Switch Pro controller
|
||||||
|
(context.vendorId == 0x0f0d && context.productId == 0x00c1)) { // HORIPAD for Switch
|
||||||
|
switch (event.getScanCode()) {
|
||||||
|
case 0x130:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_A;
|
||||||
|
case 0x131:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_B;
|
||||||
|
case 0x132:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_X;
|
||||||
|
case 0x133:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||||
|
case 0x134:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||||
|
case 0x135:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||||
|
case 0x136:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_L2;
|
||||||
|
case 0x137:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_R2;
|
||||||
|
case 0x138:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||||
|
case 0x139:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_START;
|
||||||
|
case 0x13A:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||||
|
case 0x13B:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||||
|
case 0x13D:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (context.usesLinuxGamepadStandardFaceButtons) {
|
if (context.usesLinuxGamepadStandardFaceButtons) {
|
||||||
// Android's Generic.kl swaps BTN_NORTH and BTN_WEST
|
// Android's Generic.kl swaps BTN_NORTH and BTN_WEST
|
||||||
switch (event.getScanCode()) {
|
switch (event.getScanCode()) {
|
||||||
@@ -991,6 +1028,21 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
return keyCode;
|
return keyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int handleFlipFaceButtons(int keyCode) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_A:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_B;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_B:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_A;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_X:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||||
|
return KeyEvent.KEYCODE_BUTTON_X;
|
||||||
|
default:
|
||||||
|
return keyCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Vector2d populateCachedVector(float x, float y) {
|
private Vector2d populateCachedVector(float x, float y) {
|
||||||
// Reinitialize our cached Vector2d object
|
// Reinitialize our cached Vector2d object
|
||||||
inputVector.initialize(x, y);
|
inputVector.initialize(x, y);
|
||||||
@@ -1256,6 +1308,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
|
|
||||||
int keyCode = handleRemapping(context, event);
|
int keyCode = handleRemapping(context, event);
|
||||||
|
|
||||||
|
if (prefConfig.flipFaceButtons) {
|
||||||
|
keyCode = handleFlipFaceButtons(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1417,6 +1474,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
|
|
||||||
int keyCode = handleRemapping(context, event);
|
int keyCode = handleRemapping(context, event);
|
||||||
|
|
||||||
|
if (prefConfig.flipFaceButtons) {
|
||||||
|
keyCode = handleFlipFaceButtons(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,8 +75,10 @@ public class ShieldCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||||
return event.getAxisValue(AXIS_RELATIVE_X) != 0 ||
|
// All mouse events should use relative axes, even if they are zero. This avoids triggering
|
||||||
event.getAxisValue(AXIS_RELATIVE_Y) != 0;
|
// cursor jumps if we get an event with no associated motion, like ACTION_DOWN or ACTION_UP.
|
||||||
|
return event.getPointerCount() == 1 && event.getActionIndex() == 0 &&
|
||||||
|
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class XboxOneController extends AbstractXboxController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
|
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||||
|
private static final byte[] ONE_S_INIT = {0x05, 0x20, 0x00, 0x0f, 0x06};
|
||||||
private static final byte[] HORI_INIT = {0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
|
private static final byte[] HORI_INIT = {0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
|
||||||
0x00, 0x00, 0x00, (byte)0x80, 0x00};
|
0x00, 0x00, 0x00, (byte)0x80, 0x00};
|
||||||
private static final byte[] PDP_INIT1 = {0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14};
|
private static final byte[] PDP_INIT1 = {0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14};
|
||||||
@@ -38,6 +39,8 @@ public class XboxOneController extends AbstractXboxController {
|
|||||||
new InitPacket(0x0e6f, 0x0165, HORI_INIT),
|
new InitPacket(0x0e6f, 0x0165, HORI_INIT),
|
||||||
new InitPacket(0x0f0d, 0x0067, HORI_INIT),
|
new InitPacket(0x0f0d, 0x0067, HORI_INIT),
|
||||||
new InitPacket(0x0000, 0x0000, FW2015_INIT),
|
new InitPacket(0x0000, 0x0000, FW2015_INIT),
|
||||||
|
new InitPacket(0x045e, 0x02ea, ONE_S_INIT),
|
||||||
|
new InitPacket(0x045e, 0x0b00, ONE_S_INIT),
|
||||||
new InitPacket(0x0e6f, 0x0000, PDP_INIT1),
|
new InitPacket(0x0e6f, 0x0000, PDP_INIT1),
|
||||||
new InitPacket(0x0e6f, 0x0000, PDP_INIT2),
|
new InitPacket(0x0e6f, 0x0000, PDP_INIT2),
|
||||||
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT1),
|
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT1),
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package com.limelight.binding.input.touch;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.limelight.nvstream.NvConnection;
|
||||||
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class AbsoluteTouchContext implements TouchContext {
|
||||||
|
private int lastTouchDownX = 0;
|
||||||
|
private int lastTouchDownY = 0;
|
||||||
|
private long lastTouchDownTime = 0;
|
||||||
|
private int lastTouchUpX = 0;
|
||||||
|
private int lastTouchUpY = 0;
|
||||||
|
private long lastTouchUpTime = 0;
|
||||||
|
private int lastTouchLocationX = 0;
|
||||||
|
private int lastTouchLocationY = 0;
|
||||||
|
private boolean cancelled;
|
||||||
|
private boolean confirmedLongPress;
|
||||||
|
private boolean confirmedTap;
|
||||||
|
private Timer longPressTimer;
|
||||||
|
private Timer tapDownTimer;
|
||||||
|
private float accumulatedScrollDelta;
|
||||||
|
|
||||||
|
private final NvConnection conn;
|
||||||
|
private final int actionIndex;
|
||||||
|
private final View targetView;
|
||||||
|
|
||||||
|
private static final int SCROLL_SPEED_DIVISOR = 20;
|
||||||
|
|
||||||
|
private static final int LONG_PRESS_TIME_THRESHOLD = 650;
|
||||||
|
private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
|
||||||
|
|
||||||
|
private static final int DOUBLE_TAP_TIME_THRESHOLD = 250;
|
||||||
|
private static final int DOUBLE_TAP_DISTANCE_THRESHOLD = 60;
|
||||||
|
|
||||||
|
private static final int TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD = 100;
|
||||||
|
private static final int TOUCH_DOWN_DEAD_ZONE_DISTANCE_THRESHOLD = 20;
|
||||||
|
|
||||||
|
public AbsoluteTouchContext(NvConnection conn, int actionIndex, View view)
|
||||||
|
{
|
||||||
|
this.conn = conn;
|
||||||
|
this.actionIndex = actionIndex;
|
||||||
|
this.targetView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getActionIndex()
|
||||||
|
{
|
||||||
|
return actionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
|
||||||
|
{
|
||||||
|
if (!isNewFinger) {
|
||||||
|
// We don't handle finger transitions for absolute mode
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTouchLocationX = lastTouchDownX = eventX;
|
||||||
|
lastTouchLocationY = lastTouchDownY = eventY;
|
||||||
|
lastTouchDownTime = SystemClock.uptimeMillis();
|
||||||
|
cancelled = confirmedTap = confirmedLongPress = false;
|
||||||
|
accumulatedScrollDelta = 0;
|
||||||
|
|
||||||
|
if (actionIndex == 0) {
|
||||||
|
// Start the timers
|
||||||
|
startTapDownTimer();
|
||||||
|
startLongPressTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean distanceExceeds(int deltaX, int deltaY, double limit) {
|
||||||
|
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) > limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePosition(int eventX, int eventY) {
|
||||||
|
// We may get values slightly outside our view region on ACTION_HOVER_ENTER and ACTION_HOVER_EXIT.
|
||||||
|
// Normalize these to the view size. We can't just drop them because we won't always get an event
|
||||||
|
// right at the boundary of the view, so dropping them would result in our cursor never really
|
||||||
|
// reaching the sides of the screen.
|
||||||
|
eventX = Math.min(Math.max(eventX, 0), targetView.getWidth());
|
||||||
|
eventY = Math.min(Math.max(eventY, 0), targetView.getHeight());
|
||||||
|
|
||||||
|
conn.sendMousePosition((short)eventX, (short)eventY, (short)targetView.getWidth(), (short)targetView.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchUpEvent(int eventX, int eventY)
|
||||||
|
{
|
||||||
|
if (cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionIndex == 0) {
|
||||||
|
// Cancel the timers
|
||||||
|
cancelLongPressTimer();
|
||||||
|
cancelTapDownTimer();
|
||||||
|
|
||||||
|
// Raise the mouse buttons that we currently have down
|
||||||
|
if (confirmedLongPress) {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
else if (confirmedTap) {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If we get here, this means that the tap completed within the touch down
|
||||||
|
// deadzone time. We'll need to send the touch down and up events now at the
|
||||||
|
// original touch down position.
|
||||||
|
tapConfirmed();
|
||||||
|
try {
|
||||||
|
// FIXME: Sleeping on the main thread sucks
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTouchLocationX = lastTouchUpX = eventX;
|
||||||
|
lastTouchLocationY = lastTouchUpY = eventY;
|
||||||
|
lastTouchUpTime = SystemClock.uptimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void startLongPressTimer() {
|
||||||
|
longPressTimer = new Timer(true);
|
||||||
|
longPressTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (AbsoluteTouchContext.this) {
|
||||||
|
// Check if someone cancelled us
|
||||||
|
if (longPressTimer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncancellable now
|
||||||
|
longPressTimer = null;
|
||||||
|
|
||||||
|
// This timer should have already expired, but cancel it just in case
|
||||||
|
cancelTapDownTimer();
|
||||||
|
|
||||||
|
// Switch from a left click to a right click after a long press
|
||||||
|
confirmedLongPress = true;
|
||||||
|
if (confirmedTap) {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, LONG_PRESS_TIME_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void cancelLongPressTimer() {
|
||||||
|
if (longPressTimer != null) {
|
||||||
|
longPressTimer.cancel();
|
||||||
|
longPressTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void startTapDownTimer() {
|
||||||
|
tapDownTimer = new Timer(true);
|
||||||
|
tapDownTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (AbsoluteTouchContext.this) {
|
||||||
|
// Check if someone cancelled us
|
||||||
|
if (tapDownTimer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncancellable now
|
||||||
|
tapDownTimer = null;
|
||||||
|
|
||||||
|
// Start our tap
|
||||||
|
tapConfirmed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void cancelTapDownTimer() {
|
||||||
|
if (tapDownTimer != null) {
|
||||||
|
tapDownTimer.cancel();
|
||||||
|
tapDownTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tapConfirmed() {
|
||||||
|
if (confirmedTap || confirmedLongPress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmedTap = true;
|
||||||
|
cancelTapDownTimer();
|
||||||
|
|
||||||
|
// Left button down at original position
|
||||||
|
if (lastTouchDownTime - lastTouchUpTime > DOUBLE_TAP_TIME_THRESHOLD ||
|
||||||
|
distanceExceeds(lastTouchDownX - lastTouchUpX, lastTouchDownY - lastTouchUpY, DOUBLE_TAP_DISTANCE_THRESHOLD)) {
|
||||||
|
// Don't reposition for finger down events within the deadzone. This makes double-clicking easier.
|
||||||
|
updatePosition(lastTouchDownX, lastTouchDownY);
|
||||||
|
}
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean touchMoveEvent(int eventX, int eventY)
|
||||||
|
{
|
||||||
|
if (cancelled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionIndex == 0) {
|
||||||
|
if (distanceExceeds(eventX - lastTouchDownX, eventY - lastTouchDownY, LONG_PRESS_DISTANCE_THRESHOLD)) {
|
||||||
|
// Moved too far since touch down. Cancel the long press timer.
|
||||||
|
cancelLongPressTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore motion within the deadzone period after touch down
|
||||||
|
if (confirmedTap || distanceExceeds(eventX - lastTouchDownX, eventY - lastTouchDownY, TOUCH_DOWN_DEAD_ZONE_DISTANCE_THRESHOLD)) {
|
||||||
|
tapConfirmed();
|
||||||
|
updatePosition(eventX, eventY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (actionIndex == 1) {
|
||||||
|
accumulatedScrollDelta += (eventY - lastTouchLocationY) / (float)SCROLL_SPEED_DIVISOR;
|
||||||
|
if ((short)accumulatedScrollDelta != 0) {
|
||||||
|
conn.sendMouseHighResScroll((short)accumulatedScrollDelta);
|
||||||
|
accumulatedScrollDelta -= (short)accumulatedScrollDelta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTouchLocationX = eventX;
|
||||||
|
lastTouchLocationY = eventY;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelTouch() {
|
||||||
|
cancelled = true;
|
||||||
|
|
||||||
|
// Cancel the timers
|
||||||
|
cancelLongPressTimer();
|
||||||
|
cancelTapDownTimer();
|
||||||
|
|
||||||
|
// Raise the mouse buttons
|
||||||
|
if (confirmedLongPress) {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
else if (confirmedTap) {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPointerCount(int pointerCount) {
|
||||||
|
if (actionIndex == 0 && pointerCount > 1) {
|
||||||
|
cancelTouch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-8
@@ -1,5 +1,6 @@
|
|||||||
package com.limelight.binding.input;
|
package com.limelight.binding.input.touch;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
@@ -8,7 +9,7 @@ import com.limelight.nvstream.input.MouseButtonPacket;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class TouchContext {
|
public class RelativeTouchContext implements TouchContext {
|
||||||
private int lastTouchX = 0;
|
private int lastTouchX = 0;
|
||||||
private int lastTouchY = 0;
|
private int lastTouchY = 0;
|
||||||
private int originalTouchX = 0;
|
private int originalTouchX = 0;
|
||||||
@@ -32,8 +33,8 @@ public class TouchContext {
|
|||||||
private static final int TAP_TIME_THRESHOLD = 250;
|
private static final int TAP_TIME_THRESHOLD = 250;
|
||||||
private static final int DRAG_TIME_THRESHOLD = 650;
|
private static final int DRAG_TIME_THRESHOLD = 650;
|
||||||
|
|
||||||
public TouchContext(NvConnection conn, int actionIndex,
|
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
||||||
int referenceWidth, int referenceHeight, View view)
|
int referenceWidth, int referenceHeight, View view)
|
||||||
{
|
{
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
this.actionIndex = actionIndex;
|
this.actionIndex = actionIndex;
|
||||||
@@ -42,6 +43,7 @@ public class TouchContext {
|
|||||||
this.targetView = view;
|
this.targetView = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getActionIndex()
|
public int getActionIndex()
|
||||||
{
|
{
|
||||||
return actionIndex;
|
return actionIndex;
|
||||||
@@ -57,7 +59,7 @@ public class TouchContext {
|
|||||||
|
|
||||||
private boolean isTap()
|
private boolean isTap()
|
||||||
{
|
{
|
||||||
long timeDelta = System.currentTimeMillis() - originalTouchTime;
|
long timeDelta = SystemClock.uptimeMillis() - originalTouchTime;
|
||||||
|
|
||||||
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
|
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
|
||||||
}
|
}
|
||||||
@@ -72,7 +74,8 @@ public class TouchContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean touchDownEvent(int eventX, int eventY)
|
@Override
|
||||||
|
public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
|
||||||
{
|
{
|
||||||
// Get the view dimensions to scale inputs on this touch
|
// Get the view dimensions to scale inputs on this touch
|
||||||
xFactor = referenceWidth / (double)targetView.getWidth();
|
xFactor = referenceWidth / (double)targetView.getWidth();
|
||||||
@@ -80,7 +83,7 @@ public class TouchContext {
|
|||||||
|
|
||||||
originalTouchX = lastTouchX = eventX;
|
originalTouchX = lastTouchX = eventX;
|
||||||
originalTouchY = lastTouchY = eventY;
|
originalTouchY = lastTouchY = eventY;
|
||||||
originalTouchTime = System.currentTimeMillis();
|
originalTouchTime = SystemClock.uptimeMillis();
|
||||||
cancelled = confirmedDrag = confirmedMove = false;
|
cancelled = confirmedDrag = confirmedMove = false;
|
||||||
distanceMoved = 0;
|
distanceMoved = 0;
|
||||||
|
|
||||||
@@ -92,6 +95,7 @@ public class TouchContext {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void touchUpEvent(int eventX, int eventY)
|
public void touchUpEvent(int eventX, int eventY)
|
||||||
{
|
{
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
@@ -128,7 +132,7 @@ public class TouchContext {
|
|||||||
dragTimer.schedule(new TimerTask() {
|
dragTimer.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
synchronized (TouchContext.this) {
|
synchronized (RelativeTouchContext.this) {
|
||||||
// Check if someone already set move
|
// Check if someone already set move
|
||||||
if (confirmedMove) {
|
if (confirmedMove) {
|
||||||
return;
|
return;
|
||||||
@@ -179,6 +183,7 @@ public class TouchContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean touchMoveEvent(int eventX, int eventY)
|
public boolean touchMoveEvent(int eventX, int eventY)
|
||||||
{
|
{
|
||||||
if (eventX != lastTouchX || eventY != lastTouchY)
|
if (eventX != lastTouchX || eventY != lastTouchY)
|
||||||
@@ -223,6 +228,7 @@ public class TouchContext {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void cancelTouch() {
|
public void cancelTouch() {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
|
||||||
@@ -235,7 +241,11 @@ public class TouchContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isCancelled() {
|
public boolean isCancelled() {
|
||||||
return cancelled;
|
return cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPointerCount(int pointerCount) {}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.limelight.binding.input.touch;
|
||||||
|
|
||||||
|
public interface TouchContext {
|
||||||
|
int getActionIndex();
|
||||||
|
void setPointerCount(int pointerCount);
|
||||||
|
boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger);
|
||||||
|
boolean touchMoveEvent(int eventX, int eventY);
|
||||||
|
void touchUpEvent(int eventX, int eventY);
|
||||||
|
void cancelTouch();
|
||||||
|
boolean isCancelled();
|
||||||
|
}
|
||||||
+8
-4
@@ -205,7 +205,8 @@ public class VirtualControllerConfigurationLoader {
|
|||||||
|
|
||||||
controller.addElement(createDigitalButton(
|
controller.addElement(createDigitalButton(
|
||||||
VirtualControllerElement.EID_A,
|
VirtualControllerElement.EID_A,
|
||||||
ControllerPacket.A_FLAG, 0, 1, "A", -1, controller, context),
|
!config.flipFaceButtons ? ControllerPacket.A_FLAG : ControllerPacket.B_FLAG, 0, 1,
|
||||||
|
!config.flipFaceButtons ? "A" : "B", -1, controller, context),
|
||||||
screenScale(BUTTON_BASE_X, height) + rightDisplacement,
|
screenScale(BUTTON_BASE_X, height) + rightDisplacement,
|
||||||
screenScale(BUTTON_BASE_Y + 2 * BUTTON_SIZE, height),
|
screenScale(BUTTON_BASE_Y + 2 * BUTTON_SIZE, height),
|
||||||
screenScale(BUTTON_SIZE, height),
|
screenScale(BUTTON_SIZE, height),
|
||||||
@@ -214,7 +215,8 @@ public class VirtualControllerConfigurationLoader {
|
|||||||
|
|
||||||
controller.addElement(createDigitalButton(
|
controller.addElement(createDigitalButton(
|
||||||
VirtualControllerElement.EID_B,
|
VirtualControllerElement.EID_B,
|
||||||
ControllerPacket.B_FLAG, 0, 1, "B", -1, controller, context),
|
config.flipFaceButtons ? ControllerPacket.A_FLAG : ControllerPacket.B_FLAG, 0, 1,
|
||||||
|
config.flipFaceButtons ? "A" : "B", -1, controller, context),
|
||||||
screenScale(BUTTON_BASE_X + BUTTON_SIZE, height) + rightDisplacement,
|
screenScale(BUTTON_BASE_X + BUTTON_SIZE, height) + rightDisplacement,
|
||||||
screenScale(BUTTON_BASE_Y + BUTTON_SIZE, height),
|
screenScale(BUTTON_BASE_Y + BUTTON_SIZE, height),
|
||||||
screenScale(BUTTON_SIZE, height),
|
screenScale(BUTTON_SIZE, height),
|
||||||
@@ -223,7 +225,8 @@ public class VirtualControllerConfigurationLoader {
|
|||||||
|
|
||||||
controller.addElement(createDigitalButton(
|
controller.addElement(createDigitalButton(
|
||||||
VirtualControllerElement.EID_X,
|
VirtualControllerElement.EID_X,
|
||||||
ControllerPacket.X_FLAG, 0, 1, "X", -1, controller, context),
|
!config.flipFaceButtons ? ControllerPacket.X_FLAG : ControllerPacket.Y_FLAG, 0, 1,
|
||||||
|
!config.flipFaceButtons ? "X" : "Y", -1, controller, context),
|
||||||
screenScale(BUTTON_BASE_X - BUTTON_SIZE, height) + rightDisplacement,
|
screenScale(BUTTON_BASE_X - BUTTON_SIZE, height) + rightDisplacement,
|
||||||
screenScale(BUTTON_BASE_Y + BUTTON_SIZE, height),
|
screenScale(BUTTON_BASE_Y + BUTTON_SIZE, height),
|
||||||
screenScale(BUTTON_SIZE, height),
|
screenScale(BUTTON_SIZE, height),
|
||||||
@@ -232,7 +235,8 @@ public class VirtualControllerConfigurationLoader {
|
|||||||
|
|
||||||
controller.addElement(createDigitalButton(
|
controller.addElement(createDigitalButton(
|
||||||
VirtualControllerElement.EID_Y,
|
VirtualControllerElement.EID_Y,
|
||||||
ControllerPacket.Y_FLAG, 0, 1, "Y", -1, controller, context),
|
config.flipFaceButtons ? ControllerPacket.X_FLAG : ControllerPacket.Y_FLAG, 0, 1,
|
||||||
|
config.flipFaceButtons ? "X" : "Y", -1, controller, context),
|
||||||
screenScale(BUTTON_BASE_X, height) + rightDisplacement,
|
screenScale(BUTTON_BASE_X, height) + rightDisplacement,
|
||||||
screenScale(BUTTON_BASE_Y, height),
|
screenScale(BUTTON_BASE_Y, height),
|
||||||
screenScale(BUTTON_SIZE, height),
|
screenScale(BUTTON_SIZE, height),
|
||||||
|
|||||||
@@ -318,8 +318,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lowLatency) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lowLatency) {
|
||||||
videoFormat.setInteger(MediaCodecHelper.KEY_LOW_LATENCY, 1);
|
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
|
||||||
}
|
}
|
||||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
// Set the Qualcomm vendor low latency extension if the Android R option is unavailable
|
// Set the Qualcomm vendor low latency extension if the Android R option is unavailable
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ public class MediaCodecHelper {
|
|||||||
private static final List<String> blacklisted59FpsDecoderPrefixes;
|
private static final List<String> blacklisted59FpsDecoderPrefixes;
|
||||||
private static final List<String> qualcommDecoderPrefixes;
|
private static final List<String> qualcommDecoderPrefixes;
|
||||||
|
|
||||||
// FIXME: Remove when Android R SDK is finalized
|
|
||||||
public static final String FEATURE_LowLatency = "low-latency";
|
|
||||||
public static final String KEY_LOW_LATENCY = "low-latency";
|
|
||||||
|
|
||||||
private static boolean isLowEndSnapdragon = false;
|
private static boolean isLowEndSnapdragon = false;
|
||||||
private static boolean initialized = false;
|
private static boolean initialized = false;
|
||||||
|
|
||||||
@@ -343,10 +339,9 @@ public class MediaCodecHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderSupportsLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
public static boolean decoderSupportsLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
||||||
// KitKat added CodecCapabilities.isFeatureSupported()
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
try {
|
try {
|
||||||
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(FEATURE_LowLatency)) {
|
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
|
||||||
LimeLog.info("Low latency decoding mode supported (FEATURE_LowLatency)");
|
LimeLog.info("Low latency decoding mode supported (FEATURE_LowLatency)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.limelight.grid;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -40,10 +41,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
|
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
|
||||||
if (prefs.listMode) {
|
if (prefs.smallIconMode) {
|
||||||
return R.layout.simple_row;
|
|
||||||
}
|
|
||||||
else if (prefs.smallIconMode) {
|
|
||||||
return R.layout.app_grid_item_small;
|
return R.layout.app_grid_item_small;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -113,30 +111,17 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean populateImageView(ImageView imgView, ProgressBar prgView, AppView.AppObject obj) {
|
public void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, AppView.AppObject obj) {
|
||||||
// Let the cached asset loader handle it
|
// Let the cached asset loader handle it
|
||||||
loader.populateImageView(obj.app, imgView, prgView);
|
loader.populateImageView(obj.app, imgView, txtView);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean populateTextView(TextView txtView, AppView.AppObject obj) {
|
|
||||||
// Select the text view so it starts marquee mode
|
|
||||||
txtView.setSelected(true);
|
|
||||||
|
|
||||||
// Return false to use the app's toString method
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
|
|
||||||
if (obj.isRunning) {
|
if (obj.isRunning) {
|
||||||
// Show the play button overlay
|
// Show the play button overlay
|
||||||
overlayView.setImageResource(R.drawable.ic_play);
|
overlayView.setImageResource(R.drawable.ic_play);
|
||||||
return true;
|
overlayView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
overlayView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No overlay
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.widget.ProgressBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@@ -55,9 +54,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean populateImageView(ImageView imgView, ProgressBar prgView, T obj);
|
public abstract void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, T obj);
|
||||||
public abstract boolean populateTextView(TextView txtView, T obj);
|
|
||||||
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(int i, View convertView, ViewGroup viewGroup) {
|
public View getView(int i, View convertView, ViewGroup viewGroup) {
|
||||||
@@ -70,22 +67,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
|||||||
TextView txtView = convertView.findViewById(R.id.grid_text);
|
TextView txtView = convertView.findViewById(R.id.grid_text);
|
||||||
ProgressBar prgView = convertView.findViewById(R.id.grid_spinner);
|
ProgressBar prgView = convertView.findViewById(R.id.grid_spinner);
|
||||||
|
|
||||||
if (imgView != null) {
|
populateView(imgView, prgView, txtView, overlayView, itemList.get(i));
|
||||||
if (!populateImageView(imgView, prgView, itemList.get(i))) {
|
|
||||||
imgView.setImageBitmap(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!populateTextView(txtView, itemList.get(i))) {
|
|
||||||
txtView.setText(itemList.get(i).toString());
|
|
||||||
}
|
|
||||||
if (overlayView != null) {
|
|
||||||
if (!populateOverlayView(overlayView, itemList.get(i))) {
|
|
||||||
overlayView.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
overlayView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
|
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
|
||||||
if (prefs.listMode) {
|
return R.layout.pc_grid_item;
|
||||||
return R.layout.simple_row;
|
|
||||||
}
|
|
||||||
else if (prefs.smallIconMode) {
|
|
||||||
return R.layout.pc_grid_item_small;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return R.layout.pc_grid_item;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLayoutWithPreferences(Context context, PreferenceConfiguration prefs) {
|
public void updateLayoutWithPreferences(Context context, PreferenceConfiguration prefs) {
|
||||||
@@ -57,7 +49,8 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean populateImageView(ImageView imgView, ProgressBar prgView, PcView.ComputerObject obj) {
|
public void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, PcView.ComputerObject obj) {
|
||||||
|
imgView.setImageResource(R.drawable.ic_computer);
|
||||||
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
||||||
imgView.setAlpha(1.0f);
|
imgView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
@@ -72,12 +65,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
prgView.setVisibility(View.INVISIBLE);
|
prgView.setVisibility(View.INVISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
imgView.setImageResource(R.drawable.ic_computer);
|
txtView.setText(obj.details.name);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
|
|
||||||
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
||||||
txtView.setAlpha(1.0f);
|
txtView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
@@ -85,16 +73,10 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
txtView.setAlpha(0.4f);
|
txtView.setAlpha(0.4f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return false to use the computer's toString method
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
|
|
||||||
if (obj.details.state == ComputerDetails.State.OFFLINE) {
|
if (obj.details.state == ComputerDetails.State.OFFLINE) {
|
||||||
overlayView.setImageResource(R.drawable.ic_pc_offline);
|
overlayView.setImageResource(R.drawable.ic_pc_offline);
|
||||||
overlayView.setAlpha(0.4f);
|
overlayView.setAlpha(0.4f);
|
||||||
return true;
|
overlayView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
// We must check if the status is exactly online and unpaired
|
// We must check if the status is exactly online and unpaired
|
||||||
// to avoid colliding with the loading spinner when status is unknown
|
// to avoid colliding with the loading spinner when status is unknown
|
||||||
@@ -102,8 +84,10 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
|||||||
obj.details.pairState == PairingManager.PairState.NOT_PAIRED) {
|
obj.details.pairState == PairingManager.PairState.NOT_PAIRED) {
|
||||||
overlayView.setImageResource(R.drawable.ic_lock);
|
overlayView.setImageResource(R.drawable.ic_lock);
|
||||||
overlayView.setAlpha(1.0f);
|
overlayView.setAlpha(1.0f);
|
||||||
return true;
|
overlayView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
overlayView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import android.graphics.drawable.BitmapDrawable;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.limelight.R;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ public class CachedAppAssetLoader {
|
|||||||
memoryLoader.clearCache();
|
memoryLoader.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
private ScaledBitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
||||||
// Try 3 times
|
// Try 3 times
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
// Check again whether we've been cancelled or the image view is gone
|
// Check again whether we've been cancelled or the image view is gone
|
||||||
@@ -110,7 +113,7 @@ 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) {
|
||||||
// If the cached bitmap is valid, return it. Otherwise, we'll try the load again
|
// If the cached bitmap is valid, return it. Otherwise, we'll try the load again
|
||||||
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
ScaledBitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
return bmp;
|
return bmp;
|
||||||
}
|
}
|
||||||
@@ -132,29 +135,29 @@ public class CachedAppAssetLoader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
private class LoaderTask extends AsyncTask<LoaderTuple, Void, ScaledBitmap> {
|
||||||
private final WeakReference<ImageView> imageViewRef;
|
private final WeakReference<ImageView> imageViewRef;
|
||||||
private final WeakReference<ProgressBar> progressViewRef;
|
private final WeakReference<TextView> textViewRef;
|
||||||
private final boolean diskOnly;
|
private final boolean diskOnly;
|
||||||
|
|
||||||
private LoaderTuple tuple;
|
private LoaderTuple tuple;
|
||||||
|
|
||||||
public LoaderTask(ImageView imageView, ProgressBar prgView, boolean diskOnly) {
|
public LoaderTask(ImageView imageView, TextView textView, boolean diskOnly) {
|
||||||
this.imageViewRef = new WeakReference<>(imageView);
|
this.imageViewRef = new WeakReference<>(imageView);
|
||||||
this.progressViewRef = new WeakReference<>(prgView);
|
this.textViewRef = new WeakReference<>(textView);
|
||||||
this.diskOnly = diskOnly;
|
this.diskOnly = diskOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Bitmap doInBackground(LoaderTuple... params) {
|
protected ScaledBitmap doInBackground(LoaderTuple... params) {
|
||||||
tuple = params[0];
|
tuple = params[0];
|
||||||
|
|
||||||
// Check whether it has been cancelled or the views are gone
|
// Check whether it has been cancelled or the views are gone
|
||||||
if (isCancelled() || imageViewRef.get() == null || progressViewRef.get() == null) {
|
if (isCancelled() || imageViewRef.get() == null || textViewRef.get() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
ScaledBitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
if (bmp == null) {
|
if (bmp == null) {
|
||||||
if (!diskOnly) {
|
if (!diskOnly) {
|
||||||
// Try to load the asset from the network
|
// Try to load the asset from the network
|
||||||
@@ -183,45 +186,61 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
// If the current loader task for this view isn't us, do nothing
|
// If the current loader task for this view isn't us, do nothing
|
||||||
final ImageView imageView = imageViewRef.get();
|
final ImageView imageView = imageViewRef.get();
|
||||||
final ProgressBar prgView = progressViewRef.get();
|
final TextView textView = textViewRef.get();
|
||||||
if (getLoaderTask(imageView) == this) {
|
if (getLoaderTask(imageView) == this) {
|
||||||
// Now display the progress bar since we have to hit the network
|
|
||||||
if (prgView != null) {
|
|
||||||
prgView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set off another loader task on the network executor. This time our AsyncDrawable
|
// Set off another loader task on the network executor. This time our AsyncDrawable
|
||||||
// will use the app image placeholder bitmap, rather than an empty bitmap.
|
// will use the app image placeholder bitmap, rather than an empty bitmap.
|
||||||
LoaderTask task = new LoaderTask(imageView, prgView, false);
|
LoaderTask task = new LoaderTask(imageView, textView, false);
|
||||||
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), noAppImageBitmap, task);
|
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), noAppImageBitmap, task);
|
||||||
imageView.setVisibility(View.VISIBLE);
|
|
||||||
imageView.setImageDrawable(asyncDrawable);
|
imageView.setImageDrawable(asyncDrawable);
|
||||||
|
imageView.startAnimation(AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadein));
|
||||||
|
imageView.setVisibility(View.VISIBLE);
|
||||||
|
textView.setVisibility(View.VISIBLE);
|
||||||
task.executeOnExecutor(networkExecutor, tuple);
|
task.executeOnExecutor(networkExecutor, tuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Bitmap bitmap) {
|
protected void onPostExecute(final ScaledBitmap bitmap) {
|
||||||
// Do nothing if cancelled
|
// Do nothing if cancelled
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ImageView imageView = imageViewRef.get();
|
final ImageView imageView = imageViewRef.get();
|
||||||
final ProgressBar prgView = progressViewRef.get();
|
final TextView textView = textViewRef.get();
|
||||||
if (getLoaderTask(imageView) == this) {
|
if (getLoaderTask(imageView) == this) {
|
||||||
// Set the bitmap
|
// Fade in the box art
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
imageView.setImageBitmap(bitmap);
|
// Show the text if it's a placeholder
|
||||||
}
|
textView.setVisibility(isBitmapPlaceholder(bitmap) ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
// Hide the progress bar
|
if (imageView.getVisibility() == View.VISIBLE) {
|
||||||
if (prgView != null) {
|
// Fade out the placeholder first
|
||||||
prgView.setVisibility(View.INVISIBLE);
|
Animation fadeOutAnimation = AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadeout);
|
||||||
}
|
fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animation animation) {}
|
||||||
|
|
||||||
// Show the view
|
@Override
|
||||||
imageView.setVisibility(View.VISIBLE);
|
public void onAnimationEnd(Animation animation) {
|
||||||
|
// Fade in the new box art
|
||||||
|
imageView.setImageBitmap(bitmap.bitmap);
|
||||||
|
imageView.startAnimation(AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadein));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animation animation) {}
|
||||||
|
});
|
||||||
|
imageView.startAnimation(fadeOutAnimation);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// View is invisible already, so just fade in the new art
|
||||||
|
imageView.setImageBitmap(bitmap.bitmap);
|
||||||
|
imageView.startAnimation(AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadein));
|
||||||
|
imageView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +318,13 @@ public class CachedAppAssetLoader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean populateImageView(NvApp app, ImageView imgView, ProgressBar prgView) {
|
private boolean isBitmapPlaceholder(ScaledBitmap bitmap) {
|
||||||
|
return (bitmap == null) ||
|
||||||
|
(bitmap.originalWidth == 130 && bitmap.originalHeight == 180) || // GFE 2.0
|
||||||
|
(bitmap.originalWidth == 628 && bitmap.originalHeight == 888); // GFE 3.0
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean populateImageView(NvApp app, ImageView imgView, TextView textView) {
|
||||||
LoaderTuple tuple = new LoaderTuple(computer, app);
|
LoaderTuple tuple = new LoaderTuple(computer, app);
|
||||||
|
|
||||||
// If there's already a task in progress for this view,
|
// If there's already a task in progress for this view,
|
||||||
@@ -309,22 +334,26 @@ public class CachedAppAssetLoader {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the progress bar always on initial load
|
// Always set the name text so we have it if needed later
|
||||||
prgView.setVisibility(View.INVISIBLE);
|
textView.setText(app.getAppName());
|
||||||
|
|
||||||
// First, try the memory cache in the current context
|
// First, try the memory cache in the current context
|
||||||
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
ScaledBitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
// Show the bitmap immediately
|
// Show the bitmap immediately
|
||||||
imgView.setVisibility(View.VISIBLE);
|
imgView.setVisibility(View.VISIBLE);
|
||||||
imgView.setImageBitmap(bmp);
|
imgView.setImageBitmap(bmp.bitmap);
|
||||||
|
|
||||||
|
// Show the text if it's a placeholder bitmap
|
||||||
|
textView.setVisibility(isBitmapPlaceholder(bmp) ? View.VISIBLE : View.GONE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not in memory, create an async task to load it. This task will be attached
|
// If it's not in memory, create an async task to load it. This task will be attached
|
||||||
// via AsyncDrawable to this view.
|
// via AsyncDrawable to this view.
|
||||||
final LoaderTask task = new LoaderTask(imgView, prgView, true);
|
final LoaderTask task = new LoaderTask(imgView, textView, true);
|
||||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(imgView.getResources(), placeholderBitmap, task);
|
final AsyncDrawable asyncDrawable = new AsyncDrawable(imgView.getResources(), placeholderBitmap, task);
|
||||||
|
textView.setVisibility(View.INVISIBLE);
|
||||||
imgView.setVisibility(View.INVISIBLE);
|
imgView.setVisibility(View.INVISIBLE);
|
||||||
imgView.setImageDrawable(asyncDrawable);
|
imgView.setImageDrawable(asyncDrawable);
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class DiskAssetLoader {
|
|||||||
return inSampleSize;
|
return inSampleSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
public ScaledBitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
||||||
File file = getFile(tuple.computer.uuid, tuple.app.getAppId());
|
File file = getFile(tuple.computer.uuid, tuple.app.getAppId());
|
||||||
|
|
||||||
// Don't bother with anything if it doesn't exist
|
// Don't bother with anything if it doesn't exist
|
||||||
@@ -110,27 +110,33 @@ public class DiskAssetLoader {
|
|||||||
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
||||||
|
return new ScaledBitmap(decodeOnlyOptions.outWidth, decodeOnlyOptions.outHeight, bmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// On P, we can get a bitmap back in one step with ImageDecoder
|
// On P, we can get a bitmap back in one step with ImageDecoder
|
||||||
|
final ScaledBitmap scaledBitmap = new ScaledBitmap();
|
||||||
try {
|
try {
|
||||||
bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
|
scaledBitmap.bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onHeaderDecoded(ImageDecoder imageDecoder, ImageDecoder.ImageInfo imageInfo, ImageDecoder.Source source) {
|
public void onHeaderDecoded(ImageDecoder imageDecoder, ImageDecoder.ImageInfo imageInfo, ImageDecoder.Source source) {
|
||||||
|
scaledBitmap.originalWidth = imageInfo.getSize().getWidth();
|
||||||
|
scaledBitmap.originalHeight = imageInfo.getSize().getHeight();
|
||||||
|
|
||||||
imageDecoder.setTargetSize(STANDARD_ASSET_WIDTH, STANDARD_ASSET_HEIGHT);
|
imageDecoder.setTargetSize(STANDARD_ASSET_WIDTH, STANDARD_ASSET_HEIGHT);
|
||||||
if (isLowRamDevice) {
|
if (isLowRamDevice) {
|
||||||
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
|
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return scaledBitmap;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bmp;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile(String computerUuid, int appId) {
|
public File getFile(String computerUuid, int appId) {
|
||||||
|
|||||||
@@ -1,37 +1,74 @@
|
|||||||
package com.limelight.grid.assets;
|
package com.limelight.grid.assets;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.util.LruCache;
|
import android.util.LruCache;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class MemoryAssetLoader {
|
public class MemoryAssetLoader {
|
||||||
private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
private static final LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(maxMemory / 16) {
|
private static final LruCache<String, ScaledBitmap> memoryCache = new LruCache<String, ScaledBitmap>(maxMemory / 16) {
|
||||||
@Override
|
@Override
|
||||||
protected int sizeOf(String key, Bitmap bitmap) {
|
protected int sizeOf(String key, ScaledBitmap bitmap) {
|
||||||
// Sizeof returns kilobytes
|
// Sizeof returns kilobytes
|
||||||
return bitmap.getByteCount() / 1024;
|
return bitmap.bitmap.getByteCount() / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void entryRemoved(boolean evicted, String key, ScaledBitmap oldValue, ScaledBitmap newValue) {
|
||||||
|
super.entryRemoved(evicted, key, oldValue, newValue);
|
||||||
|
|
||||||
|
if (evicted) {
|
||||||
|
// Keep a soft reference around to the bitmap as long as we can
|
||||||
|
evictionCache.put(key, new SoftReference<>(oldValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
private static final HashMap<String, SoftReference<ScaledBitmap>> evictionCache = new HashMap<>();
|
||||||
|
|
||||||
private static String constructKey(CachedAppAssetLoader.LoaderTuple tuple) {
|
private static String constructKey(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||||
return tuple.computer.uuid+"-"+tuple.app.getAppId();
|
return tuple.computer.uuid+"-"+tuple.app.getAppId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
|
public ScaledBitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||||
Bitmap bmp = memoryCache.get(constructKey(tuple));
|
final String key = constructKey(tuple);
|
||||||
|
|
||||||
|
ScaledBitmap bmp = memoryCache.get(key);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
LimeLog.info("Memory cache hit for tuple: "+tuple);
|
LimeLog.info("LRU cache hit for tuple: "+tuple);
|
||||||
|
return bmp;
|
||||||
}
|
}
|
||||||
return bmp;
|
|
||||||
|
SoftReference<ScaledBitmap> bmpRef = evictionCache.get(key);
|
||||||
|
if (bmpRef != null) {
|
||||||
|
bmp = bmpRef.get();
|
||||||
|
if (bmp != null) {
|
||||||
|
LimeLog.info("Eviction cache hit for tuple: "+tuple);
|
||||||
|
|
||||||
|
// Put this entry back into the LRU cache
|
||||||
|
evictionCache.remove(key);
|
||||||
|
memoryCache.put(key, bmp);
|
||||||
|
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The data is gone, so remove the dangling SoftReference now
|
||||||
|
evictionCache.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void populateCache(CachedAppAssetLoader.LoaderTuple tuple, Bitmap bitmap) {
|
public void populateCache(CachedAppAssetLoader.LoaderTuple tuple, ScaledBitmap bitmap) {
|
||||||
memoryCache.put(constructKey(tuple), bitmap);
|
memoryCache.put(constructKey(tuple), bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearCache() {
|
public void clearCache() {
|
||||||
|
// We must evict first because that will push all items into the eviction cache
|
||||||
memoryCache.evictAll();
|
memoryCache.evictAll();
|
||||||
|
evictionCache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.limelight.grid.assets;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
public class ScaledBitmap {
|
||||||
|
public int originalWidth;
|
||||||
|
public int originalHeight;
|
||||||
|
|
||||||
|
public Bitmap bitmap;
|
||||||
|
|
||||||
|
public ScaledBitmap() {}
|
||||||
|
|
||||||
|
public ScaledBitmap(int originalWidth, int originalHeight, Bitmap bitmap) {
|
||||||
|
this.originalWidth = originalWidth;
|
||||||
|
this.originalHeight = originalHeight;
|
||||||
|
this.bitmap = bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -348,6 +348,12 @@ public class NvConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendMouseHighResScroll(final short scrollAmount) {
|
||||||
|
if (!isMonkey) {
|
||||||
|
MoonBridge.sendMouseHighResScroll(scrollAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
||||||
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ public class ComputerDetails {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append("Name: ").append(name).append("\n");
|
||||||
str.append("State: ").append(state).append("\n");
|
str.append("State: ").append(state).append("\n");
|
||||||
str.append("Active Address: ").append(activeAddress).append("\n");
|
str.append("Active Address: ").append(activeAddress).append("\n");
|
||||||
str.append("Name: ").append(name).append("\n");
|
|
||||||
str.append("UUID: ").append(uuid).append("\n");
|
str.append("UUID: ").append(uuid).append("\n");
|
||||||
str.append("Local Address: ").append(localAddress).append("\n");
|
str.append("Local Address: ").append(localAddress).append("\n");
|
||||||
str.append("Remote Address: ").append(remoteAddress).append("\n");
|
str.append("Remote Address: ").append(remoteAddress).append("\n");
|
||||||
|
|||||||
@@ -58,4 +58,13 @@ public class NvApp {
|
|||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return this.initialized;
|
return this.initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append("Name: ").append(appName).append("\n");
|
||||||
|
str.append("HDR: ").append(hdrSupported ? "Yes" : "No").append("\n");
|
||||||
|
str.append("ID: ").append(appId).append("\n");
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,20 +186,20 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
||||||
String statusCodeText = xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_code");
|
// We use Long.parseLong() because in rare cases GFE can send back a status code of
|
||||||
if (statusCodeText == null) {
|
// 0xFFFFFFFF, which will cause Integer.parseInt() to throw a NumberFormatException due
|
||||||
throw new GfeHttpResponseException(418, "Status code is missing");
|
// to exceeding Integer.MAX_VALUE. We'll get the desired error code of -1 by just casting
|
||||||
}
|
// the resulting long into an int.
|
||||||
try {
|
int statusCode = (int)Long.parseLong(xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_code"));
|
||||||
int statusCode = Integer.parseInt(statusCodeText);
|
if (statusCode != 200) {
|
||||||
if (statusCode != 200) {
|
String statusMsg = xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_message");
|
||||||
throw new GfeHttpResponseException(statusCode, xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_message"));
|
if (statusCode == -1 && "Invalid".equals(statusMsg)) {
|
||||||
|
// Special case handling an audio capture error which GFE doesn't
|
||||||
|
// provide any useful status message for.
|
||||||
|
statusCode = 418;
|
||||||
|
statusMsg = "Missing audio capture device. Reinstall GeForce Experience.";
|
||||||
}
|
}
|
||||||
}
|
throw new GfeHttpResponseException(statusCode, statusMsg);
|
||||||
catch (NumberFormatException e) {
|
|
||||||
// It seems like GFE 3.20.3.63 is returning garbage for status_code in rare cases.
|
|
||||||
// Surface this in a more friendly way rather than crashing.
|
|
||||||
throw new GfeHttpResponseException(418, "Status code is not a number: "+statusCodeText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,6 +632,12 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean launchApp(ConnectionContext context, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
|
public boolean launchApp(ConnectionContext context, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
|
||||||
|
// Using an FPS value over 60 causes SOPS to default to 720p60,
|
||||||
|
// so force it to 0 to ensure the correct resolution is set. We
|
||||||
|
// used to use 60 here but that locked the frame rate to 60 FPS
|
||||||
|
// on GFE 3.20.3.
|
||||||
|
int fps = context.streamConfig.getLaunchRefreshRate() > 60 ? 0 : context.streamConfig.getLaunchRefreshRate();
|
||||||
|
|
||||||
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
|
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
|
||||||
// GFE to force SOPS to 720p60. This is fine for < 720p resolutions like
|
// GFE to force SOPS to 720p60. This is fine for < 720p resolutions like
|
||||||
// 360p or 480p, but it is not ideal for 1440p and other resolutions.
|
// 360p or 480p, but it is not ideal for 1440p and other resolutions.
|
||||||
@@ -645,20 +651,10 @@ public class NvHTTP {
|
|||||||
enableSops = false;
|
enableSops = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using SOPS with FPS values over 60 causes GFE to fall back
|
|
||||||
// to 720p60. On previous GFE versions, we could avoid this by
|
|
||||||
// forcing the FPS value to 60 when launching the stream, but
|
|
||||||
// now on GFE 3.20.3 that seems to trigger some sort of
|
|
||||||
// frame rate limiter that locks the game to 60 FPS.
|
|
||||||
if (context.streamConfig.getLaunchRefreshRate() > 60) {
|
|
||||||
LimeLog.info("Disabling SOPS due to high frame rate: "+context.streamConfig.getLaunchRefreshRate());
|
|
||||||
enableSops = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String xmlStr = openHttpConnectionToString(baseUrlHttps +
|
String xmlStr = openHttpConnectionToString(baseUrlHttps +
|
||||||
"/launch?" + buildUniqueIdUuidString() +
|
"/launch?" + buildUniqueIdUuidString() +
|
||||||
"&appid=" + appId +
|
"&appid=" + appId +
|
||||||
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + context.streamConfig.getLaunchRefreshRate() +
|
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
||||||
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
||||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||||
"&rikeyid="+context.riKeyId +
|
"&rikeyid="+context.riKeyId +
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ public class MoonBridge {
|
|||||||
public static final int CONN_STATUS_OKAY = 0;
|
public static final int CONN_STATUS_OKAY = 0;
|
||||||
public static final int CONN_STATUS_POOR = 1;
|
public static final int CONN_STATUS_POOR = 1;
|
||||||
|
|
||||||
|
public static final int ML_ERROR_GRACEFUL_TERMINATION = 0;
|
||||||
|
public static final int ML_ERROR_NO_VIDEO_TRAFFIC = -100;
|
||||||
|
|
||||||
private static AudioRenderer audioRenderer;
|
private static AudioRenderer audioRenderer;
|
||||||
private static VideoDecoderRenderer videoRenderer;
|
private static VideoDecoderRenderer videoRenderer;
|
||||||
private static NvConnectionListener connectionListener;
|
private static NvConnectionListener connectionListener;
|
||||||
@@ -258,6 +261,8 @@ public class MoonBridge {
|
|||||||
|
|
||||||
public static native void sendMouseScroll(byte scrollClicks);
|
public static native void sendMouseScroll(byte scrollClicks);
|
||||||
|
|
||||||
|
public static native void sendMouseHighResScroll(short scrollAmount);
|
||||||
|
|
||||||
public static native String getStageName(int stage);
|
public static native String getStageName(int stage);
|
||||||
|
|
||||||
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public class PreferenceConfiguration {
|
|||||||
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
|
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
|
||||||
private static final String OSC_OPACITY_PREF_STRING = "seekbar_osc_opacity";
|
private static final String OSC_OPACITY_PREF_STRING = "seekbar_osc_opacity";
|
||||||
private static final String LANGUAGE_PREF_STRING = "list_languages";
|
private static final String LANGUAGE_PREF_STRING = "list_languages";
|
||||||
private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode";
|
|
||||||
private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode";
|
private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode";
|
||||||
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
||||||
static final String AUDIO_CONFIG_PREF_STRING = "list_audio_config";
|
static final String AUDIO_CONFIG_PREF_STRING = "list_audio_config";
|
||||||
@@ -41,8 +40,11 @@ public class PreferenceConfiguration {
|
|||||||
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
|
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
|
||||||
private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
|
private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
|
||||||
private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
|
private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
|
||||||
|
private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
|
||||||
|
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
|
||||||
|
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
|
||||||
|
|
||||||
static final String DEFAULT_RESOLUTION = "720p";
|
static final String DEFAULT_RESOLUTION = "1280x720";
|
||||||
static final String DEFAULT_FPS = "60";
|
static final String DEFAULT_FPS = "60";
|
||||||
private static final boolean DEFAULT_STRETCH = false;
|
private static final boolean DEFAULT_STRETCH = false;
|
||||||
private static final boolean DEFAULT_SOPS = true;
|
private static final boolean DEFAULT_SOPS = true;
|
||||||
@@ -51,7 +53,6 @@ public class PreferenceConfiguration {
|
|||||||
private static final int DEFAULT_DEADZONE = 15;
|
private static final int DEFAULT_DEADZONE = 15;
|
||||||
private static final int DEFAULT_OPACITY = 90;
|
private static final int DEFAULT_OPACITY = 90;
|
||||||
public static final String DEFAULT_LANGUAGE = "default";
|
public static final String DEFAULT_LANGUAGE = "default";
|
||||||
private static final boolean DEFAULT_LIST_MODE = false;
|
|
||||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||||
private static final boolean DEFAULT_USB_DRIVER = true;
|
private static final boolean DEFAULT_USB_DRIVER = true;
|
||||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||||
@@ -67,12 +68,22 @@ public class PreferenceConfiguration {
|
|||||||
private static final boolean DEFAULT_UNLOCK_FPS = false;
|
private static final boolean DEFAULT_UNLOCK_FPS = false;
|
||||||
private static final boolean DEFAULT_VIBRATE_OSC = true;
|
private static final boolean DEFAULT_VIBRATE_OSC = true;
|
||||||
private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
|
private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
|
||||||
|
private static final boolean DEFAULT_FLIP_FACE_BUTTONS = false;
|
||||||
|
private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
|
||||||
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
||||||
|
private static final boolean DEFAULT_LATENCY_TOAST = false;
|
||||||
|
|
||||||
public static final int FORCE_H265_ON = -1;
|
public static final int FORCE_H265_ON = -1;
|
||||||
public static final int AUTOSELECT_H265 = 0;
|
public static final int AUTOSELECT_H265 = 0;
|
||||||
public static final int FORCE_H265_OFF = 1;
|
public static final int FORCE_H265_OFF = 1;
|
||||||
|
|
||||||
|
public static final String RES_360P = "640x360";
|
||||||
|
public static final String RES_480P = "854x480";
|
||||||
|
public static final String RES_720P = "1280x720";
|
||||||
|
public static final String RES_1080P = "1920x1080";
|
||||||
|
public static final String RES_1440P = "2560x1440";
|
||||||
|
public static final String RES_4K = "3840x2160";
|
||||||
|
|
||||||
public int width, height, fps;
|
public int width, height, fps;
|
||||||
public int bitrate;
|
public int bitrate;
|
||||||
public int videoFormat;
|
public int videoFormat;
|
||||||
@@ -80,73 +91,71 @@ public class PreferenceConfiguration {
|
|||||||
public int oscOpacity;
|
public int oscOpacity;
|
||||||
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
||||||
public String language;
|
public String language;
|
||||||
public boolean listMode, smallIconMode, multiController, usbDriver;
|
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
|
||||||
public boolean onscreenController;
|
public boolean onscreenController;
|
||||||
public boolean onlyL3R3;
|
public boolean onlyL3R3;
|
||||||
public boolean disableFrameDrop;
|
public boolean disableFrameDrop;
|
||||||
public boolean enableHdr;
|
public boolean enableHdr;
|
||||||
public boolean enablePip;
|
public boolean enablePip;
|
||||||
public boolean enablePerfOverlay;
|
public boolean enablePerfOverlay;
|
||||||
|
public boolean enableLatencyToast;
|
||||||
public boolean bindAllUsb;
|
public boolean bindAllUsb;
|
||||||
public boolean mouseEmulation;
|
public boolean mouseEmulation;
|
||||||
public boolean mouseNavButtons;
|
public boolean mouseNavButtons;
|
||||||
public boolean unlockFps;
|
public boolean unlockFps;
|
||||||
public boolean vibrateOsc;
|
public boolean vibrateOsc;
|
||||||
public boolean vibrateFallbackToDevice;
|
public boolean vibrateFallbackToDevice;
|
||||||
|
public boolean touchscreenTrackpad;
|
||||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||||
|
|
||||||
private static int getHeightFromResolutionString(String resString) {
|
private static String convertFromLegacyResolutionString(String resString) {
|
||||||
if (resString.equalsIgnoreCase("360p")) {
|
if (resString.equalsIgnoreCase("360p")) {
|
||||||
return 360;
|
return RES_360P;
|
||||||
}
|
}
|
||||||
else if (resString.equalsIgnoreCase("480p")) {
|
else if (resString.equalsIgnoreCase("480p")) {
|
||||||
return 480;
|
return RES_480P;
|
||||||
}
|
}
|
||||||
else if (resString.equalsIgnoreCase("720p")) {
|
else if (resString.equalsIgnoreCase("720p")) {
|
||||||
return 720;
|
return RES_720P;
|
||||||
}
|
}
|
||||||
else if (resString.equalsIgnoreCase("1080p")) {
|
else if (resString.equalsIgnoreCase("1080p")) {
|
||||||
return 1080;
|
return RES_1080P;
|
||||||
}
|
}
|
||||||
else if (resString.equalsIgnoreCase("1440p")) {
|
else if (resString.equalsIgnoreCase("1440p")) {
|
||||||
return 1440;
|
return RES_1440P;
|
||||||
}
|
}
|
||||||
else if (resString.equalsIgnoreCase("4K")) {
|
else if (resString.equalsIgnoreCase("4K")) {
|
||||||
return 2160;
|
return RES_4K;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Should be unreachable
|
// Should be unreachable
|
||||||
return 720;
|
return RES_720P;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getWidthFromResolutionString(String resString) {
|
private static int getWidthFromResolutionString(String resString) {
|
||||||
int height = getHeightFromResolutionString(resString);
|
return Integer.parseInt(resString.split("x")[0]);
|
||||||
if (height == 480) {
|
}
|
||||||
// This isn't an exact 16:9 resolution
|
|
||||||
return 854;
|
private static int getHeightFromResolutionString(String resString) {
|
||||||
}
|
return Integer.parseInt(resString.split("x")[1]);
|
||||||
else {
|
|
||||||
return (height * 16) / 9;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getResolutionString(int width, int height) {
|
private static String getResolutionString(int width, int height) {
|
||||||
switch (height) {
|
switch (height) {
|
||||||
case 360:
|
case 360:
|
||||||
return "360p";
|
return RES_360P;
|
||||||
case 480:
|
case 480:
|
||||||
return "480p";
|
return RES_480P;
|
||||||
default:
|
default:
|
||||||
case 720:
|
case 720:
|
||||||
return "720p";
|
return RES_720P;
|
||||||
case 1080:
|
case 1080:
|
||||||
return "1080p";
|
return RES_1080P;
|
||||||
case 1440:
|
case 1440:
|
||||||
return "1440p";
|
return RES_1440P;
|
||||||
case 2160:
|
case 2160:
|
||||||
return "4K";
|
return RES_4K;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,11 +326,24 @@ public class PreferenceConfiguration {
|
|||||||
else {
|
else {
|
||||||
// Use the new preference location
|
// Use the new preference location
|
||||||
String resStr = prefs.getString(RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION);
|
String resStr = prefs.getString(RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION);
|
||||||
|
|
||||||
|
// Convert legacy resolution strings to the new style
|
||||||
|
if (!resStr.contains("x")) {
|
||||||
|
resStr = PreferenceConfiguration.convertFromLegacyResolutionString(resStr);
|
||||||
|
prefs.edit().putString(RESOLUTION_PREF_STRING, resStr).apply();
|
||||||
|
}
|
||||||
|
|
||||||
config.width = PreferenceConfiguration.getWidthFromResolutionString(resStr);
|
config.width = PreferenceConfiguration.getWidthFromResolutionString(resStr);
|
||||||
config.height = PreferenceConfiguration.getHeightFromResolutionString(resStr);
|
config.height = PreferenceConfiguration.getHeightFromResolutionString(resStr);
|
||||||
config.fps = Integer.parseInt(prefs.getString(FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS));
|
config.fps = Integer.parseInt(prefs.getString(FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!prefs.contains(SMALL_ICONS_PREF_STRING)) {
|
||||||
|
// We need to write small icon mode's default to disk for the settings page to display
|
||||||
|
// the current state of the option properly
|
||||||
|
prefs.edit().putBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context)).apply();
|
||||||
|
}
|
||||||
|
|
||||||
// This must happen after the preferences migration to ensure the preferences are populated
|
// This must happen after the preferences migration to ensure the preferences are populated
|
||||||
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, prefs.getInt(BITRATE_PREF_OLD_STRING, 0) * 1000);
|
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, prefs.getInt(BITRATE_PREF_OLD_STRING, 0) * 1000);
|
||||||
if (config.bitrate == 0) {
|
if (config.bitrate == 0) {
|
||||||
@@ -352,7 +374,6 @@ public class PreferenceConfiguration {
|
|||||||
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
|
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
|
||||||
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
|
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
|
||||||
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);
|
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);
|
||||||
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
|
|
||||||
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
|
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
|
||||||
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
||||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||||
@@ -368,6 +389,9 @@ public class PreferenceConfiguration {
|
|||||||
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
|
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
|
||||||
config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
|
config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
|
||||||
config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
|
config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
|
||||||
|
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
|
||||||
|
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
|
||||||
|
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.limelight.preferences;
|
package com.limelight.preferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
@@ -7,6 +8,7 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Vibrator;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
||||||
@@ -54,9 +56,7 @@ public class StreamSettings extends Activity {
|
|||||||
|
|
||||||
// Check for changes that require a UI reload to take effect
|
// Check for changes that require a UI reload to take effect
|
||||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||||
if (newPrefs.listMode != previousPrefs.listMode ||
|
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||||
newPrefs.smallIconMode != previousPrefs.smallIconMode ||
|
|
||||||
!newPrefs.language.equals(previousPrefs.language)) {
|
|
||||||
// Restart the PC view to apply UI changes
|
// Restart the PC view to apply UI changes
|
||||||
Intent intent = new Intent(this, PcView.class);
|
Intent intent = new Intent(this, PcView.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
@@ -141,18 +141,40 @@ public class StreamSettings extends Activity {
|
|||||||
// hide on-screen controls category on non touch screen devices
|
// hide on-screen controls category on non touch screen devices
|
||||||
if (!getActivity().getPackageManager().
|
if (!getActivity().getPackageManager().
|
||||||
hasSystemFeature("android.hardware.touchscreen")) {
|
hasSystemFeature("android.hardware.touchscreen")) {
|
||||||
PreferenceCategory category =
|
{
|
||||||
(PreferenceCategory) findPreference("category_onscreen_controls");
|
PreferenceCategory category =
|
||||||
screen.removePreference(category);
|
(PreferenceCategory) findPreference("category_onscreen_controls");
|
||||||
|
screen.removePreference(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_input_settings");
|
||||||
|
category.removePreference(findPreference("checkbox_touchscreen_trackpad"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove PiP mode on devices pre-Oreo
|
// Remove PiP mode on devices pre-Oreo or where the feature is not available (some low RAM devices)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||||
|
!getActivity().getPackageManager().hasSystemFeature("android.software.picture_in_picture")) {
|
||||||
PreferenceCategory category =
|
PreferenceCategory category =
|
||||||
(PreferenceCategory) findPreference("category_basic_settings");
|
(PreferenceCategory) findPreference("category_ui_settings");
|
||||||
category.removePreference(findPreference("checkbox_enable_pip"));
|
category.removePreference(findPreference("checkbox_enable_pip"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the vibration options if the device can't vibrate
|
||||||
|
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_input_settings");
|
||||||
|
category.removePreference(findPreference("checkbox_vibrate_fallback"));
|
||||||
|
|
||||||
|
// The entire OSC category may have already been removed by the touchscreen check above
|
||||||
|
category = (PreferenceCategory) findPreference("category_onscreen_controls");
|
||||||
|
if (category != null) {
|
||||||
|
category.removePreference(findPreference("checkbox_vibrate_osc"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int maxSupportedFps = 0;
|
int maxSupportedFps = 0;
|
||||||
|
|
||||||
// Hide non-supported resolution/FPS combinations
|
// Hide non-supported resolution/FPS combinations
|
||||||
@@ -241,33 +263,33 @@ public class StreamSettings extends Activity {
|
|||||||
if (maxSupportedResW != 0) {
|
if (maxSupportedResW != 0) {
|
||||||
if (maxSupportedResW < 3840) {
|
if (maxSupportedResW < 3840) {
|
||||||
// 4K is unsupported
|
// 4K is unsupported
|
||||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "4K", new Runnable() {
|
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_4K, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
||||||
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p");
|
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P);
|
||||||
resetBitrateToDefault(prefs, null, null);
|
resetBitrateToDefault(prefs, null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (maxSupportedResW < 2560) {
|
if (maxSupportedResW < 2560) {
|
||||||
// 1440p is unsupported
|
// 1440p is unsupported
|
||||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p", new Runnable() {
|
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
||||||
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p");
|
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P);
|
||||||
resetBitrateToDefault(prefs, null, null);
|
resetBitrateToDefault(prefs, null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (maxSupportedResW < 1920) {
|
if (maxSupportedResW < 1920) {
|
||||||
// 1080p is unsupported
|
// 1080p is unsupported
|
||||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p", new Runnable() {
|
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
||||||
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "720p");
|
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_720P);
|
||||||
resetBitrateToDefault(prefs, null, null);
|
resetBitrateToDefault(prefs, null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -319,7 +341,7 @@ public class StreamSettings extends Activity {
|
|||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
LimeLog.info("Excluding unlock FPS toggle based on OS");
|
LimeLog.info("Excluding unlock FPS toggle based on OS");
|
||||||
PreferenceCategory category =
|
PreferenceCategory category =
|
||||||
(PreferenceCategory) findPreference("category_basic_settings");
|
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||||
category.removePreference(findPreference("checkbox_unlock_fps"));
|
category.removePreference(findPreference("checkbox_unlock_fps"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -3,41 +3,22 @@ package com.limelight.utils;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import com.limelight.HelpActivity;
|
import com.limelight.HelpActivity;
|
||||||
|
|
||||||
public class HelpLauncher {
|
public class HelpLauncher {
|
||||||
|
|
||||||
private static boolean isKnownBrowser(Context context, Intent i) {
|
|
||||||
ResolveInfo resolvedActivity = context.getPackageManager().resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY);
|
|
||||||
if (resolvedActivity == null) {
|
|
||||||
// No browser
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name = resolvedActivity.activityInfo.name;
|
|
||||||
if (name == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = name.toLowerCase();
|
|
||||||
return name.contains("chrome") || name.contains("firefox");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void launchUrl(Context context, String url) {
|
private static void launchUrl(Context context, String url) {
|
||||||
// Try to launch the default browser
|
// Try to launch the default browser
|
||||||
try {
|
try {
|
||||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||||
i.setData(Uri.parse(url));
|
i.setData(Uri.parse(url));
|
||||||
|
|
||||||
// Several Android TV devices will lie and say they do have a browser
|
// Several Android TV devices will lie and say they do have a browser even though the OS
|
||||||
// even though the OS just shows an error dialog if we try to use it. We need to
|
// just shows an error dialog if we try to use it. We used to try to be clever and check
|
||||||
// be a bit more clever on these devices and detect if the browser is a legitimate
|
// the package name of the resolved intent, but it's not worth it anymore with Android 11's
|
||||||
// browser or just a fake error message activity.
|
// package visibility changes. We'll just always use the WebView on Android TV.
|
||||||
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
|
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||||
isKnownBrowser(context, i)) {
|
|
||||||
context.startActivity(i);
|
context.startActivity(i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: 247b1fe0e3...407d57a50a
@@ -49,6 +49,11 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMouseScroll(JNIEnv *env, jclass c
|
|||||||
LiSendScrollEvent(scrollClicks);
|
LiSendScrollEvent(scrollClicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseHighResScroll(JNIEnv *env, jclass clazz, jshort scrollAmount) {
|
||||||
|
LiSendHighResScrollEvent(scrollAmount);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_limelight_nvstream_jni_MoonBridge_stopConnection(JNIEnv *env, jclass clazz) {
|
Java_com_limelight_nvstream_jni_MoonBridge_stopConnection(JNIEnv *env, jclass clazz) {
|
||||||
LiStopConnection();
|
LiStopConnection();
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<alpha
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0"
|
||||||
|
android:interpolator="@android:anim/accelerate_interpolator"
|
||||||
|
android:duration="100"/>
|
||||||
|
</set>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<alpha
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0"
|
||||||
|
android:interpolator="@android:anim/accelerate_interpolator"
|
||||||
|
android:duration="100"/>
|
||||||
|
</set>
|
||||||
@@ -1,49 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="150dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="200dp"
|
||||||
android:padding="20dp">
|
android:padding="10dp">
|
||||||
<RelativeLayout
|
<ImageView
|
||||||
android:id="@+id/grid_image_layout"
|
android:id="@+id/grid_image"
|
||||||
|
android:cropToPadding="false"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
</ImageView>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/grid_overlay"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_width="wrap_content"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_width="50dp"
|
||||||
<ImageView
|
android:layout_height="50dp">
|
||||||
android:id="@+id/grid_image"
|
</ImageView>
|
||||||
android:cropToPadding="false"
|
|
||||||
android:scaleType="fitXY"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_width="150dp"
|
|
||||||
android:layout_height="175dp">
|
|
||||||
</ImageView>
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/grid_spinner"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_width="75dp"
|
|
||||||
android:layout_height="75dp"
|
|
||||||
android:indeterminate="true">
|
|
||||||
</ProgressBar>
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/grid_overlay"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="50dp">
|
|
||||||
</ImageView>
|
|
||||||
</RelativeLayout>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/grid_text"
|
android:id="@+id/grid_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/grid_image_layout"
|
android:layout_below="@id/grid_overlay"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:singleLine="true"
|
android:textSize="18sp">
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:marqueeRepeatLimit="marquee_forever"
|
|
||||||
android:scrollHorizontally="true"
|
|
||||||
android:textSize="18sp" >
|
|
||||||
</TextView>
|
</TextView>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -1,49 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="100dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="133dp"
|
||||||
android:padding="10dp">
|
android:padding="5dp">
|
||||||
<RelativeLayout
|
<ImageView
|
||||||
android:id="@+id/grid_image_layout"
|
android:id="@+id/grid_image"
|
||||||
|
android:cropToPadding="false"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
</ImageView>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/grid_overlay"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_width="wrap_content"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_width="33dp"
|
||||||
<ImageView
|
android:layout_height="33dp">
|
||||||
android:id="@+id/grid_image"
|
</ImageView>
|
||||||
android:cropToPadding="false"
|
|
||||||
android:scaleType="fitXY"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_width="100dp"
|
|
||||||
android:layout_height="117dp">
|
|
||||||
</ImageView>
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/grid_spinner"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:indeterminate="true">
|
|
||||||
</ProgressBar>
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/grid_overlay"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_width="33dp"
|
|
||||||
android:layout_height="33dp">
|
|
||||||
</ImageView>
|
|
||||||
</RelativeLayout>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/grid_text"
|
android:id="@+id/grid_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/grid_image_layout"
|
android:layout_below="@id/grid_overlay"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:singleLine="true"
|
android:textSize="14sp">
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:marqueeRepeatLimit="marquee_forever"
|
|
||||||
android:scrollHorizontally="true"
|
|
||||||
android:textSize="14sp" >
|
|
||||||
</TextView>
|
</TextView>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:numColumns="auto_fit"
|
android:numColumns="auto_fit"
|
||||||
android:columnWidth="160dp"
|
android:columnWidth="150dp"
|
||||||
android:stretchMode="spacingWidthUniform"
|
android:stretchMode="spacingWidthUniform"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:numColumns="auto_fit"
|
android:numColumns="auto_fit"
|
||||||
android:columnWidth="105dp"
|
android:columnWidth="100dp"
|
||||||
android:stretchMode="spacingWidthUniform"
|
android:stretchMode="spacingWidthUniform"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/fragmentView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fastScrollEnabled="true"
|
|
||||||
android:longClickable="false"
|
|
||||||
android:focusable="true"
|
|
||||||
android:focusableInTouchMode="true"
|
|
||||||
android:stackFromBottom="false"
|
|
||||||
android:clipToPadding="false"/>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="15dp">
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/grid_image_layout"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/grid_image"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_width="75dp"
|
|
||||||
android:layout_height="75dp">
|
|
||||||
</ImageView>
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/grid_overlay"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_width="33dp"
|
|
||||||
android:layout_height="33dp">
|
|
||||||
</ImageView>
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/grid_spinner"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_width="33dp"
|
|
||||||
android:layout_height="33dp"
|
|
||||||
android:indeterminate="true">
|
|
||||||
</ProgressBar>
|
|
||||||
</RelativeLayout>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/grid_text"
|
|
||||||
android:layout_width="75dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/grid_image_layout"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="14sp" >
|
|
||||||
</TextView>
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/fragmentView"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:numColumns="auto_fit"
|
|
||||||
android:columnWidth="105dp"
|
|
||||||
android:stretchMode="spacingWidthUniform"
|
|
||||||
android:focusable="true"
|
|
||||||
android:focusableInTouchMode="true"
|
|
||||||
android:nextFocusLeft="@id/settingsButton"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:gravity="center"/>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/grid_text"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textIsSelectable="false"
|
|
||||||
android:textSize="16sp" >
|
|
||||||
</TextView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -164,8 +164,6 @@
|
|||||||
<string name="category_ui_settings">UI Einstellungen</string>
|
<string name="category_ui_settings">UI Einstellungen</string>
|
||||||
<string name="title_language_list">Sprache</string>
|
<string name="title_language_list">Sprache</string>
|
||||||
<string name="summary_language_list">Sprache die Moonlight verwenden soll</string>
|
<string name="summary_language_list">Sprache die Moonlight verwenden soll</string>
|
||||||
<string name="title_checkbox_list_mode">Zeige Listen anstelle von Rastern.</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Zeige Apps und PCs als Liste anstelle eines Rasters</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Verwende kleine Icons</string>
|
<string name="title_checkbox_small_icon_mode">Verwende kleine Icons</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Verwende kleine Icons in der Rasteransicht, um mehr gleichzeitig anzeigen zu können</string>
|
<string name="summary_checkbox_small_icon_mode">Verwende kleine Icons in der Rasteransicht, um mehr gleichzeitig anzeigen zu können</string>
|
||||||
|
|
||||||
|
|||||||
@@ -111,8 +111,6 @@
|
|||||||
<string name="category_ui_settings">Configuración de la interfaz</string>
|
<string name="category_ui_settings">Configuración de la interfaz</string>
|
||||||
<string name="title_language_list">Idioma</string>
|
<string name="title_language_list">Idioma</string>
|
||||||
<string name="summary_language_list">Idioma a usar por Moonlight</string>
|
<string name="summary_language_list">Idioma a usar por Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Usar listas en lugar de cuadrículas</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Mostrar aplicaciones y PCs en listas en lugar de el cuadrículas</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Usar iconos pequeños</string>
|
<string name="title_checkbox_small_icon_mode">Usar iconos pequeños</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Usar iconos pequeños en las entradas de cuadrículas para permitir más entradas en la pantalla</string>
|
<string name="summary_checkbox_small_icon_mode">Usar iconos pequeños en las entradas de cuadrículas para permitir más entradas en la pantalla</string>
|
||||||
|
|
||||||
|
|||||||
@@ -171,8 +171,6 @@
|
|||||||
<string name="category_ui_settings">Paramètres de l\'interface utilisateur</string>
|
<string name="category_ui_settings">Paramètres de l\'interface utilisateur</string>
|
||||||
<string name="title_language_list">Langue</string>
|
<string name="title_language_list">Langue</string>
|
||||||
<string name="summary_language_list">Langue à utiliser pour Moonlight</string>
|
<string name="summary_language_list">Langue à utiliser pour Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Utiliser des listes au lieu des grilles</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Afficher les applications et les PC en listes au lieu de grilles</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Utiliser des petites icônes</string>
|
<string name="title_checkbox_small_icon_mode">Utiliser des petites icônes</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Utilisez les petites icônes dans les éléments de la grille pour permettre plus d\'éléments à l\'écran</string>
|
<string name="summary_checkbox_small_icon_mode">Utilisez les petites icônes dans les éléments de la grille pour permettre plus d\'éléments à l\'écran</string>
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,6 @@
|
|||||||
<string name="category_ui_settings">Impostazioni dell\'interfaccia</string>
|
<string name="category_ui_settings">Impostazioni dell\'interfaccia</string>
|
||||||
<string name="title_language_list">Lingua</string>
|
<string name="title_language_list">Lingua</string>
|
||||||
<string name="summary_language_list">Lingua da usare in Moonlight</string>
|
<string name="summary_language_list">Lingua da usare in Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Usa lista invece della griglia</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Visualizza le applicazioni e i PC in una lista invece di una griglia</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Usa icone piccole</string>
|
<string name="title_checkbox_small_icon_mode">Usa icone piccole</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Usa icone piccole nella griglia per avere più oggetti a schermo</string>
|
<string name="summary_checkbox_small_icon_mode">Usa icone piccole nella griglia per avere più oggetti a schermo</string>
|
||||||
|
|
||||||
|
|||||||
@@ -105,8 +105,6 @@
|
|||||||
<string name="category_ui_settings">インターフェース</string>
|
<string name="category_ui_settings">インターフェース</string>
|
||||||
<string name="title_language_list">言語</string>
|
<string name="title_language_list">言語</string>
|
||||||
<string name="summary_language_list">Moonlightで使用する言語</string>
|
<string name="summary_language_list">Moonlightで使用する言語</string>
|
||||||
<string name="title_checkbox_list_mode">リストメニュー</string>
|
|
||||||
<string name="summary_checkbox_list_mode">ゲームをグリッドではなく、リストで表示します</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">小さなアイコン</string>
|
<string name="title_checkbox_small_icon_mode">小さなアイコン</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">グリッドで小さなアイコンを使用します</string>
|
<string name="summary_checkbox_small_icon_mode">グリッドで小さなアイコンを使用します</string>
|
||||||
|
|
||||||
|
|||||||
@@ -120,8 +120,6 @@
|
|||||||
<string name="category_ui_settings">UI 설정</string>
|
<string name="category_ui_settings">UI 설정</string>
|
||||||
<string name="title_language_list">언어</string>
|
<string name="title_language_list">언어</string>
|
||||||
<string name="summary_language_list">Moonlight에서 사용할 언어를 선택합니다.</string>
|
<string name="summary_language_list">Moonlight에서 사용할 언어를 선택합니다.</string>
|
||||||
<string name="title_checkbox_list_mode">그리드(바둑판) 대신 리스트 뷰 사용</string>
|
|
||||||
<string name="summary_checkbox_list_mode">그리드 대신 리스트로 앱과 PC를 표시합니다.</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">작은 아이콘 사용</string>
|
<string name="title_checkbox_small_icon_mode">작은 아이콘 사용</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">더 많이 표시하기 위해 그리드 표시에서 작은 아이콘을 사용합니다.</string>
|
<string name="summary_checkbox_small_icon_mode">더 많이 표시하기 위해 그리드 표시에서 작은 아이콘을 사용합니다.</string>
|
||||||
|
|
||||||
|
|||||||
@@ -109,8 +109,6 @@
|
|||||||
<string name="category_ui_settings">UI Installingen</string>
|
<string name="category_ui_settings">UI Installingen</string>
|
||||||
<string name="title_language_list">Taal</string>
|
<string name="title_language_list">Taal</string>
|
||||||
<string name="summary_language_list">Taal te gebruiken in Moonlight</string>
|
<string name="summary_language_list">Taal te gebruiken in Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Gebruik lijsten in plaats van kolommen</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Gebruik kleine iconen</string>
|
<string name="title_checkbox_small_icon_mode">Gebruik kleine iconen</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Gebruik kleine iconen in kolom onderdelen zodat meer items tegelijk zichtbaar worden.</string>
|
<string name="summary_checkbox_small_icon_mode">Gebruik kleine iconen in kolom onderdelen zodat meer items tegelijk zichtbaar worden.</string>
|
||||||
|
|
||||||
|
|||||||
@@ -169,8 +169,6 @@
|
|||||||
<string name="category_ui_settings">Setari UI</string>
|
<string name="category_ui_settings">Setari UI</string>
|
||||||
<string name="title_language_list">Limba (Language)</string>
|
<string name="title_language_list">Limba (Language)</string>
|
||||||
<string name="summary_language_list">Limba folosită de către Moonlight</string>
|
<string name="summary_language_list">Limba folosită de către Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Folosește liste în loc de grile</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Aplicațiile și PC-urile vor fi afișate in liste in loc de grile</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Folosește iconițe mici</string>
|
<string name="title_checkbox_small_icon_mode">Folosește iconițe mici</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Iconițele folosite în grile vor fi mici pentru a încăpea mai multe odata</string>
|
<string name="summary_checkbox_small_icon_mode">Iconițele folosite în grile vor fi mici pentru a încăpea mai multe odata</string>
|
||||||
|
|
||||||
|
|||||||
@@ -107,8 +107,6 @@
|
|||||||
<string name="category_ui_settings">Настройки Интерфейса</string>
|
<string name="category_ui_settings">Настройки Интерфейса</string>
|
||||||
<string name="title_language_list">Язык</string>
|
<string name="title_language_list">Язык</string>
|
||||||
<string name="summary_language_list">Язык, который будет использоваться в Moonlight</string>
|
<string name="summary_language_list">Язык, который будет использоваться в Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Использовать списки вместо сеток</string>
|
|
||||||
<string name="summary_checkbox_list_mode">Выводить приложения и компьютеры списком, вместо использования сетки</string>
|
|
||||||
<string name="title_checkbox_small_icon_mode">Использовать маленькие иконки</string>
|
<string name="title_checkbox_small_icon_mode">Использовать маленькие иконки</string>
|
||||||
<string name="summary_checkbox_small_icon_mode">Использовать маленькие иконки в сетке для отображения большего числа элементов на экране</string>
|
<string name="summary_checkbox_small_icon_mode">Использовать маленькие иконки в сетке для отображения большего числа элементов на экране</string>
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,9 @@
|
|||||||
<string name="title_audio_config_list"> 环绕声设置 </string>
|
<string name="title_audio_config_list"> 环绕声设置 </string>
|
||||||
<string name="summary_audio_config_list"> 为家庭影院系统启用5.1或7.1环绕声 </string>
|
<string name="summary_audio_config_list"> 为家庭影院系统启用5.1或7.1环绕声 </string>
|
||||||
|
|
||||||
<string name="category_input_settings">输入设置</string>
|
<string name="category_input_settings">输入设置</string>
|
||||||
|
<string name="title_checkbox_touchscreen_trackpad">将触控屏作为触控板使用</string>
|
||||||
|
<string name="summary_checkbox_touchscreen_trackpad">如果启用,则将触控屏作为触控板使用。 如果禁止,则触控屏直接控制鼠标光标</string>
|
||||||
<string name="title_checkbox_multi_controller"> 自动检测手柄 </string>
|
<string name="title_checkbox_multi_controller"> 自动检测手柄 </string>
|
||||||
<string name="summary_checkbox_multi_controller"> 禁用此项所有手柄将视为一个手柄 </string>
|
<string name="summary_checkbox_multi_controller"> 禁用此项所有手柄将视为一个手柄 </string>
|
||||||
<string name="title_checkbox_vibrate_fallback"> 用设备震动模拟游戏震动效果 </string>
|
<string name="title_checkbox_vibrate_fallback"> 用设备震动模拟游戏震动效果 </string>
|
||||||
@@ -151,7 +153,9 @@
|
|||||||
<string name="title_checkbox_mouse_emulation"> 通过手柄模拟鼠标 </string>
|
<string name="title_checkbox_mouse_emulation"> 通过手柄模拟鼠标 </string>
|
||||||
<string name="summary_checkbox_mouse_emulation"> 长按开始键将手柄切换为鼠标模式 </string>
|
<string name="summary_checkbox_mouse_emulation"> 长按开始键将手柄切换为鼠标模式 </string>
|
||||||
<string name="title_checkbox_mouse_nav_buttons"> 启用前进后退鼠标键 </string>
|
<string name="title_checkbox_mouse_nav_buttons"> 启用前进后退鼠标键 </string>
|
||||||
<string name="summary_checkbox_mouse_nav_buttons"> 在一些支持不佳的设备上启用此项可能会使其右键失效 </string>
|
<string name="summary_checkbox_mouse_nav_buttons"> 在一些支持不佳的设备上启用此项可能会使其右键失效 </string>
|
||||||
|
<string name="title_checkbox_flip_face_buttons"> 反转技能键 </string>
|
||||||
|
<string name="summary_checkbox_flip_face_buttons"> 为手柄和虚拟手柄调转A/B和X/Y技能键 </string>
|
||||||
|
|
||||||
<string name="category_on_screen_controls_settings"> 屏幕控制按钮设置 </string>
|
<string name="category_on_screen_controls_settings"> 屏幕控制按钮设置 </string>
|
||||||
<string name="title_checkbox_show_onscreen_controls"> 显示屏幕控制按钮 </string>
|
<string name="title_checkbox_show_onscreen_controls"> 显示屏幕控制按钮 </string>
|
||||||
@@ -169,8 +173,6 @@
|
|||||||
<string name="category_ui_settings"> 界面设置 </string>
|
<string name="category_ui_settings"> 界面设置 </string>
|
||||||
<string name="title_language_list"> 语言 </string>
|
<string name="title_language_list"> 语言 </string>
|
||||||
<string name="summary_language_list"> 选择Moonlight显示的语言 </string>
|
<string name="summary_language_list"> 选择Moonlight显示的语言 </string>
|
||||||
<string name="title_checkbox_list_mode"> 使用列表代替图标 </string>
|
|
||||||
<string name="summary_checkbox_list_mode"> 列表显示电脑和游戏 </string>
|
|
||||||
<string name="title_checkbox_small_icon_mode"> 使用小图标 </string>
|
<string name="title_checkbox_small_icon_mode"> 使用小图标 </string>
|
||||||
<string name="summary_checkbox_small_icon_mode"> 使用小图标以在屏幕上显示更多项目 </string>
|
<string name="summary_checkbox_small_icon_mode"> 使用小图标以在屏幕上显示更多项目 </string>
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,9 @@
|
|||||||
<string name="title_audio_config_list"> 環繞聲設置 </string>
|
<string name="title_audio_config_list"> 環繞聲設置 </string>
|
||||||
<string name="summary_audio_config_list"> 為家庭劇院系統啟用5.1或7.1環繞聲 </string>
|
<string name="summary_audio_config_list"> 為家庭劇院系統啟用5.1或7.1環繞聲 </string>
|
||||||
|
|
||||||
<string name="category_input_settings">輸入設置</string>
|
<string name="category_input_settings"> 輸入設置 </string>
|
||||||
|
<string name="title_checkbox_touchscreen_trackpad"> 將觸控屏作為觸控板使用 </string>
|
||||||
|
<string name="summary_checkbox_touchscreen_trackpad"> 如果啟用,則將觸控屏作為觸控板使用。 如果禁止,則觸控屏直接控制滑鼠游標 </string>
|
||||||
<string name="title_checkbox_multi_controller"> 自動檢測手柄 </string>
|
<string name="title_checkbox_multi_controller"> 自動檢測手柄 </string>
|
||||||
<string name="summary_checkbox_multi_controller"> 禁用此項所有手柄將視為一個手柄 </string>
|
<string name="summary_checkbox_multi_controller"> 禁用此項所有手柄將視為一個手柄 </string>
|
||||||
<string name="title_checkbox_vibrate_fallback"> 用設備震動類比遊戲震動效果 </string>
|
<string name="title_checkbox_vibrate_fallback"> 用設備震動類比遊戲震動效果 </string>
|
||||||
@@ -151,7 +153,9 @@
|
|||||||
<string name="title_checkbox_mouse_emulation"> 通過手柄類比滑鼠 </string>
|
<string name="title_checkbox_mouse_emulation"> 通過手柄類比滑鼠 </string>
|
||||||
<string name="summary_checkbox_mouse_emulation"> 長按開始鍵將手柄切換為滑鼠模式 </string>
|
<string name="summary_checkbox_mouse_emulation"> 長按開始鍵將手柄切換為滑鼠模式 </string>
|
||||||
<string name="title_checkbox_mouse_nav_buttons"> 啟用前進後退滑鼠鍵 </string>
|
<string name="title_checkbox_mouse_nav_buttons"> 啟用前進後退滑鼠鍵 </string>
|
||||||
<string name="summary_checkbox_mouse_nav_buttons"> 在一些支援不佳的設備上啟用此項可能會使其右鍵失效 </string>
|
<string name="summary_checkbox_mouse_nav_buttons"> 在一些支援不佳的設備上啟用此項可能會使其右鍵失效 </string>
|
||||||
|
<string name="title_checkbox_flip_face_buttons">反轉技能鍵</string>
|
||||||
|
<string name="summary_checkbox_flip_face_buttons">為手柄和虛擬手柄調轉A/B和X/Y技能鍵</string>
|
||||||
|
|
||||||
<string name="category_on_screen_controls_settings"> 螢幕控制按鈕設置 </string>
|
<string name="category_on_screen_controls_settings"> 螢幕控制按鈕設置 </string>
|
||||||
<string name="title_checkbox_show_onscreen_controls"> 顯示幕幕控制按鈕 </string>
|
<string name="title_checkbox_show_onscreen_controls"> 顯示幕幕控制按鈕 </string>
|
||||||
@@ -169,8 +173,6 @@
|
|||||||
<string name="category_ui_settings"> 介面設置 </string>
|
<string name="category_ui_settings"> 介面設置 </string>
|
||||||
<string name="title_language_list"> 語言 </string>
|
<string name="title_language_list"> 語言 </string>
|
||||||
<string name="summary_language_list"> 選擇Moonlight顯示的語言 </string>
|
<string name="summary_language_list"> 選擇Moonlight顯示的語言 </string>
|
||||||
<string name="title_checkbox_list_mode"> 使用清單代替圖示 </string>
|
|
||||||
<string name="summary_checkbox_list_mode"> 清單顯示電腦和遊戲 </string>
|
|
||||||
<string name="title_checkbox_small_icon_mode"> 使用小圖示 </string>
|
<string name="title_checkbox_small_icon_mode"> 使用小圖示 </string>
|
||||||
<string name="summary_checkbox_small_icon_mode"> 使用小圖示以在螢幕上顯示更多專案 </string>
|
<string name="summary_checkbox_small_icon_mode"> 使用小圖示以在螢幕上顯示更多專案 </string>
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
<item>4K</item>
|
<item>4K</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="resolution_values" translatable="false">
|
<string-array name="resolution_values" translatable="false">
|
||||||
<item>360p</item>
|
<item>640x360</item>
|
||||||
<item>480p</item>
|
<item>854x480</item>
|
||||||
<item>720p</item>
|
<item>1280x720</item>
|
||||||
<item>1080p</item>
|
<item>1920x1080</item>
|
||||||
<item>1440p</item>
|
<item>2560x1440</item>
|
||||||
<item>4K</item>
|
<item>3840x2160</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="fps_names">
|
<string-array name="fps_names">
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
|
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
|
||||||
<string name="unable_to_pin_shortcut">Your current launcher does not allow for creating pinned shortcuts.</string>
|
<string name="unable_to_pin_shortcut">Your current launcher does not allow for creating pinned shortcuts.</string>
|
||||||
<string name="video_decoder_init_failed">Video decoder failed to initialize. Your device may not support the selected resolution or frame rate.</string>
|
<string name="video_decoder_init_failed">Video decoder failed to initialize. Your device may not support the selected resolution or frame rate.</string>
|
||||||
|
<string name="no_video_received_error">No video received from host. Check the host PC\'s firewall and port forwarding rules.</string>
|
||||||
|
|
||||||
<!-- Start application messages -->
|
<!-- Start application messages -->
|
||||||
<string name="conn_establishing_title">Establishing Connection</string>
|
<string name="conn_establishing_title">Establishing Connection</string>
|
||||||
@@ -126,33 +127,31 @@
|
|||||||
<string name="title_seekbar_bitrate">Video bitrate</string>
|
<string name="title_seekbar_bitrate">Video bitrate</string>
|
||||||
<string name="summary_seekbar_bitrate">Increase for better image quality. Decrease to improve performance on slower connections.</string>
|
<string name="summary_seekbar_bitrate">Increase for better image quality. Decrease to improve performance on slower connections.</string>
|
||||||
<string name="suffix_seekbar_bitrate">Kbps</string>
|
<string name="suffix_seekbar_bitrate">Kbps</string>
|
||||||
<string name="title_unlock_fps">Unlock all possible frame rates</string>
|
|
||||||
<string name="summary_unlock_fps">Streaming at 90 or 120 FPS may reduce latency on high-end devices but can cause lag or crashes on devices that can\'t support it</string>
|
|
||||||
<string name="title_checkbox_stretch_video">Stretch video to full-screen</string>
|
<string name="title_checkbox_stretch_video">Stretch video to full-screen</string>
|
||||||
<string name="title_checkbox_disable_warnings">Disable warning messages</string>
|
|
||||||
<string name="summary_checkbox_disable_warnings">Disable on-screen connection warning messages while streaming</string>
|
|
||||||
<string name="title_checkbox_enable_pip">Enable Picture-in-Picture observer mode</string>
|
|
||||||
<string name="summary_checkbox_enable_pip">Allows the stream to be viewed (but not controlled) while multitasking</string>
|
|
||||||
|
|
||||||
<string name="category_audio_settings">Audio Settings</string>
|
<string name="category_audio_settings">Audio Settings</string>
|
||||||
<string name="title_audio_config_list">Surround sound configuration</string>
|
<string name="title_audio_config_list">Surround sound configuration</string>
|
||||||
<string name="summary_audio_config_list">Enable 5.1 or 7.1 surround sound for home-theater systems</string>
|
<string name="summary_audio_config_list">Enable 5.1 or 7.1 surround sound for home-theater systems</string>
|
||||||
|
|
||||||
<string name="category_input_settings">Input Settings</string>
|
<string name="category_input_settings">Input Settings</string>
|
||||||
|
<string name="title_checkbox_touchscreen_trackpad">Use the touchscreen as a trackpad</string>
|
||||||
|
<string name="summary_checkbox_touchscreen_trackpad">If enabled, the touchscreen acts like a trackpad. If disabled, the touchscreen directly controls the mouse cursor.</string>
|
||||||
<string name="title_checkbox_multi_controller">Automatic gamepad presence detection</string>
|
<string name="title_checkbox_multi_controller">Automatic gamepad presence detection</string>
|
||||||
<string name="summary_checkbox_multi_controller">Unchecking this option forces a gamepad to always be present</string>
|
<string name="summary_checkbox_multi_controller">Unchecking this option forces a gamepad to always be present</string>
|
||||||
<string name="title_checkbox_vibrate_fallback">Emulate rumble support with vibration</string>
|
<string name="title_checkbox_vibrate_fallback">Emulate rumble support with vibration</string>
|
||||||
<string name="summary_checkbox_vibrate_fallback">Vibrates your device to emulate rumble if your gamepad does not support it</string>
|
<string name="summary_checkbox_vibrate_fallback">Vibrates your device to emulate rumble if your gamepad does not support it</string>
|
||||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||||
<string name="suffix_seekbar_deadzone">%</string>
|
<string name="suffix_seekbar_deadzone">%</string>
|
||||||
<string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</string>
|
<string name="title_checkbox_xb1_driver">Xbox 360/One USB gamepad driver</string>
|
||||||
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox controller support</string>
|
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox controller support</string>
|
||||||
<string name="title_checkbox_usb_bind_all">Override Android controller support</string>
|
<string name="title_checkbox_usb_bind_all">Override native Xbox gamepad support</string>
|
||||||
<string name="summary_checkbox_usb_bind_all">Forces Moonlight\'s USB driver to take over all supported Xbox gamepads</string>
|
<string name="summary_checkbox_usb_bind_all">Use Moonlight\'s USB driver for all supported gamepads, even if native Xbox controller support is present</string>
|
||||||
<string name="title_checkbox_mouse_emulation">Mouse emulation via gamepad</string>
|
<string name="title_checkbox_mouse_emulation">Mouse emulation via gamepad</string>
|
||||||
<string name="summary_checkbox_mouse_emulation">Long pressing the Start button will switch the gamepad into mouse mode</string>
|
<string name="summary_checkbox_mouse_emulation">Long pressing the Start button will switch the gamepad into mouse mode</string>
|
||||||
<string name="title_checkbox_mouse_nav_buttons">Enable back and forward mouse buttons</string>
|
<string name="title_checkbox_mouse_nav_buttons">Enable back and forward mouse buttons</string>
|
||||||
<string name="summary_checkbox_mouse_nav_buttons">Enabling this option may break right clicking on some buggy devices</string>
|
<string name="summary_checkbox_mouse_nav_buttons">Enabling this option may break right clicking on some buggy devices</string>
|
||||||
|
<string name="title_checkbox_flip_face_buttons">Flip face buttons</string>
|
||||||
|
<string name="summary_checkbox_flip_face_buttons">Switches the face buttons A/B and X/Y for gamepads and the on-screen controls</string>
|
||||||
|
|
||||||
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
||||||
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
||||||
@@ -172,12 +171,12 @@
|
|||||||
<string name="suffix_osc_opacity">%</string>
|
<string name="suffix_osc_opacity">%</string>
|
||||||
|
|
||||||
<string name="category_ui_settings">UI Settings</string>
|
<string name="category_ui_settings">UI Settings</string>
|
||||||
|
<string name="title_checkbox_enable_pip">Enable Picture-in-Picture observer mode</string>
|
||||||
|
<string name="summary_checkbox_enable_pip">Allows the stream to be viewed (but not controlled) while multitasking</string>
|
||||||
<string name="title_language_list">Language</string>
|
<string name="title_language_list">Language</string>
|
||||||
<string name="summary_language_list">Language to use for Moonlight</string>
|
<string name="summary_language_list">Language to use for Moonlight</string>
|
||||||
<string name="title_checkbox_list_mode">Use lists instead of grids</string>
|
<string name="title_checkbox_small_icon_mode">Use small box art</string>
|
||||||
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
|
<string name="summary_checkbox_small_icon_mode">Smaller box art in the app grid allows more apps to be visible on screen</string>
|
||||||
<string name="title_checkbox_small_icon_mode">Use small icons</string>
|
|
||||||
<string name="summary_checkbox_small_icon_mode">Use small icons in grid items to allow more items on screen</string>
|
|
||||||
|
|
||||||
<string name="category_host_settings">Host Settings</string>
|
<string name="category_host_settings">Host Settings</string>
|
||||||
<string name="title_checkbox_enable_sops">Optimize game settings</string>
|
<string name="title_checkbox_enable_sops">Optimize game settings</string>
|
||||||
@@ -186,13 +185,19 @@
|
|||||||
<string name="summary_checkbox_host_audio">Play audio from the computer and this device</string>
|
<string name="summary_checkbox_host_audio">Play audio from the computer and this device</string>
|
||||||
|
|
||||||
<string name="category_advanced_settings">Advanced Settings</string>
|
<string name="category_advanced_settings">Advanced Settings</string>
|
||||||
|
<string name="title_unlock_fps">Unlock all possible frame rates</string>
|
||||||
|
<string name="summary_unlock_fps">Streaming at 90 or 120 FPS may reduce latency on high-end devices but can cause lag or instability on devices that can\'t support it</string>
|
||||||
|
<string name="title_checkbox_disable_warnings">Disable warning messages</string>
|
||||||
|
<string name="summary_checkbox_disable_warnings">Disable on-screen connection warning messages while streaming</string>
|
||||||
<string name="title_disable_frame_drop">Never drop frames</string>
|
<string name="title_disable_frame_drop">Never drop frames</string>
|
||||||
<string name="summary_disable_frame_drop">May reduce micro-stuttering on some devices, but can increase latency</string>
|
<string name="summary_disable_frame_drop">May reduce micro-stuttering on some devices, but can increase latency</string>
|
||||||
<string name="title_video_format">Change H.265 settings</string>
|
<string name="title_video_format">Change H.265 settings</string>
|
||||||
<string name="summary_video_format">H.265 lowers video bandwidth requirements but requires a very recent device</string>
|
<string name="summary_video_format">H.265 lowers video bandwidth requirements but requires a newer device</string>
|
||||||
<string name="title_enable_hdr">Enable HDR (Experimental)</string>
|
<string name="title_enable_hdr">Enable HDR (Experimental)</string>
|
||||||
<string name="summary_enable_hdr">Stream HDR when the game and PC GPU support it. HDR requires a GTX 1000 series GPU or later.</string>
|
<string name="summary_enable_hdr">Stream HDR when the game and PC GPU support it. HDR requires a GTX 1000 series GPU or later.</string>
|
||||||
<string name="title_enable_perf_overlay">Enable performance overlay</string>
|
<string name="title_enable_perf_overlay">Show performance stats while streaming</string>
|
||||||
<string name="summary_enable_perf_overlay">Display an on-screen overlay with real-time performance information while streaming</string>
|
<string name="summary_enable_perf_overlay">Display real-time stream performance information while streaming</string>
|
||||||
|
<string name="title_enable_post_stream_toast">Show latency message after streaming</string>
|
||||||
|
<string name="summary_enable_post_stream_toast">Display a latency information message after the stream ends</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
android:summary="@string/summary_resolution_list"
|
android:summary="@string/summary_resolution_list"
|
||||||
android:entries="@array/resolution_names"
|
android:entries="@array/resolution_names"
|
||||||
android:entryValues="@array/resolution_values"
|
android:entryValues="@array/resolution_values"
|
||||||
android:defaultValue="720p" />
|
android:defaultValue="1280x720" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="list_fps"
|
android:key="list_fps"
|
||||||
android:title="@string/title_fps_list"
|
android:title="@string/title_fps_list"
|
||||||
@@ -27,20 +27,10 @@
|
|||||||
android:summary="@string/summary_seekbar_bitrate"
|
android:summary="@string/summary_seekbar_bitrate"
|
||||||
android:text="@string/suffix_seekbar_bitrate"
|
android:text="@string/suffix_seekbar_bitrate"
|
||||||
android:title="@string/title_seekbar_bitrate" />
|
android:title="@string/title_seekbar_bitrate" />
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="checkbox_unlock_fps"
|
|
||||||
android:title="@string/title_unlock_fps"
|
|
||||||
android:summary="@string/summary_unlock_fps"
|
|
||||||
android:defaultValue="false" />
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="checkbox_stretch_video"
|
android:key="checkbox_stretch_video"
|
||||||
android:title="@string/title_checkbox_stretch_video"
|
android:title="@string/title_checkbox_stretch_video"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="checkbox_enable_pip"
|
|
||||||
android:title="@string/title_checkbox_enable_pip"
|
|
||||||
android:summary="@string/summary_checkbox_enable_pip"
|
|
||||||
android:defaultValue="false" />
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_audio_settings">
|
<PreferenceCategory android:title="@string/category_audio_settings">
|
||||||
<ListPreference
|
<ListPreference
|
||||||
@@ -51,13 +41,19 @@
|
|||||||
android:entryValues="@array/audio_config_values"
|
android:entryValues="@array/audio_config_values"
|
||||||
android:defaultValue="2" />
|
android:defaultValue="2" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_input_settings">
|
<PreferenceCategory android:title="@string/category_input_settings"
|
||||||
|
android:key="category_input_settings">
|
||||||
<!--com.limelight.preferences.SeekBarPreference
|
<!--com.limelight.preferences.SeekBarPreference
|
||||||
android:key="seekbar_deadzone"
|
android:key="seekbar_deadzone"
|
||||||
android:defaultValue="15"
|
android:defaultValue="15"
|
||||||
android:max="50"
|
android:max="50"
|
||||||
android:text="@string/suffix_seekbar_deadzone"
|
android:text="@string/suffix_seekbar_deadzone"
|
||||||
android:title="@string/title_seekbar_deadzone"/-->
|
android:title="@string/title_seekbar_deadzone"/-->
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_touchscreen_trackpad"
|
||||||
|
android:title="@string/title_checkbox_touchscreen_trackpad"
|
||||||
|
android:summary="@string/summary_checkbox_touchscreen_trackpad"
|
||||||
|
android:defaultValue="true" />
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="checkbox_multi_controller"
|
android:key="checkbox_multi_controller"
|
||||||
android:title="@string/title_checkbox_multi_controller"
|
android:title="@string/title_checkbox_multi_controller"
|
||||||
@@ -89,6 +85,11 @@
|
|||||||
android:title="@string/title_checkbox_vibrate_fallback"
|
android:title="@string/title_checkbox_vibrate_fallback"
|
||||||
android:summary="@string/summary_checkbox_vibrate_fallback"
|
android:summary="@string/summary_checkbox_vibrate_fallback"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_flip_face_buttons"
|
||||||
|
android:title="@string/title_checkbox_flip_face_buttons"
|
||||||
|
android:summary="@string/summary_checkbox_flip_face_buttons"
|
||||||
|
android:defaultValue="false" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
|
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
|
||||||
android:key="category_onscreen_controls">
|
android:key="category_onscreen_controls">
|
||||||
@@ -142,7 +143,13 @@
|
|||||||
android:summary="@string/summary_checkbox_host_audio"
|
android:summary="@string/summary_checkbox_host_audio"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_ui_settings">
|
<PreferenceCategory android:title="@string/category_ui_settings"
|
||||||
|
android:key="category_ui_settings">
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_enable_pip"
|
||||||
|
android:title="@string/title_checkbox_enable_pip"
|
||||||
|
android:summary="@string/summary_checkbox_enable_pip"
|
||||||
|
android:defaultValue="false" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="list_languages"
|
android:key="list_languages"
|
||||||
android:title="@string/title_language_list"
|
android:title="@string/title_language_list"
|
||||||
@@ -154,14 +161,14 @@
|
|||||||
android:key="checkbox_small_icon_mode"
|
android:key="checkbox_small_icon_mode"
|
||||||
android:title="@string/title_checkbox_small_icon_mode"
|
android:title="@string/title_checkbox_small_icon_mode"
|
||||||
android:summary="@string/summary_checkbox_small_icon_mode" />
|
android:summary="@string/summary_checkbox_small_icon_mode" />
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="checkbox_list_mode"
|
|
||||||
android:title="@string/title_checkbox_list_mode"
|
|
||||||
android:summary="@string/summary_checkbox_list_mode"
|
|
||||||
android:defaultValue="false" />
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/category_advanced_settings"
|
<PreferenceCategory android:title="@string/category_advanced_settings"
|
||||||
android:key="category_advanced_settings">
|
android:key="category_advanced_settings">
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_unlock_fps"
|
||||||
|
android:title="@string/title_unlock_fps"
|
||||||
|
android:summary="@string/summary_unlock_fps"
|
||||||
|
android:defaultValue="false" />
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="checkbox_disable_warnings"
|
android:key="checkbox_disable_warnings"
|
||||||
android:title="@string/title_checkbox_disable_warnings"
|
android:title="@string/title_checkbox_disable_warnings"
|
||||||
@@ -189,5 +196,10 @@
|
|||||||
android:title="@string/title_enable_perf_overlay"
|
android:title="@string/title_enable_perf_overlay"
|
||||||
android:summary="@string/summary_enable_perf_overlay"
|
android:summary="@string/summary_enable_perf_overlay"
|
||||||
android:defaultValue="false"/>
|
android:defaultValue="false"/>
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="checkbox_enable_post_stream_toast"
|
||||||
|
android:title="@string/title_enable_post_stream_toast"
|
||||||
|
android:summary="@string/summary_enable_post_stream_toast"
|
||||||
|
android:defaultValue="false"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
- Improved stylus deadzone behavior
|
||||||
|
- Fixed incorrect mouse position on styluses without hover support
|
||||||
|
- Fixed mouse clicks causing cursor jumping on Nvidia Shield devices
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
- Direct touchscreen mouse control is now supported! (disable touchscreen trackpad mode in settings)
|
||||||
|
- Added an option to switch the A/B and X/Y face buttons on gamepads and on-screen controls
|
||||||
|
- Added a special error message if no video was received instead of just displaying a black screen
|
||||||
|
- Improved support for Xbox One S controllers using the new 4.8 firmware
|
||||||
|
- Improved handling of audio capture errors reported by GeForce Experience
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
- Fixed stream starting in 720p when configured for 1080p or 4K at 90 or 120 FPS
|
||||||
|
- Fixed incorrect behavior of right-side modifier keys while streaming
|
||||||
|
- Fixed the mouse cursor not reaching the bottom and right edges of the host's screen on some devices
|
||||||
|
- Updated Traditional and Simplified Chinese translations
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
- Improved PC and App Grid UI
|
||||||
|
- Android 11 optimizations
|
||||||
|
- Added mapping for Nintendo Switch Pro controller
|
||||||
|
- Post-stream latency message is now disabled by default
|
||||||
|
- Fixed control stream connection error on some networks
|
||||||
|
- Reorganized some of the settings options
|
||||||
@@ -15,12 +15,12 @@ Streaming performance may vary based on your client device and network setup. HD
|
|||||||
* Mouse control via gamepad by long-pressing Start
|
* Mouse control via gamepad by long-pressing Start
|
||||||
|
|
||||||
'''PC Requirements'''
|
'''PC Requirements'''
|
||||||
* NVIDIA GeForce GTX/RTX series GPU (''GT-series and AMD GPUs aren't supported by NVIDIA GameStream'')
|
* NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU
|
||||||
* Windows 7 or later
|
* Windows 7 or later
|
||||||
* NVIDIA GeForce Experience (GFE) 2.2.2 or later
|
* NVIDIA GeForce Experience or NVIDIA Quadro Experience installed
|
||||||
|
|
||||||
'''Quick Setup Instructions'''
|
'''Quick Setup Instructions'''
|
||||||
# Make sure GeForce Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
|
# Make sure GeForce/Quadro Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
|
||||||
# Tap on the PC in Moonlight and type the PIN on your PC
|
# Tap on the PC in Moonlight and type the PIN on your PC
|
||||||
# Start streaming!
|
# Start streaming!
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
#Mon Feb 24 12:32:24 PST 2020
|
#Thu May 28 11:41:09 PDT 2020
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||||
|
|||||||
Reference in New Issue
Block a user