Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 429c32477c | |||
| f5d51b2061 | |||
| 2ad1aaa277 | |||
| 3afd32dbc1 | |||
| 092830ed07 | |||
| d118a6d3ff | |||
| fe97ffdc2f | |||
| 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 |
+3
-3
@@ -8,11 +8,11 @@ android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-29.0.3
|
||||
- android-29
|
||||
- build-tools-30.0.0
|
||||
- android-30
|
||||
|
||||
before_install:
|
||||
- sdkmanager --list
|
||||
|
||||
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)
|
||||
|
||||
[Moonlight](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 for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
* Full gamepad support for MOGA, Xbox 360, PS3, OUYA, and Shield
|
||||
* Automatically finds GameStream-compatible PCs on your network
|
||||
|
||||
## Installation
|
||||
|
||||
* 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
|
||||
## Downloads
|
||||
* [Google Play Store](https://play.google.com/store/apps/details?id=com.limelight)
|
||||
* [Amazon App Store](https://www.amazon.com/gp/product/B00JK4MFN2)
|
||||
* [F-Droid](https://f-droid.org/packages/com.limelight)
|
||||
* [APK](https://github.com/moonlight-stream/moonlight-android/releases)
|
||||
|
||||
## Building
|
||||
* Install Android Studio and the Android NDK
|
||||
* 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.
|
||||
* Build the APK using Android Studio
|
||||
* Build the APK using Android Studio or gradle
|
||||
|
||||
## Authors
|
||||
|
||||
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
|
||||
versionName "9.5.1"
|
||||
versionCode = 225
|
||||
versionName "9.6.1"
|
||||
versionCode = 233
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
|
||||
@@ -120,7 +120,8 @@
|
||||
android:resizeableActivity="true"
|
||||
android:launchMode="singleTask"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/StreamTheme">
|
||||
android:theme="@style/StreamTheme"
|
||||
android:preferMinimalPostProcessing="true">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.AppView" />
|
||||
|
||||
@@ -434,8 +434,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
return true;
|
||||
|
||||
case VIEW_DETAILS_ID:
|
||||
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details),
|
||||
getResources().getString(R.string.applist_details_id) + " " + app.app.getAppId(), false);
|
||||
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details), app.app.toString(), false);
|
||||
return true;
|
||||
|
||||
case CREATE_SHORTCUT_ID:
|
||||
@@ -565,9 +564,8 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
@Override
|
||||
public int getAdapterFragmentLayoutId() {
|
||||
return PreferenceConfiguration.readPreferences(this).listMode ?
|
||||
R.layout.list_view : (PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
|
||||
R.layout.app_grid_view_small : R.layout.app_grid_view);
|
||||
return PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
|
||||
R.layout.app_grid_view_small : R.layout.app_grid_view;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -63,6 +63,7 @@ import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.View.OnGenericMotionListener;
|
||||
@@ -212,11 +213,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
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
|
||||
// was turned on by the user
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
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
|
||||
@@ -609,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() {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||
@@ -709,7 +693,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
// 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
|
||||
getWindow().setAttributes(windowLayoutParams);
|
||||
@@ -865,34 +851,36 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
displayedFailureDialog = true;
|
||||
stopConnection();
|
||||
|
||||
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
||||
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
||||
String message = null;
|
||||
if (averageEndToEndLat > 0) {
|
||||
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
|
||||
if (averageDecoderLat > 0) {
|
||||
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
|
||||
if (prefConfig.enableLatencyToast) {
|
||||
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
||||
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
||||
String message = null;
|
||||
if (averageEndToEndLat > 0) {
|
||||
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" 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
|
||||
if (message != null) {
|
||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||
message += " [H.265 HDR]";
|
||||
// Add the video codec to the post-stream toast
|
||||
if (message != null) {
|
||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||
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) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||
if (message != null) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the tombstone count if we terminated normally
|
||||
@@ -1691,6 +1679,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
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
|
||||
|
||||
@@ -692,9 +692,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
@Override
|
||||
public int getAdapterFragmentLayoutId() {
|
||||
return PreferenceConfiguration.readPreferences(this).listMode ?
|
||||
R.layout.list_view : (PreferenceConfiguration.readPreferences(this).smallIconMode ?
|
||||
R.layout.pc_grid_view_small : R.layout.pc_grid_view);
|
||||
return R.layout.pc_grid_view;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,12 +40,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
|
||||
|
||||
private static final int EMULATING_SPECIAL = 0x1;
|
||||
private static final int EMULATING_SELECT = 0x2;
|
||||
|
||||
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
|
||||
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
|
||||
|
||||
private final Vector2d inputVector = new Vector2d();
|
||||
|
||||
private final SparseArray<InputDeviceContext> inputDeviceContexts = new SparseArray<>();
|
||||
@@ -832,6 +826,38 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
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) {
|
||||
// Android's Generic.kl swaps BTN_NORTH and BTN_WEST
|
||||
switch (event.getScanCode()) {
|
||||
@@ -1390,41 +1416,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we're emulating the select button
|
||||
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
|
||||
{
|
||||
// If either start or LB is up, select comes up too
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||
(context.inputMap & ControllerPacket.LB_FLAG) == 0)
|
||||
{
|
||||
context.inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||
|
||||
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're emulating the special button
|
||||
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
|
||||
{
|
||||
// If either start or select and RB is up, the special button comes up too
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||
((context.inputMap & ControllerPacket.BACK_FLAG) == 0 &&
|
||||
(context.inputMap & ControllerPacket.RB_FLAG) == 0))
|
||||
{
|
||||
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
|
||||
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
sendControllerInputPacket(context);
|
||||
|
||||
if (context.pendingExit && context.inputMap == 0) {
|
||||
@@ -1544,29 +1535,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.pendingExit = true;
|
||||
}
|
||||
|
||||
// Start+LB acts like select for controllers with one button
|
||||
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG) ||
|
||||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
|
||||
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
{
|
||||
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
|
||||
context.inputMap |= ControllerPacket.BACK_FLAG;
|
||||
|
||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
|
||||
}
|
||||
|
||||
// We detect select+start or start+RB as the special button combo
|
||||
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG) ||
|
||||
context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG) ||
|
||||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
|
||||
SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
{
|
||||
context.inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
|
||||
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
|
||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
|
||||
}
|
||||
|
||||
// We don't need to send repeat key down events, but the platform
|
||||
// sends us events that claim to be repeats but they're from different
|
||||
// devices, so we just send them all and deal with some duplicates.
|
||||
@@ -1712,8 +1680,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public boolean hasJoystickAxes;
|
||||
public boolean pendingExit;
|
||||
|
||||
public int emulatingButtonFlags = 0;
|
||||
|
||||
// Used for OUYA bumper state tracking since they force all buttons
|
||||
// up when the OUYA button goes down. We watch the last time we get
|
||||
// a bumper up and compare that to our maximum delay when we receive
|
||||
|
||||
@@ -210,7 +210,7 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
// calculate new radius sizes depending
|
||||
radius_complete = getPercent(getCorrectWidth() / 2, 100);
|
||||
radius_complete = getPercent(getCorrectWidth() / 2, 100) - 2 * getDefaultStrokeWidth();
|
||||
radius_dead_zone = getPercent(getCorrectWidth() / 2, 30);
|
||||
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20);
|
||||
|
||||
|
||||
+4
-4
@@ -167,11 +167,11 @@ public class VirtualControllerConfigurationLoader {
|
||||
private static final int DPAD_BASE_Y = 41;
|
||||
private static final int DPAD_SIZE = 30;
|
||||
|
||||
private static final int ANALOG_L_BASE_X = 4;
|
||||
private static final int ANALOG_L_BASE_Y = 1;
|
||||
private static final int ANALOG_R_BASE_X = 96;
|
||||
private static final int ANALOG_L_BASE_X = 6;
|
||||
private static final int ANALOG_L_BASE_Y = 4;
|
||||
private static final int ANALOG_R_BASE_X = 98;
|
||||
private static final int ANALOG_R_BASE_Y = 42;
|
||||
private static final int ANALOG_SIZE = 28;
|
||||
private static final int ANALOG_SIZE = 26;
|
||||
|
||||
private static final int L3_R3_BASE_Y = 60;
|
||||
|
||||
|
||||
@@ -318,8 +318,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
||||
}
|
||||
|
||||
if (lowLatency) {
|
||||
videoFormat.setInteger(MediaCodecHelper.KEY_LOW_LATENCY, 1);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lowLatency) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// 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> 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 initialized = false;
|
||||
|
||||
@@ -343,10 +339,9 @@ public class MediaCodecHelper {
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
// KitKat added CodecCapabilities.isFeatureSupported()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
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)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.limelight.grid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
@@ -40,10 +41,7 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
}
|
||||
|
||||
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
|
||||
if (prefs.listMode) {
|
||||
return R.layout.simple_row;
|
||||
}
|
||||
else if (prefs.smallIconMode) {
|
||||
if (prefs.smallIconMode) {
|
||||
return R.layout.app_grid_item_small;
|
||||
}
|
||||
else {
|
||||
@@ -113,30 +111,17 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
}
|
||||
|
||||
@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
|
||||
loader.populateImageView(obj.app, imgView, prgView);
|
||||
return true;
|
||||
}
|
||||
loader.populateImageView(obj.app, imgView, txtView);
|
||||
|
||||
@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) {
|
||||
// Show the play button overlay
|
||||
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 com.limelight.R;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -55,9 +54,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||
return i;
|
||||
}
|
||||
|
||||
public abstract boolean populateImageView(ImageView imgView, ProgressBar prgView, T obj);
|
||||
public abstract boolean populateTextView(TextView txtView, T obj);
|
||||
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
|
||||
public abstract void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, T obj);
|
||||
|
||||
@Override
|
||||
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);
|
||||
ProgressBar prgView = convertView.findViewById(R.id.grid_spinner);
|
||||
|
||||
if (imgView != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
populateView(imgView, prgView, txtView, overlayView, itemList.get(i));
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@@ -22,15 +22,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
}
|
||||
|
||||
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
|
||||
if (prefs.listMode) {
|
||||
return R.layout.simple_row;
|
||||
}
|
||||
else if (prefs.smallIconMode) {
|
||||
return R.layout.pc_grid_item_small;
|
||||
}
|
||||
else {
|
||||
return R.layout.pc_grid_item;
|
||||
}
|
||||
return R.layout.pc_grid_item;
|
||||
}
|
||||
|
||||
public void updateLayoutWithPreferences(Context context, PreferenceConfiguration prefs) {
|
||||
@@ -57,7 +49,8 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
}
|
||||
|
||||
@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) {
|
||||
imgView.setAlpha(1.0f);
|
||||
}
|
||||
@@ -72,12 +65,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
prgView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
imgView.setImageResource(R.drawable.ic_computer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
|
||||
txtView.setText(obj.details.name);
|
||||
if (obj.details.state == ComputerDetails.State.ONLINE) {
|
||||
txtView.setAlpha(1.0f);
|
||||
}
|
||||
@@ -85,16 +73,10 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
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) {
|
||||
overlayView.setImageResource(R.drawable.ic_pc_offline);
|
||||
overlayView.setAlpha(0.4f);
|
||||
return true;
|
||||
overlayView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
// We must check if the status is exactly online and unpaired
|
||||
// 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) {
|
||||
overlayView.setImageResource(R.drawable.ic_lock);
|
||||
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.os.AsyncTask;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
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.NvApp;
|
||||
|
||||
@@ -89,7 +92,7 @@ public class CachedAppAssetLoader {
|
||||
memoryLoader.clearCache();
|
||||
}
|
||||
|
||||
private Bitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
||||
private ScaledBitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
||||
// Try 3 times
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// 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 (task != null) {
|
||||
// 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) {
|
||||
return bmp;
|
||||
}
|
||||
@@ -132,29 +135,29 @@ public class CachedAppAssetLoader {
|
||||
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<ProgressBar> progressViewRef;
|
||||
private final WeakReference<TextView> textViewRef;
|
||||
private final boolean diskOnly;
|
||||
|
||||
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.progressViewRef = new WeakReference<>(prgView);
|
||||
this.textViewRef = new WeakReference<>(textView);
|
||||
this.diskOnly = diskOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(LoaderTuple... params) {
|
||||
protected ScaledBitmap doInBackground(LoaderTuple... params) {
|
||||
tuple = params[0];
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||
ScaledBitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||
if (bmp == null) {
|
||||
if (!diskOnly) {
|
||||
// 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
|
||||
final ImageView imageView = imageViewRef.get();
|
||||
final ProgressBar prgView = progressViewRef.get();
|
||||
final TextView textView = textViewRef.get();
|
||||
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
|
||||
// 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);
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
protected void onPostExecute(final ScaledBitmap bitmap) {
|
||||
// Do nothing if cancelled
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ImageView imageView = imageViewRef.get();
|
||||
final ProgressBar prgView = progressViewRef.get();
|
||||
final TextView textView = textViewRef.get();
|
||||
if (getLoaderTask(imageView) == this) {
|
||||
// Set the bitmap
|
||||
// Fade in the box art
|
||||
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 (prgView != null) {
|
||||
prgView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (imageView.getVisibility() == View.VISIBLE) {
|
||||
// Fade out the placeholder first
|
||||
Animation fadeOutAnimation = AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadeout);
|
||||
fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {}
|
||||
|
||||
// Show the view
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
@Override
|
||||
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);
|
||||
|
||||
// If there's already a task in progress for this view,
|
||||
@@ -309,22 +334,26 @@ public class CachedAppAssetLoader {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hide the progress bar always on initial load
|
||||
prgView.setVisibility(View.INVISIBLE);
|
||||
// Always set the name text so we have it if needed later
|
||||
textView.setText(app.getAppName());
|
||||
|
||||
// First, try the memory cache in the current context
|
||||
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||
ScaledBitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||
if (bmp != null) {
|
||||
// Show the bitmap immediately
|
||||
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;
|
||||
}
|
||||
|
||||
// If it's not in memory, create an async task to load it. This task will be attached
|
||||
// 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);
|
||||
textView.setVisibility(View.INVISIBLE);
|
||||
imgView.setVisibility(View.INVISIBLE);
|
||||
imgView.setImageDrawable(asyncDrawable);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public class DiskAssetLoader {
|
||||
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());
|
||||
|
||||
// Don't bother with anything if it doesn't exist
|
||||
@@ -110,27 +110,33 @@ public class DiskAssetLoader {
|
||||
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
if (bmp != null) {
|
||||
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
||||
return new ScaledBitmap(decodeOnlyOptions.outWidth, decodeOnlyOptions.outHeight, bmp);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// On P, we can get a bitmap back in one step with ImageDecoder
|
||||
final ScaledBitmap scaledBitmap = new ScaledBitmap();
|
||||
try {
|
||||
bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
|
||||
scaledBitmap.bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
|
||||
@Override
|
||||
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);
|
||||
if (isLowRamDevice) {
|
||||
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
|
||||
}
|
||||
}
|
||||
});
|
||||
return scaledBitmap;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bmp;
|
||||
return null;
|
||||
}
|
||||
|
||||
public File getFile(String computerUuid, int appId) {
|
||||
|
||||
@@ -1,37 +1,74 @@
|
||||
package com.limelight.grid.assets;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.LruCache;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MemoryAssetLoader {
|
||||
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
|
||||
protected int sizeOf(String key, Bitmap bitmap) {
|
||||
protected int sizeOf(String key, ScaledBitmap bitmap) {
|
||||
// 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) {
|
||||
return tuple.computer.uuid+"-"+tuple.app.getAppId();
|
||||
}
|
||||
|
||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
Bitmap bmp = memoryCache.get(constructKey(tuple));
|
||||
public ScaledBitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
final String key = constructKey(tuple);
|
||||
|
||||
ScaledBitmap bmp = memoryCache.get(key);
|
||||
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);
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
// We must evict first because that will push all items into the eviction cache
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,9 @@ public class ComputerDetails {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("Name: ").append(name).append("\n");
|
||||
str.append("State: ").append(state).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("Local Address: ").append(localAddress).append("\n");
|
||||
str.append("Remote Address: ").append(remoteAddress).append("\n");
|
||||
|
||||
@@ -58,4 +58,13 @@ public class NvApp {
|
||||
public boolean isInitialized() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ public class PreferenceConfiguration {
|
||||
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
|
||||
private static final String OSC_OPACITY_PREF_STRING = "seekbar_osc_opacity";
|
||||
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 MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
||||
static final String AUDIO_CONFIG_PREF_STRING = "list_audio_config";
|
||||
@@ -43,8 +42,9 @@ public class PreferenceConfiguration {
|
||||
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";
|
||||
private static final boolean DEFAULT_STRETCH = false;
|
||||
private static final boolean DEFAULT_SOPS = true;
|
||||
@@ -53,7 +53,6 @@ public class PreferenceConfiguration {
|
||||
private static final int DEFAULT_DEADZONE = 15;
|
||||
private static final int DEFAULT_OPACITY = 90;
|
||||
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_USB_DRIVER = true;
|
||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||
@@ -72,11 +71,19 @@ public class PreferenceConfiguration {
|
||||
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 boolean DEFAULT_LATENCY_TOAST = false;
|
||||
|
||||
public static final int FORCE_H265_ON = -1;
|
||||
public static final int AUTOSELECT_H265 = 0;
|
||||
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 bitrate;
|
||||
public int videoFormat;
|
||||
@@ -84,13 +91,14 @@ public class PreferenceConfiguration {
|
||||
public int oscOpacity;
|
||||
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
||||
public String language;
|
||||
public boolean listMode, smallIconMode, multiController, usbDriver, flipFaceButtons;
|
||||
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
|
||||
public boolean onscreenController;
|
||||
public boolean onlyL3R3;
|
||||
public boolean disableFrameDrop;
|
||||
public boolean enableHdr;
|
||||
public boolean enablePip;
|
||||
public boolean enablePerfOverlay;
|
||||
public boolean enableLatencyToast;
|
||||
public boolean bindAllUsb;
|
||||
public boolean mouseEmulation;
|
||||
public boolean mouseNavButtons;
|
||||
@@ -100,58 +108,54 @@ public class PreferenceConfiguration {
|
||||
public boolean touchscreenTrackpad;
|
||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||
|
||||
private static int getHeightFromResolutionString(String resString) {
|
||||
private static String convertFromLegacyResolutionString(String resString) {
|
||||
if (resString.equalsIgnoreCase("360p")) {
|
||||
return 360;
|
||||
return RES_360P;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("480p")) {
|
||||
return 480;
|
||||
return RES_480P;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("720p")) {
|
||||
return 720;
|
||||
return RES_720P;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("1080p")) {
|
||||
return 1080;
|
||||
return RES_1080P;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("1440p")) {
|
||||
return 1440;
|
||||
return RES_1440P;
|
||||
}
|
||||
else if (resString.equalsIgnoreCase("4K")) {
|
||||
return 2160;
|
||||
return RES_4K;
|
||||
}
|
||||
else {
|
||||
// Should be unreachable
|
||||
return 720;
|
||||
return RES_720P;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getWidthFromResolutionString(String resString) {
|
||||
int height = getHeightFromResolutionString(resString);
|
||||
if (height == 480) {
|
||||
// This isn't an exact 16:9 resolution
|
||||
return 854;
|
||||
}
|
||||
else {
|
||||
return (height * 16) / 9;
|
||||
}
|
||||
return Integer.parseInt(resString.split("x")[0]);
|
||||
}
|
||||
|
||||
private static int getHeightFromResolutionString(String resString) {
|
||||
return Integer.parseInt(resString.split("x")[1]);
|
||||
}
|
||||
|
||||
private static String getResolutionString(int width, int height) {
|
||||
switch (height) {
|
||||
case 360:
|
||||
return "360p";
|
||||
return RES_360P;
|
||||
case 480:
|
||||
return "480p";
|
||||
return RES_480P;
|
||||
default:
|
||||
case 720:
|
||||
return "720p";
|
||||
return RES_720P;
|
||||
case 1080:
|
||||
return "1080p";
|
||||
return RES_1080P;
|
||||
case 1440:
|
||||
return "1440p";
|
||||
return RES_1440P;
|
||||
case 2160:
|
||||
return "4K";
|
||||
|
||||
return RES_4K;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,11 +326,24 @@ public class PreferenceConfiguration {
|
||||
else {
|
||||
// Use the new preference location
|
||||
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.height = PreferenceConfiguration.getHeightFromResolutionString(resStr);
|
||||
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
|
||||
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, prefs.getInt(BITRATE_PREF_OLD_STRING, 0) * 1000);
|
||||
if (config.bitrate == 0) {
|
||||
@@ -357,7 +374,6 @@ public class PreferenceConfiguration {
|
||||
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
|
||||
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
|
||||
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.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
|
||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||
@@ -375,6 +391,7 @@ public class PreferenceConfiguration {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.MediaCodecInfo;
|
||||
@@ -7,6 +8,7 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
@@ -54,9 +56,7 @@ public class StreamSettings extends Activity {
|
||||
|
||||
// Check for changes that require a UI reload to take effect
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (newPrefs.listMode != previousPrefs.listMode ||
|
||||
newPrefs.smallIconMode != previousPrefs.smallIconMode ||
|
||||
!newPrefs.language.equals(previousPrefs.language)) {
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@@ -154,13 +154,29 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove PiP mode on devices pre-Oreo
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
|
||||
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
!getActivity().getPackageManager().hasSystemFeature("android.software.picture_in_picture") ||
|
||||
getActivity().getPackageManager().hasSystemFeature("com.amazon.software.fireos")) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_basic_settings");
|
||||
(PreferenceCategory) findPreference("category_ui_settings");
|
||||
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;
|
||||
|
||||
// Hide non-supported resolution/FPS combinations
|
||||
@@ -249,33 +265,33 @@ public class StreamSettings extends Activity {
|
||||
if (maxSupportedResW != 0) {
|
||||
if (maxSupportedResW < 3840) {
|
||||
// 4K is unsupported
|
||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "4K", new Runnable() {
|
||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_4K, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (maxSupportedResW < 2560) {
|
||||
// 1440p is unsupported
|
||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p", new Runnable() {
|
||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (maxSupportedResW < 1920) {
|
||||
// 1080p is unsupported
|
||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p", new Runnable() {
|
||||
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -327,7 +343,7 @@ public class StreamSettings extends Activity {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
LimeLog.info("Excluding unlock FPS toggle based on OS");
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_basic_settings");
|
||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||
category.removePreference(findPreference("checkbox_unlock_fps"));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -3,41 +3,22 @@ package com.limelight.utils;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.limelight.HelpActivity;
|
||||
|
||||
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) {
|
||||
// Try to launch the default browser
|
||||
try {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
|
||||
// Several Android TV devices will lie and say they do have a browser
|
||||
// even though the OS just shows an error dialog if we try to use it. We need to
|
||||
// be a bit more clever on these devices and detect if the browser is a legitimate
|
||||
// browser or just a fake error message activity.
|
||||
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
|
||||
isKnownBrowser(context, i)) {
|
||||
// Several Android TV devices will lie and say they do have a browser even though the OS
|
||||
// just shows an error dialog if we try to use it. We used to try to be clever and check
|
||||
// the package name of the resolved intent, but it's not worth it anymore with Android 11's
|
||||
// package visibility changes. We'll just always use the WebView on Android TV.
|
||||
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
context.startActivity(i);
|
||||
return;
|
||||
}
|
||||
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: bea625a13d...407d57a50a
@@ -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"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp">
|
||||
<RelativeLayout
|
||||
android:id="@+id/grid_image_layout"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="200dp"
|
||||
android:padding="10dp">
|
||||
<ImageView
|
||||
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_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
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>
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp">
|
||||
</ImageView>
|
||||
<TextView
|
||||
android:id="@+id/grid_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/grid_image_layout"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/grid_overlay"
|
||||
android:layout_margin="5dp"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:textSize="18sp" >
|
||||
android:textSize="18sp">
|
||||
</TextView>
|
||||
</RelativeLayout>
|
||||
@@ -1,49 +1,29 @@
|
||||
<?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="10dp">
|
||||
<RelativeLayout
|
||||
android:id="@+id/grid_image_layout"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="133dp"
|
||||
android:padding="5dp">
|
||||
<ImageView
|
||||
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_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
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>
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_width="33dp"
|
||||
android:layout_height="33dp">
|
||||
</ImageView>
|
||||
<TextView
|
||||
android:id="@+id/grid_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/grid_image_layout"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/grid_overlay"
|
||||
android:layout_margin="5dp"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:textSize="14sp" >
|
||||
android:textSize="14sp">
|
||||
</TextView>
|
||||
</RelativeLayout>
|
||||
@@ -4,7 +4,7 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:numColumns="auto_fit"
|
||||
android:columnWidth="160dp"
|
||||
android:columnWidth="150dp"
|
||||
android:stretchMode="spacingWidthUniform"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:numColumns="auto_fit"
|
||||
android:columnWidth="105dp"
|
||||
android:columnWidth="100dp"
|
||||
android:stretchMode="spacingWidthUniform"
|
||||
android:focusable="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="title_language_list">Sprache</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="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="title_language_list">Idioma</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="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="title_language_list">Langue</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="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="title_language_list">Lingua</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="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="title_language_list">言語</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="summary_checkbox_small_icon_mode">グリッドで小さなアイコンを使用します</string>
|
||||
|
||||
|
||||
@@ -120,8 +120,6 @@
|
||||
<string name="category_ui_settings">UI 설정</string>
|
||||
<string name="title_language_list">언어</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="summary_checkbox_small_icon_mode">더 많이 표시하기 위해 그리드 표시에서 작은 아이콘을 사용합니다.</string>
|
||||
|
||||
|
||||
@@ -109,8 +109,6 @@
|
||||
<string name="category_ui_settings">UI Installingen</string>
|
||||
<string name="title_language_list">Taal</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="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="title_language_list">Limba (Language)</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="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="title_language_list">Язык</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="summary_checkbox_small_icon_mode">Использовать маленькие иконки в сетке для отображения большего числа элементов на экране</string>
|
||||
|
||||
|
||||
@@ -173,8 +173,6 @@
|
||||
<string name="category_ui_settings"> 界面设置 </string>
|
||||
<string name="title_language_list"> 语言 </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="summary_checkbox_small_icon_mode"> 使用小图标以在屏幕上显示更多项目 </string>
|
||||
|
||||
@@ -191,24 +189,10 @@
|
||||
<string name="summary_video_format">H.265能降低视频带宽需求,但需要较新的设备才能支持</string>
|
||||
<string name="title_enable_hdr"> 启用 HDR (实验) </string>
|
||||
<string name="summary_enable_hdr"> 当游戏和显卡支持时以HDR模式串流。 HDR需要GTX 1000系列或更高规格显卡。 </string>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<string name="title_enable_perf_overlay"> 启用性能信息 </string>
|
||||
<string name="summary_enable_perf_overlay"> 在串流中显示实时性能信息 </string>
|
||||
<string name="title_enable_post_stream_toast">串流完毕显示延迟信息</string>
|
||||
<string name="summary_enable_post_stream_toast">串流结束后显示延迟信息</string>
|
||||
<string name="title_osc_opacity">更改屏幕按钮透明度</string>
|
||||
<string name="dialog_title_osc_opacity">透明度</string>
|
||||
<string name="suffix_osc_opacity">%</string>
|
||||
|
||||
@@ -173,8 +173,6 @@
|
||||
<string name="category_ui_settings"> 介面設置 </string>
|
||||
<string name="title_language_list"> 語言 </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="summary_checkbox_small_icon_mode"> 使用小圖示以在螢幕上顯示更多專案 </string>
|
||||
|
||||
@@ -191,24 +189,10 @@
|
||||
<string name="summary_video_format">H.265能降低視頻頻寬需求,但需要較新的設備才能支援</string>
|
||||
<string name="title_enable_hdr"> 啟用 HDR (實驗) </string>
|
||||
<string name="summary_enable_hdr"> 當遊戲和顯卡支援時以HDR模式串流。 HDR需要GTX 1000系列或更高規格顯卡。 </string>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<string name="title_enable_perf_overlay"> 啟用性能資訊 </string>
|
||||
<string name="summary_enable_perf_overlay"> 在串流中顯示即時性能資訊 </string>
|
||||
<string name="title_enable_post_stream_toast">串流完畢顯示延遲資訊</string>
|
||||
<string name="summary_enable_post_stream_toast">串流結束後顯示延遲資訊</string>
|
||||
<string name="title_osc_opacity">更改屏幕按鈕透明度</string>
|
||||
<string name="dialog_title_osc_opacity">透明度</string>
|
||||
<string name="suffix_osc_opacity">%</string>
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
<item>4K</item>
|
||||
</string-array>
|
||||
<string-array name="resolution_values" translatable="false">
|
||||
<item>360p</item>
|
||||
<item>480p</item>
|
||||
<item>720p</item>
|
||||
<item>1080p</item>
|
||||
<item>1440p</item>
|
||||
<item>4K</item>
|
||||
<item>640x360</item>
|
||||
<item>854x480</item>
|
||||
<item>1280x720</item>
|
||||
<item>1920x1080</item>
|
||||
<item>2560x1440</item>
|
||||
<item>3840x2160</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="fps_names">
|
||||
|
||||
@@ -127,13 +127,7 @@
|
||||
<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="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_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="title_audio_config_list">Surround sound configuration</string>
|
||||
@@ -148,10 +142,10 @@
|
||||
<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="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="title_checkbox_usb_bind_all">Override Android controller support</string>
|
||||
<string name="summary_checkbox_usb_bind_all">Forces Moonlight\'s USB driver to take over all supported Xbox gamepads</string>
|
||||
<string name="title_checkbox_usb_bind_all">Override native Xbox gamepad support</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="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>
|
||||
@@ -177,12 +171,12 @@
|
||||
<string name="suffix_osc_opacity">%</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="summary_language_list">Language to use for Moonlight</string>
|
||||
<string name="title_checkbox_list_mode">Use lists instead of grids</string>
|
||||
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</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="title_checkbox_small_icon_mode">Use small box art</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="category_host_settings">Host Settings</string>
|
||||
<string name="title_checkbox_enable_sops">Optimize game settings</string>
|
||||
@@ -191,13 +185,19 @@
|
||||
<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="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="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="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="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="summary_enable_perf_overlay">Display an on-screen overlay with real-time performance information while streaming</string>
|
||||
<string name="title_enable_perf_overlay">Show performance stats 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>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
android:summary="@string/summary_resolution_list"
|
||||
android:entries="@array/resolution_names"
|
||||
android:entryValues="@array/resolution_values"
|
||||
android:defaultValue="720p" />
|
||||
android:defaultValue="1280x720" />
|
||||
<ListPreference
|
||||
android:key="list_fps"
|
||||
android:title="@string/title_fps_list"
|
||||
@@ -27,20 +27,10 @@
|
||||
android:summary="@string/summary_seekbar_bitrate"
|
||||
android:text="@string/suffix_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
|
||||
android:key="checkbox_stretch_video"
|
||||
android:title="@string/title_checkbox_stretch_video"
|
||||
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 android:title="@string/category_audio_settings">
|
||||
<ListPreference
|
||||
@@ -153,7 +143,13 @@
|
||||
android:summary="@string/summary_checkbox_host_audio"
|
||||
android:defaultValue="false" />
|
||||
</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
|
||||
android:key="list_languages"
|
||||
android:title="@string/title_language_list"
|
||||
@@ -165,14 +161,14 @@
|
||||
android:key="checkbox_small_icon_mode"
|
||||
android:title="@string/title_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 android:title="@string/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
|
||||
android:key="checkbox_disable_warnings"
|
||||
android:title="@string/title_checkbox_disable_warnings"
|
||||
@@ -200,5 +196,10 @@
|
||||
android:title="@string/title_enable_perf_overlay"
|
||||
android:summary="@string/summary_enable_perf_overlay"
|
||||
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>
|
||||
</PreferenceScreen>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
- Removed old button emulation feature which prevented LB+Start, RB+Start, and Start+Select from working as expected
|
||||
- Fixed edges of on-screen analog sticks being clipped
|
||||
- Updated Simplified and Traditional Chinese translations
|
||||
+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
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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