mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-23 00:27:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
babfc99c35 | ||
|
|
1eca461cb1 | ||
|
|
ebd327c7a6 | ||
|
|
602febe876 | ||
|
|
84fcd3ae6a | ||
|
|
84296c6e1c | ||
|
|
6012e0ea8c | ||
|
|
9c76defad0 | ||
|
|
ffd6fab35c | ||
|
|
296f97f7ca | ||
|
|
9cbef34f29 | ||
|
|
7a65136d29 | ||
|
|
acaebea846 | ||
|
|
ce850ac12f | ||
|
|
a93422d3ed | ||
|
|
b2e605838e | ||
|
|
2e14002442 | ||
|
|
c743949df5 | ||
|
|
f207a3f6d1 | ||
|
|
d6211605a1 | ||
|
|
80620ed4c6 | ||
|
|
76bd0ab696 | ||
|
|
e0914df58a |
No files matched your search
+3
-2
@@ -9,8 +9,8 @@ android {
|
|||||||
minSdk 16
|
minSdk 16
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
|
|
||||||
versionName "10.4"
|
versionName "10.6"
|
||||||
versionCode = 280
|
versionCode = 283
|
||||||
|
|
||||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||||
ndk.debugSymbolLevel = 'FULL'
|
ndk.debugSymbolLevel = 'FULL'
|
||||||
@@ -130,4 +130,5 @@ dependencies {
|
|||||||
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
|
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
|
||||||
implementation 'com.squareup.okio:okio:1.17.5'
|
implementation 'com.squareup.okio:okio:1.17.5'
|
||||||
implementation 'org.jmdns:jmdns:3.5.7'
|
implementation 'org.jmdns:jmdns:3.5.7'
|
||||||
|
implementation 'com.github.cgutman:ShieldControllerExtensions:1.0'
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,8 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:gwpAsanMode="always"
|
android:gwpAsanMode="always"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
@@ -265,14 +267,20 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Make sure Wi-Fi is fully powered up
|
// Make sure Wi-Fi is fully powered up
|
||||||
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
highPerfWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Moonlight High Perf Lock");
|
try {
|
||||||
highPerfWifiLock.setReferenceCounted(false);
|
highPerfWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Moonlight High Perf Lock");
|
||||||
highPerfWifiLock.acquire();
|
highPerfWifiLock.setReferenceCounted(false);
|
||||||
|
highPerfWifiLock.acquire();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
lowLatencyWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "Moonlight Low Latency Lock");
|
lowLatencyWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "Moonlight Low Latency Lock");
|
||||||
lowLatencyWifiLock.setReferenceCounted(false);
|
lowLatencyWifiLock.setReferenceCounted(false);
|
||||||
lowLatencyWifiLock.acquire();
|
lowLatencyWifiLock.acquire();
|
||||||
|
}
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
// Some Samsung Galaxy S10+/S10e devices throw a SecurityException from
|
||||||
|
// WifiLock.acquire() even though we have android.permission.WAKE_LOCK in our manifest.
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
||||||
@@ -591,13 +599,42 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMetaKeyCaptureState(boolean enabled) {
|
||||||
|
// This uses custom APIs present on some Samsung devices to allow capture of
|
||||||
|
// meta key events while streaming.
|
||||||
|
try {
|
||||||
|
Class<?> semWindowManager = Class.forName("com.samsung.android.view.SemWindowManager");
|
||||||
|
Method getInstanceMethod = semWindowManager.getMethod("getInstance");
|
||||||
|
Object manager = getInstanceMethod.invoke(null);
|
||||||
|
|
||||||
|
if (manager != null) {
|
||||||
|
Class<?>[] parameterTypes = new Class<?>[2];
|
||||||
|
parameterTypes[0] = String.class;
|
||||||
|
parameterTypes[1] = boolean.class;
|
||||||
|
Method requestMetaKeyEventMethod = semWindowManager.getDeclaredMethod("requestMetaKeyEvent", parameterTypes);
|
||||||
|
requestMetaKeyEventMethod.invoke(manager, this.getComponentName(), enabled);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LimeLog.warning("SemWindowManager.getInstance() returned null");
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserLeaveHint() {
|
public void onUserLeaveHint() {
|
||||||
super.onUserLeaveHint();
|
super.onUserLeaveHint();
|
||||||
|
|
||||||
// PiP is only supported on Oreo and later, and we don't need to manually enter PiP on
|
// PiP is only supported on Oreo and later, and we don't need to manually enter PiP on
|
||||||
// Android S and later.
|
// Android S and later. On Android R, we will use onPictureInPictureRequested() instead.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
if (autoEnterPip) {
|
if (autoEnterPip) {
|
||||||
try {
|
try {
|
||||||
// This has thrown all sorts of weird exceptions on Samsung devices
|
// This has thrown all sorts of weird exceptions on Samsung devices
|
||||||
@@ -611,6 +648,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TargetApi(Build.VERSION_CODES.R)
|
||||||
|
public boolean onPictureInPictureRequested() {
|
||||||
|
// Enter PiP when requested unless we're on Android 12 which supports auto-enter.
|
||||||
|
if (autoEnterPip && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
enterPictureInPictureMode(getPictureInPictureParams(false));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
super.onWindowFocusChanged(hasFocus);
|
super.onWindowFocusChanged(hasFocus);
|
||||||
@@ -1225,10 +1272,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showKeyboard() {
|
public void toggleKeyboard() {
|
||||||
LimeLog.info("Showing keyboard overlay");
|
LimeLog.info("Toggling keyboard overlay");
|
||||||
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
|
inputManager.toggleSoftInput(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the event was consumed
|
// Returns true if the event was consumed
|
||||||
@@ -1465,7 +1512,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// All fingers up
|
// All fingers up
|
||||||
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
||||||
// This is a 3 finger tap to bring up the keyboard
|
// This is a 3 finger tap to bring up the keyboard
|
||||||
showKeyboard();
|
toggleKeyboard();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1690,6 +1737,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Enable cursor visibility again
|
// Enable cursor visibility again
|
||||||
inputCaptureProvider.disableCapture();
|
inputCaptureProvider.disableCapture();
|
||||||
|
|
||||||
|
// Disable meta key capture
|
||||||
|
setMetaKeyCaptureState(false);
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
if (!displayedFailureDialog) {
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
LimeLog.severe("Connection terminated: " + errorCode);
|
LimeLog.severe("Connection terminated: " + errorCode);
|
||||||
@@ -1801,6 +1851,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Keep the display on
|
// Keep the display on
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
|
// Enable meta key capture
|
||||||
|
setMetaKeyCaptureState(true);
|
||||||
|
|
||||||
// Update GameManager state to indicate we're in game
|
// Update GameManager state to indicate we're in game
|
||||||
UiHelper.notifyStreamConnected(Game.this);
|
UiHelper.notifyStreamConnected(Game.this);
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ public class HelpActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
// Back goes back through the WebView history
|
// Back goes back through the WebView history
|
||||||
// until no more history remains
|
// until no more history remains
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class ShortcutTrampoline extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to wake the target PC if it's offline (up to some retry limit)
|
// Try to wake the target PC if it's offline (up to some retry limit)
|
||||||
if (details.state == ComputerDetails.State.OFFLINE && --wakeHostTries >= 0) {
|
if (details.state == ComputerDetails.State.OFFLINE && details.macAddress != null && --wakeHostTries >= 0) {
|
||||||
try {
|
try {
|
||||||
// Make a best effort attempt to wake the target PC
|
// Make a best effort attempt to wake the target PC
|
||||||
WakeOnLanSender.sendWolPacket(computer);
|
WakeOnLanSender.sendWolPacket(computer);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import com.limelight.LimeLog;
|
|||||||
import com.limelight.binding.input.driver.AbstractController;
|
import com.limelight.binding.input.driver.AbstractController;
|
||||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
import com.limelight.binding.input.shield.ShieldControllerExtensionsHandler;
|
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.ControllerPacket;
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
@@ -33,6 +32,8 @@ import com.limelight.preferences.PreferenceConfiguration;
|
|||||||
import com.limelight.ui.GameGestures;
|
import com.limelight.ui.GameGestures;
|
||||||
import com.limelight.utils.Vector2d;
|
import com.limelight.utils.Vector2d;
|
||||||
|
|
||||||
|
import org.cgutman.shieldcontrollerextensions.SceManager;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
@@ -62,7 +63,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||||
private final GameGestures gestures;
|
private final GameGestures gestures;
|
||||||
private final Vibrator deviceVibrator;
|
private final Vibrator deviceVibrator;
|
||||||
private final ShieldControllerExtensionsHandler shieldControllerExtensionsHandler;
|
private final SceManager sceManager;
|
||||||
private boolean hasGameController;
|
private boolean hasGameController;
|
||||||
|
|
||||||
private final PreferenceConfiguration prefConfig;
|
private final PreferenceConfiguration prefConfig;
|
||||||
@@ -74,7 +75,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
this.gestures = gestures;
|
this.gestures = gestures;
|
||||||
this.prefConfig = prefConfig;
|
this.prefConfig = prefConfig;
|
||||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
this.shieldControllerExtensionsHandler = new ShieldControllerExtensionsHandler(activityContext);
|
|
||||||
|
this.sceManager = new SceManager(activityContext);
|
||||||
|
this.sceManager.start();
|
||||||
|
|
||||||
int deadzonePercentage = prefConfig.deadzonePercentage;
|
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||||
|
|
||||||
@@ -203,7 +206,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
deviceContext.destroy();
|
deviceContext.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
shieldControllerExtensionsHandler.destroy();
|
sceManager.stop();
|
||||||
deviceVibrator.cancel();
|
deviceVibrator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1445,32 +1448,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
if (deviceContext.controllerNumber == controllerNumber) {
|
if (deviceContext.controllerNumber == controllerNumber) {
|
||||||
foundMatchingDevice = true;
|
foundMatchingDevice = true;
|
||||||
|
|
||||||
// Cancel pending rumble repeat timer if one exists
|
|
||||||
if (deviceContext.rumbleRepeatTimer != null) {
|
|
||||||
deviceContext.rumbleRepeatTimer.cancel();
|
|
||||||
deviceContext.rumbleRepeatTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
|
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
||||||
vibrated = true;
|
vibrated = true;
|
||||||
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
||||||
}
|
}
|
||||||
// On Shield devices, we can use their special API to rumble Shield controllers
|
// On Shield devices, we can use their special API to rumble Shield controllers
|
||||||
else if (shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
|
else if (sceManager.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
|
||||||
vibrated = true;
|
vibrated = true;
|
||||||
|
|
||||||
// The Shield controller can only rumble up to 1 second at a time, so we will call rumble again
|
|
||||||
// every 500 ms until the host PC gives us another rumble value.
|
|
||||||
if (lowFreqMotor != 0 || highFreqMotor != 0) {
|
|
||||||
deviceContext.rumbleRepeatTimer = new Timer("Rumble Repeat - "+deviceContext.name, true);
|
|
||||||
deviceContext.rumbleRepeatTimer.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor);
|
|
||||||
}
|
|
||||||
}, 500, 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If all else fails, we have to try the old Vibrator API
|
// If all else fails, we have to try the old Vibrator API
|
||||||
else if (deviceContext.vibrator != null) {
|
else if (deviceContext.vibrator != null) {
|
||||||
@@ -1961,7 +1946,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
public VibratorManager vibratorManager;
|
public VibratorManager vibratorManager;
|
||||||
public Vibrator vibrator;
|
public Vibrator vibrator;
|
||||||
public InputDevice inputDevice;
|
public InputDevice inputDevice;
|
||||||
public Timer rumbleRepeatTimer;
|
|
||||||
|
|
||||||
public int leftStickXAxis = -1;
|
public int leftStickXAxis = -1;
|
||||||
public int leftStickYAxis = -1;
|
public int leftStickYAxis = -1;
|
||||||
@@ -2013,10 +1997,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
else if (vibrator != null) {
|
else if (vibrator != null) {
|
||||||
vibrator.cancel();
|
vibrator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rumbleRepeatTimer != null) {
|
|
||||||
rumbleRepeatTimer.cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
package com.limelight.binding.input.shield;
|
|
||||||
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.IInterface;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
|
|
||||||
public interface IExposedControllerManagerListener extends IInterface {
|
|
||||||
void onDeviceAdded(String controllerToken);
|
|
||||||
void onDeviceChanged(String controllerToken, int i);
|
|
||||||
void onDeviceRemoved(String controllerToken);
|
|
||||||
|
|
||||||
public static abstract class Stub extends Binder implements IExposedControllerManagerListener {
|
|
||||||
public Stub() {
|
|
||||||
attachInterface(this, "com.nvidia.blakepairing.IExposedControllerManagerListener");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder asBinder() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onTransact(int code, Parcel input, Parcel output, int flags) throws RemoteException {
|
|
||||||
switch (code) {
|
|
||||||
case 1:
|
|
||||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
|
||||||
onDeviceAdded(input.readString());
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
|
||||||
onDeviceChanged(input.readString(), input.readInt());
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
|
||||||
onDeviceRemoved(input.readString());
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
case 5:
|
|
||||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
|
||||||
// Don't care
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return super.onTransact(code, input, output, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-291
@@ -1,291 +0,0 @@
|
|||||||
package com.limelight.binding.input.shield;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.hardware.input.InputManager;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class ShieldControllerExtensionsHandler implements InputManager.InputDeviceListener {
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
private IBinder binder;
|
|
||||||
private ServiceConnection serviceConnection = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
|
||||||
binder = iBinder;
|
|
||||||
|
|
||||||
try {
|
|
||||||
listenerId = registerListener();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName componentName) {
|
|
||||||
listenerId = 0;
|
|
||||||
tokenToDeviceIdMap.clear();
|
|
||||||
deviceIdToTokenMap.clear();
|
|
||||||
|
|
||||||
binder = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ConcurrentHashMap handles synchronization between the Binder thread adding/removing
|
|
||||||
// entries and callers on arbitrary threads that are doing device lookups.
|
|
||||||
//
|
|
||||||
// Since these are separate maps, they can be temporarily inconsistent (only one-way
|
|
||||||
// of the two-way mapping present). This is fine for our purposes here.
|
|
||||||
private ConcurrentHashMap<String, Integer> tokenToDeviceIdMap = new ConcurrentHashMap<>();
|
|
||||||
private ConcurrentHashMap<Integer, String> deviceIdToTokenMap = new ConcurrentHashMap<>();
|
|
||||||
private AtomicBoolean needsRefresh = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private int listenerId;
|
|
||||||
private IExposedControllerManagerListener.Stub controllerListener = new IExposedControllerManagerListener.Stub() {
|
|
||||||
@Override
|
|
||||||
public void onDeviceAdded(String controllerToken) {
|
|
||||||
try {
|
|
||||||
int inputDeviceId = getInputDeviceId(controllerToken);
|
|
||||||
|
|
||||||
LimeLog.info("Shield controller added: " + controllerToken + " -> " + inputDeviceId);
|
|
||||||
|
|
||||||
tokenToDeviceIdMap.put(controllerToken, inputDeviceId);
|
|
||||||
deviceIdToTokenMap.put(inputDeviceId, controllerToken);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDeviceChanged(String controllerToken, int i) {
|
|
||||||
LimeLog.info("Shield controller changed: " + controllerToken + " " + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDeviceRemoved(String controllerToken) {
|
|
||||||
LimeLog.info("Shield controller removed: " + controllerToken);
|
|
||||||
|
|
||||||
Integer deviceId = tokenToDeviceIdMap.remove(controllerToken);
|
|
||||||
if (deviceId != null) {
|
|
||||||
deviceIdToTokenMap.remove(deviceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public ShieldControllerExtensionsHandler(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
|
|
||||||
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
|
||||||
inputManager.registerInputDeviceListener(this, null);
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setClassName("com.nvidia.blakepairing", "com.nvidia.blakepairing.AccessoryService");
|
|
||||||
if (!context.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) {
|
|
||||||
LimeLog.info("com.nvidia.blakepairing.AccessoryService is not available on this device");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getControllerToken(InputDevice device) {
|
|
||||||
// Refresh device ID <-> token mappings if one of our devices was removed
|
|
||||||
if (needsRefresh.compareAndSet(true, false)) {
|
|
||||||
try {
|
|
||||||
LimeLog.info("Refreshing controller token mappings");
|
|
||||||
|
|
||||||
// We have to enumerate tokenToDeviceIdMap rather than deviceIdToTokenMap
|
|
||||||
// because we remove the deviceIdToTokenMap entry when the device goes away.
|
|
||||||
HashMap<String, Integer> newTokenToDeviceIdMap = new HashMap<>();
|
|
||||||
HashMap<Integer, String> newDeviceIdToTokenMap = new HashMap<>();
|
|
||||||
for (String existingToken : tokenToDeviceIdMap.keySet()) {
|
|
||||||
int deviceId = getInputDeviceId(existingToken);
|
|
||||||
if (deviceId != 0) {
|
|
||||||
newTokenToDeviceIdMap.put(existingToken, deviceId);
|
|
||||||
newDeviceIdToTokenMap.put(deviceId, existingToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenToDeviceIdMap.clear();
|
|
||||||
deviceIdToTokenMap.clear();
|
|
||||||
|
|
||||||
tokenToDeviceIdMap.putAll(newTokenToDeviceIdMap);
|
|
||||||
deviceIdToTokenMap.putAll(newDeviceIdToTokenMap);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceIdToTokenMap.get(device.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean rumble(InputDevice device, int lowFreqMotor, int highFreqMotor) {
|
|
||||||
String controllerToken = getControllerToken(device);
|
|
||||||
if (controllerToken != null) {
|
|
||||||
try {
|
|
||||||
return rumble(controllerToken, lowFreqMotor, highFreqMotor);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {
|
|
||||||
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
|
||||||
inputManager.unregisterInputDeviceListener(this);
|
|
||||||
|
|
||||||
tokenToDeviceIdMap.clear();
|
|
||||||
deviceIdToTokenMap.clear();
|
|
||||||
|
|
||||||
if (listenerId != 0) {
|
|
||||||
try {
|
|
||||||
unregisterListener(listenerId);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
listenerId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (binder != null) {
|
|
||||||
context.unbindService(serviceConnection);
|
|
||||||
binder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int registerListener() throws RemoteException {
|
|
||||||
if (binder == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parcel input = Parcel.obtain();
|
|
||||||
Parcel output = Parcel.obtain();
|
|
||||||
try {
|
|
||||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
|
||||||
input.writeStrongBinder(controllerListener);
|
|
||||||
|
|
||||||
binder.transact(20, input, output, 0);
|
|
||||||
|
|
||||||
output.readException();
|
|
||||||
return output.readInt();
|
|
||||||
} finally {
|
|
||||||
input.recycle();
|
|
||||||
output.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean unregisterListener(int listenerId) throws RemoteException {
|
|
||||||
if (binder == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parcel input = Parcel.obtain();
|
|
||||||
Parcel output = Parcel.obtain();
|
|
||||||
try {
|
|
||||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
|
||||||
input.writeInt(listenerId);
|
|
||||||
|
|
||||||
binder.transact(21, input, output, 0);
|
|
||||||
|
|
||||||
output.readException();
|
|
||||||
return output.readInt() != 0;
|
|
||||||
} finally {
|
|
||||||
input.recycle();
|
|
||||||
output.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getInputDeviceId(String controllerToken) throws RemoteException {
|
|
||||||
if (binder == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parcel input = Parcel.obtain();
|
|
||||||
Parcel output = Parcel.obtain();
|
|
||||||
try {
|
|
||||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
|
||||||
input.writeString(controllerToken);
|
|
||||||
|
|
||||||
binder.transact(13, input, output, 0);
|
|
||||||
|
|
||||||
output.readException();
|
|
||||||
return output.readInt();
|
|
||||||
} finally {
|
|
||||||
input.recycle();
|
|
||||||
output.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rumble duration maximum of 1 second
|
|
||||||
private boolean rumble(String controllerToken, int lowFreqMotor, int highFreqMotor) throws RemoteException {
|
|
||||||
if (binder == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parcel input = Parcel.obtain();
|
|
||||||
Parcel output = Parcel.obtain();
|
|
||||||
try {
|
|
||||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
|
||||||
input.writeString(controllerToken);
|
|
||||||
input.writeInt(lowFreqMotor);
|
|
||||||
input.writeInt(highFreqMotor);
|
|
||||||
|
|
||||||
binder.transact(18, input, output, 0);
|
|
||||||
|
|
||||||
output.readException();
|
|
||||||
return output.readInt() != 0;
|
|
||||||
} finally {
|
|
||||||
input.recycle();
|
|
||||||
output.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rumble duration maximum of 1.5 seconds
|
|
||||||
private boolean rumbleWithDuration(String controllerToken, int lowFreqMotor, int highFreqMotor, long durationMs) throws RemoteException {
|
|
||||||
if (binder == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parcel input = Parcel.obtain();
|
|
||||||
Parcel output = Parcel.obtain();
|
|
||||||
try {
|
|
||||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
|
||||||
input.writeString(controllerToken);
|
|
||||||
input.writeInt(lowFreqMotor);
|
|
||||||
input.writeInt(highFreqMotor);
|
|
||||||
input.writeLong(durationMs);
|
|
||||||
|
|
||||||
binder.transact(19, input, output, 0);
|
|
||||||
|
|
||||||
output.readException();
|
|
||||||
return output.readInt() != 0;
|
|
||||||
} finally {
|
|
||||||
input.recycle();
|
|
||||||
output.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputDeviceAdded(int deviceId) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputDeviceChanged(int deviceId) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputDeviceRemoved(int deviceId) {
|
|
||||||
// Remove the device ID to token mapping, but leave the token mapping to device ID
|
|
||||||
// mapping so we will re-enumerate it when we next try to rumble a controller.
|
|
||||||
if (deviceIdToTokenMap.remove(deviceId) != null) {
|
|
||||||
needsRefresh.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
|||||||
import com.limelight.nvstream.jni.MoonBridge;
|
import com.limelight.nvstream.jni.MoonBridge;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
@@ -106,7 +107,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
private boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps);
|
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps);
|
||||||
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
|
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
|
||||||
@@ -130,14 +132,21 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
// We'll ask the decoder what it can do for us at this resolution and see if our
|
// We'll ask the decoder what it can do for us at this resolution and see if our
|
||||||
// requested frame rate falls below or inside the range of achievable frame rates.
|
// requested frame rate falls below or inside the range of achievable frame rates.
|
||||||
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height);
|
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height);
|
||||||
return prefs.fps <= fpsRange.getUpper();
|
if (fpsRange != null) {
|
||||||
|
return prefs.fps <= fpsRange.getUpper();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-through to try the Android L API if there's no performance point data
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// Video size not supported at any frame rate
|
// Video size not supported at any frame rate
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// As a last resort, we will use areSizeAndRateSupported() which is explicitly NOT a
|
||||||
|
// performance metric, but it can work at least for the purpose of determining if
|
||||||
|
// the codec is going to die when given a stream with the specified settings.
|
||||||
|
return caps.areSizeAndRateSupported(prefs.width, prefs.height, prefs.fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
|
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
|
||||||
@@ -145,15 +154,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
MediaCodecInfo.VideoCapabilities avcCaps = avcDecoderInfo.getCapabilitiesForType("video/avc").getVideoCapabilities();
|
MediaCodecInfo.VideoCapabilities avcCaps = avcDecoderInfo.getCapabilitiesForType("video/avc").getVideoCapabilities();
|
||||||
MediaCodecInfo.VideoCapabilities hevcCaps = hevcDecoderInfo.getCapabilitiesForType("video/hevc").getVideoCapabilities();
|
MediaCodecInfo.VideoCapabilities hevcCaps = hevcDecoderInfo.getCapabilitiesForType("video/hevc").getVideoCapabilities();
|
||||||
|
|
||||||
Boolean avcCanMeetPP = decoderCanMeetPerformancePoint(avcCaps, prefs);
|
return !decoderCanMeetPerformancePoint(avcCaps, prefs) && decoderCanMeetPerformancePoint(hevcCaps, prefs);
|
||||||
Boolean hevcCanMeetPP = decoderCanMeetPerformancePoint(hevcCaps, prefs);
|
|
||||||
|
|
||||||
// If one or both codecs lack performance data, don't do anything
|
|
||||||
if (avcCanMeetPP == null || hevcCanMeetPP == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !avcCanMeetPP && hevcCanMeetPP;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// No performance data
|
// No performance data
|
||||||
|
|||||||
@@ -144,12 +144,16 @@ public class MediaCodecHelper {
|
|||||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||||
// whether the performance is good enough to use for streaming, but they're
|
// whether the performance is good enough to use for streaming, but they're
|
||||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
// fully accelerated HEVC pipeline. AFAIK, the only K1 devices with this
|
||||||
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
// partially accelerated HEVC decoder are the Shield Tablet and Xiaomi MiPad,
|
||||||
// check for it here. Since there are 2 models of Shield Tablet (possibly
|
// so I'll check for those here.
|
||||||
// more with LTE), I will also exclude pre-Oreo OSes since only Shield ATV
|
//
|
||||||
// got an Oreo update.
|
// In case there are some that I missed, I will also exclude pre-Oreo OSes since
|
||||||
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// only Shield ATV got an Oreo update and any newer Tegra devices will not ship
|
||||||
|
// with an old OS like Nougat.
|
||||||
|
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") &&
|
||||||
|
!Build.DEVICE.equalsIgnoreCase("mocha") &&
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
whitelistedHevcDecoders.add("omx.nvidia");
|
whitelistedHevcDecoders.add("omx.nvidia");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
@@ -171,6 +172,7 @@ public class NvHTTP {
|
|||||||
.hostnameVerifier(hv)
|
.hostnameVerifier(hv)
|
||||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||||
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||||
|
.proxy(Proxy.NO_PROXY)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
httpClientWithReadTimeout = httpClient.newBuilder()
|
httpClientWithReadTimeout = httpClient.newBuilder()
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.limelight.preferences;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
public class LanguagePreference extends ListPreference {
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LanguagePreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LanguagePreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onClick() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
try {
|
||||||
|
// Launch the Android native app locale settings page
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
|
||||||
|
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||||
|
intent.setData(Uri.parse("package:" + getContext().getPackageName()));
|
||||||
|
getContext().startActivity(intent, null);
|
||||||
|
return;
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
// App locale settings should be present on all Android 13 devices,
|
||||||
|
// but if not, we'll launch the old language chooser.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have native app locale settings, launch the normal dialog
|
||||||
|
super.onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -319,6 +319,12 @@ public class PreferenceConfiguration {
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void completeLanguagePreferenceMigration(Context context) {
|
||||||
|
// Put our language option back to default which tells us that we've already migrated it
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
prefs.edit().putString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
|
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
|
||||||
// This particular Shield TV firmware crashes when using HDR
|
// This particular Shield TV firmware crashes when using HDR
|
||||||
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
|
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
|
||||||
|
|||||||
@@ -80,16 +80,20 @@ public class StreamSettings extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
finish();
|
finish();
|
||||||
|
|
||||||
// Check for changes that require a UI reload to take effect
|
// Language changes are handled via configuration changes in Android 13+,
|
||||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
// so manual activity relaunching is no longer required.
|
||||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
// Restart the PC view to apply UI changes
|
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||||
Intent intent = new Intent(this, PcView.class);
|
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
// Restart the PC view to apply UI changes
|
||||||
startActivity(intent, null);
|
Intent intent = new Intent(this, PcView.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.limelight.ui;
|
package com.limelight.ui;
|
||||||
|
|
||||||
public interface GameGestures {
|
public interface GameGestures {
|
||||||
void showKeyboard();
|
void toggleKeyboard();
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.GameManager;
|
import android.app.GameManager;
|
||||||
import android.app.GameState;
|
import android.app.GameState;
|
||||||
|
import android.app.LocaleManager;
|
||||||
import android.app.UiModeManager;
|
import android.app.UiModeManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -11,6 +12,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Insets;
|
import android.graphics.Insets;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.LocaleList;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
@@ -64,21 +66,29 @@ public class UiHelper {
|
|||||||
{
|
{
|
||||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
// On Android 13, migrate this non-default language setting into the OS native API
|
||||||
// Some locales include both language and country which must be separated
|
LocaleManager localeManager = activity.getSystemService(LocaleManager.class);
|
||||||
// before calling the Locale constructor.
|
localeManager.setApplicationLocales(LocaleList.forLanguageTags(locale));
|
||||||
if (locale.contains("-"))
|
PreferenceConfiguration.completeLanguagePreferenceMigration(activity);
|
||||||
{
|
|
||||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
|
||||||
locale.substring(locale.indexOf('-') + 1));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
config.locale = new Locale(locale);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||||
|
|
||||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
// Some locales include both language and country which must be separated
|
||||||
|
// before calling the Locale constructor.
|
||||||
|
if (locale.contains("-"))
|
||||||
|
{
|
||||||
|
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||||
|
locale.substring(locale.indexOf('-') + 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config.locale = new Locale(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,7 +228,7 @@
|
|||||||
<string name="audioconf_stereo">Stéréo</string>
|
<string name="audioconf_stereo">Stéréo</string>
|
||||||
<string name="audioconf_51surround">Son surround 5.1</string>
|
<string name="audioconf_51surround">Son surround 5.1</string>
|
||||||
<string name="audioconf_71surround">Son surround 7.1</string>
|
<string name="audioconf_71surround">Son surround 7.1</string>
|
||||||
<string name="videoformat_hevcauto">Utiliser HEVC uniquement s\'il est stable</string>
|
<string name="videoformat_hevcauto">Automatique</string>
|
||||||
<string name="videoformat_hevcalways">Utilisez toujours HEVC (mais il peut planter)</string>
|
<string name="videoformat_hevcalways">Utilisez toujours HEVC (mais il peut planter)</string>
|
||||||
<string name="videoformat_hevcnever">N\'utilisez jamais HEVC</string>
|
<string name="videoformat_hevcnever">N\'utilisez jamais HEVC</string>
|
||||||
<string name="title_frame_pacing">Frame-pacing vidéo</string>
|
<string name="title_frame_pacing">Frame-pacing vidéo</string>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
<string name="audioconf_stereo">Stereo</string>
|
<string name="audioconf_stereo">Stereo</string>
|
||||||
<string name="audioconf_51surround">Surround 5.1</string>
|
<string name="audioconf_51surround">Surround 5.1</string>
|
||||||
<string name="audioconf_71surround">Surround 7.1</string>
|
<string name="audioconf_71surround">Surround 7.1</string>
|
||||||
<string name="videoformat_hevcauto">Usa HEVC solo se stabile</string>
|
<string name="videoformat_hevcauto">Automatico</string>
|
||||||
<string name="videoformat_hevcalways">Usa sempre HEVC (potrebbe essere instabile)</string>
|
<string name="videoformat_hevcalways">Usa sempre HEVC (potrebbe essere instabile)</string>
|
||||||
<string name="videoformat_hevcnever">Non usare mai HEVC</string>
|
<string name="videoformat_hevcnever">Non usare mai HEVC</string>
|
||||||
<string name="title_frame_pacing">Bilanciamento frame video</string>
|
<string name="title_frame_pacing">Bilanciamento frame video</string>
|
||||||
|
|||||||
@@ -61,4 +61,116 @@
|
|||||||
<string name="nettest_text_success">Ağınız Moonlight\'ı engelliyor gibi görünmüyor. Bağlanmakta hala sorun yaşıyorsanız, bilgisayarınızın güvenlik duvarı ayarlarını kontrol edin.
|
<string name="nettest_text_success">Ağınız Moonlight\'ı engelliyor gibi görünmüyor. Bağlanmakta hala sorun yaşıyorsanız, bilgisayarınızın güvenlik duvarı ayarlarını kontrol edin.
|
||||||
\n
|
\n
|
||||||
\nİnternet üzerinden yayın yapmaya çalışıyorsanız, Moonlight İnternet Barındırma Aracını bilgisayarınıza yükleyin ve bilgisayarınızın internet bağlantısını kontrol etmek için birlikte verilen İnternet Akış Test Cihazını çalıştırın.</string>
|
\nİnternet üzerinden yayın yapmaya çalışıyorsanız, Moonlight İnternet Barındırma Aracını bilgisayarınıza yükleyin ve bilgisayarınızın internet bağlantısını kontrol etmek için birlikte verilen İnternet Akış Test Cihazını çalıştırın.</string>
|
||||||
|
<string name="title_checkbox_multi_controller">Otomatik oyun kumandası varlığı algılama</string>
|
||||||
|
<string name="summary_checkbox_multi_controller">Bu seçeneğin işaretinin kaldırılması bir oyun kumandasının her zaman mevcut olmasını zorlar</string>
|
||||||
|
<string name="summary_checkbox_vibrate_fallback">Oyun kumandanız desteklemiyorsa, gürültüyü taklit etmek için cihazınızı titreştirir</string>
|
||||||
|
<string name="title_seekbar_deadzone">Analog çubuk ölü bölgesini ayarlama</string>
|
||||||
|
<string name="summary_checkbox_xb1_driver">Yerel Xbox oyun kumandası desteği olmayan cihazlar için yerleşik USB sürücüsünü etkinleştirir</string>
|
||||||
|
<string name="title_checkbox_mouse_emulation">Oyun kumandası üzerinden fare emülasyonu</string>
|
||||||
|
<string name="title_checkbox_mouse_nav_buttons">Geri ve ileri fare düğmelerini etkinleştirme</string>
|
||||||
|
<string name="category_on_screen_controls_settings">Ekran Kontrolleri Ayarları</string>
|
||||||
|
<string name="title_checkbox_show_onscreen_controls">Ekran kontrollerini göster</string>
|
||||||
|
<string name="error_manager_not_running">ComputerManager hizmeti çalışmıyor. Lütfen birkaç saniye bekleyin veya uygulamayı yeniden başlatın.</string>
|
||||||
|
<string name="error_unknown_host">Ana bilgisayar çözümlenemedi</string>
|
||||||
|
<string name="title_decoding_error">Video Kod Çözücü Çöktü</string>
|
||||||
|
<string name="message_decoding_reset">Cihazınızın video kod çözücüsü, seçtiğiniz akış ayarlarında çökmeye devam ediyor. Akış ayarlarınız varsayılana sıfırlandı.</string>
|
||||||
|
<string name="error_usb_prohibited">USB erişimi cihaz yöneticiniz tarafından yasaklanmıştır. Knox veya MDM ayarlarınızı kontrol edin.</string>
|
||||||
|
<string name="unable_to_pin_shortcut">Mevcut başlatıcınız sabitlenmiş kısayollar oluşturmaya izin vermiyor.</string>
|
||||||
|
<string name="video_decoder_init_failed">Video kod çözücü başlatılamadı. Cihazınız seçilen çözünürlüğü veya kare hızını desteklemiyor olabilir.</string>
|
||||||
|
<string name="no_video_received_error">Ana bilgisayardan video alınmadı.</string>
|
||||||
|
<string name="no_frame_received_error">Ağ bağlantınız iyi performans göstermiyor. Video bit hızı ayarınızı düşürün veya daha hızlı bir bağlantı deneyin.</string>
|
||||||
|
<string name="early_termination_error">Yayını başlatırken ana bilgisayarda bir şeyler ters gitti.
|
||||||
|
\n
|
||||||
|
\nAna bilgisayarınızda DRM korumalı herhangi bir içeriğin açık olmadığından emin olun. Ana bilgisayarınızı yeniden başlatmayı da deneyebilirsiniz.
|
||||||
|
\n
|
||||||
|
\nSorun devam ederse GPU sürücülerinizi ve GeForce Experience\'ı yeniden yüklemeyi deneyin.</string>
|
||||||
|
<string name="check_ports_msg">Bağlantı noktaları için güvenlik duvarınızı ve bağlantı noktası yönlendirme kurallarınızı kontrol edin:</string>
|
||||||
|
<string name="conn_establishing_title">Bağlantı kuruluyor</string>
|
||||||
|
<string name="conn_terminated_title">Bağlantı sonlandırıldı</string>
|
||||||
|
<string name="conn_terminated_msg">Bağlantı sonlandırıldı</string>
|
||||||
|
<string name="yes">Evet</string>
|
||||||
|
<string name="lost_connection">Bilgisayar ile bağlantı kesildi</string>
|
||||||
|
<string name="help">Yardım</string>
|
||||||
|
<string name="delete_pc_msg">Bu bilgisayarı silmek istediğinizden emin misiniz\?</string>
|
||||||
|
<string name="slow_connection_msg">Bilgisayara yavaş bağlantı
|
||||||
|
\nBit hızınızı düşürün</string>
|
||||||
|
<string name="poor_connection_msg">Bilgisayar ile zayıf bağlantı</string>
|
||||||
|
<string name="perf_overlay_streamdetails">Video akışı: %1$s %2$.2f FPS</string>
|
||||||
|
<string name="perf_overlay_decoder">Kod çözücü: %1$s</string>
|
||||||
|
<string name="perf_overlay_incomingfps">Ağdan gelen kare hızı: %1$.2f FPS</string>
|
||||||
|
<string name="perf_overlay_renderingfps">İşleme kare hızı: %1$.2f FPS</string>
|
||||||
|
<string name="perf_overlay_netdrops">Ağ bağlantınız tarafından düşen kare sayısı: %1$.2f%%</string>
|
||||||
|
<string name="perf_overlay_dectime">Ortalama kod çözme süresi: %1$.2f ms</string>
|
||||||
|
<string name="applist_menu_resume">Oturumu sürdür</string>
|
||||||
|
<string name="applist_menu_quit">Oturumu sonlandır</string>
|
||||||
|
<string name="applist_menu_quit_and_start">Mevcut oyundan çık ve başla</string>
|
||||||
|
<string name="applist_menu_cancel">İptal</string>
|
||||||
|
<string name="applist_menu_details">Detayları göster</string>
|
||||||
|
<string name="applist_menu_scut">Kısayol oluştur</string>
|
||||||
|
<string name="applist_menu_tv_channel">Kanala ekle</string>
|
||||||
|
<string name="applist_menu_hide_app">Uygulamayı gizle</string>
|
||||||
|
<string name="applist_refresh_title">Uygulama listesi</string>
|
||||||
|
<string name="applist_quit_success">Başarıyla çıkıldı</string>
|
||||||
|
<string name="applist_refresh_error_msg">Uygulama listesi alınamadı</string>
|
||||||
|
<string name="applist_quit_app">Çıkılıyor</string>
|
||||||
|
<string name="applist_quit_fail">Çıkılamadı</string>
|
||||||
|
<string name="applist_quit_confirmation">Çalışan uygulamadan çıkmak istediğinizden emin misiniz\? Kaydedilmemiş tüm veriler kaybolacaktır.</string>
|
||||||
|
<string name="applist_details_id">Uygulama kimliği:</string>
|
||||||
|
<string name="msg_add_pc">Bilgisayara bağlanıyor…</string>
|
||||||
|
<string name="addpc_success">Bilgisayar başarıyla eklendi</string>
|
||||||
|
<string name="addpc_fail">Belirtilen bilgisayara bağlanılamıyor. Gerekli bağlantı noktalarına güvenlik duvarı üzerinden izin verildiğinden emin olun.</string>
|
||||||
|
<string name="addpc_unknown_host">Bilgisayar adresi çözümlenemiyor. Adreste yazım hatası yapmadığınızdan emin olun.</string>
|
||||||
|
<string name="addpc_enter_ip">Bir IP adresi girmelisiniz</string>
|
||||||
|
<string name="category_basic_settings">Temel Ayarlar</string>
|
||||||
|
<string name="summary_resolution_list">Görüntü netliğini artırmak için artırın. Düşük uçlu cihazlarda ve daha yavaş ağlarda daha iyi performans için azaltın.</string>
|
||||||
|
<string name="title_native_res_dialog">Yerel çözünürlük uyarısı</string>
|
||||||
|
<string name="title_fps_list">Video kare hızı</string>
|
||||||
|
<string name="title_seekbar_bitrate">Video bit hızı</string>
|
||||||
|
<string name="summary_checkbox_touchscreen_trackpad">Etkinleştirilirse, dokunmatik ekran bir dokunmatik fare gibi davranır. Devre dışı bırakılırsa, dokunmatik ekran doğrudan fare imlecini kontrol eder.</string>
|
||||||
|
<string name="conn_establishing_msg">Bağlantı başlatılıyor</string>
|
||||||
|
<string name="conn_metered">Uyarı: Aktif ağ bağlantınız ölçülüdür!</string>
|
||||||
|
<string name="conn_client_latency">Ortalama kare kod çözme gecikmesi:</string>
|
||||||
|
<string name="conn_client_latency_hw">donanım kod çözücü gecikmesi:</string>
|
||||||
|
<string name="pcview_menu_send_wol">Yerel Ağda Uyandırma isteği gönder</string>
|
||||||
|
<string name="applist_refresh_msg">Uygulamalar yenileniyor…</string>
|
||||||
|
<string name="applist_refresh_error_title">Hata</string>
|
||||||
|
<string name="title_add_pc">Bilgisayarı manuel olarak Ekle</string>
|
||||||
|
<string name="title_resolution_list">Video çözünürlüğü</string>
|
||||||
|
<string name="summary_fps_list">Daha akıcı bir video akışı için artırın. Düşük uçlu cihazlarda daha iyi performans için azaltın.</string>
|
||||||
|
<string name="resolution_prefix_native">Yerel</string>
|
||||||
|
<string name="resolution_prefix_native_fullscreen">Yerel tam ekran</string>
|
||||||
|
<string name="category_audio_settings">Ses Ayarları</string>
|
||||||
|
<string name="title_audio_config_list">Çevresel ses yapılandırması</string>
|
||||||
|
<string name="summary_seekbar_deadzone">Not: Bazı oyunlar Moonlight\'ın kullanmak üzere yapılandırıldığından daha büyük bir ölü bölge uygulayabilir.</string>
|
||||||
|
<string name="suffix_seekbar_deadzone">%</string>
|
||||||
|
<string name="title_checkbox_xb1_driver">Xbox 360/One USB oyun kumandası sürücüsü</string>
|
||||||
|
<string name="title_checkbox_usb_bind_all">Yerel Xbox oyun kumandası desteğini geçersiz kılma</string>
|
||||||
|
<string name="summary_checkbox_mouse_emulation">Başlat düğmesine uzun süre basmak oyun kumandasını fare moduna geçirir</string>
|
||||||
|
<string name="summary_checkbox_mouse_nav_buttons">Bu seçeneği etkinleştirmek bazı hatalı cihazlarda sağ tıklamayı bozabilir</string>
|
||||||
|
<string name="title_checkbox_flip_face_buttons">Yüz düğmelerini çevirme</string>
|
||||||
|
<string name="title_checkbox_absolute_mouse_mode">Uzak masaüstü fare modu</string>
|
||||||
|
<string name="error_404">GFE bir HTTP 404 hatası bildirdi. Bilgisayarınızın desteklenen bir GPU çalıştırdığından emin olun. Uzak masaüstü yazılımı kullanmak da bu hataya neden olabilir. Cihazınızı yeniden başlatmayı veya GFE\'yi yeniden yüklemeyi deneyin.</string>
|
||||||
|
<string name="message_decoding_error">Moonlight, bu cihazın video kod çözücüsü ile uyumsuzluk nedeniyle çöktü. GeForce Experience\'ın bilgisayarınızdaki en son sürüme güncellendiğinden emin olun. Çökmeler devam ederse akış ayarlarını değiştirmeyi deneyin.</string>
|
||||||
|
<string name="conn_hardware_latency">Ortalama donanım kod çözme gecikmesi:</string>
|
||||||
|
<string name="title_decoding_reset">Video Ayarlarını Sıfırla</string>
|
||||||
|
<string name="searching_pc">GameStream\'in çalıştığı bilgisayarlar aranıyor...
|
||||||
|
\n
|
||||||
|
\n GeForce Experience SHIELD ayarlarında GameStream\'in etkinleştirildiğinden emin olun.</string>
|
||||||
|
<string name="applist_connect_msg">Bilgisayara bağlanıyor…</string>
|
||||||
|
<string name="no">Hayır</string>
|
||||||
|
<string name="perf_overlay_netlatency">Ortalama ağ gecikmesi: %1$d ms (varyans: %2$d ms)</string>
|
||||||
|
<string name="addpc_wrong_sitelocal">Bu adres doğru görünmüyor. İnternet üzerinden akış için yönlendiricinizin genel IP adresini kullanmanız gerekir.</string>
|
||||||
|
<string name="text_native_res_dialog">Yerel çözünürlük modları GeForce Experience tarafından resmi olarak desteklenmez, bu nedenle ana ekran çözünürlüğünüzü kendisi ayarlamaz. Oyun içindeyken manuel olarak ayarlamanız gerekecektir.
|
||||||
|
\n
|
||||||
|
\nCihazınızın çözünürlüğüne uyması için NVIDIA Denetim Masası\'nda özel bir çözünürlük oluşturmayı seçerseniz, lütfen NVIDIA\'nın olası monitör hasarı, bilgisayar kararsızlığı ve diğer olası sorunlarla ilgili uyarısını okuyup anladığınızdan emin olun.
|
||||||
|
\n
|
||||||
|
\nBilgisayarınızda özel bir çözünürlük oluşturulmasından kaynaklanan herhangi bir sorundan sorumlu değiliz.
|
||||||
|
\n
|
||||||
|
\nSon olarak, cihazınız veya bilgisayarınız doğal çözünürlükte akışı desteklemiyor olabilir. Eğer cihazınızda çalışmıyorsa, ne yazık ki şansınız yok demektir.</string>
|
||||||
|
<string name="summary_seekbar_bitrate">Daha iyi görüntü kalitesi için artırın. Daha yavaş bağlantılarda performansı artırmak için azaltın.</string>
|
||||||
|
<string name="title_checkbox_stretch_video">Videoyu tam ekrana genişlet</string>
|
||||||
|
<string name="title_checkbox_vibrate_fallback">Titreşim ile gürültü desteğini taklit etme</string>
|
||||||
|
<string name="summary_checkbox_usb_bind_all">Yerel Xbox oyun kumandası desteği mevcut olsa bile desteklenen tüm oyun kumandaları için Moonlight\'ın USB sürücüsünü kullan</string>
|
||||||
|
<string name="summary_checkbox_flip_face_buttons">Oyun kumandaları ve ekran kontrolleri için A/B ve X/Y yüz düğmelerini değiştirir</string>
|
||||||
|
<string name="summary_checkbox_absolute_mouse_mode">Bu fare hızlandırmanın uzak masaüstü kullanımı için daha doğal davranmasını sağlayabilir, ancak birçok oyunla uyumsuzdur.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<string name="title_checkbox_vibrate_fallback">Емуляція вібровіддачі</string>
|
<string name="title_checkbox_vibrate_fallback">Емуляція вібровіддачі</string>
|
||||||
<string name="summary_checkbox_vibrate_osc">Вібрація пристрою для емуляції вібровіддачі при екранному управлінні</string>
|
<string name="summary_checkbox_vibrate_osc">Вібрація пристрою для емуляції вібровіддачі при екранному управлінні</string>
|
||||||
<string name="summary_checkbox_vibrate_fallback">Вібрує пристрій для емуляції вібровіддачі, якщо під\'єднаний контролер не підтримує її</string>
|
<string name="summary_checkbox_vibrate_fallback">Вібрує пристрій для емуляції вібровіддачі, якщо під\'єднаний контролер не підтримує її</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="title_checkbox_flip_face_buttons">Перевернути ґудзики</string>
|
||||||
<string name="summary_checkbox_flip_face_buttons">Перемикає ґудзики A/B та X/Y для контролерів та екранних елементів керування</string>
|
<string name="summary_checkbox_flip_face_buttons">Перемикає ґудзики A/B та X/Y для контролерів та екранних елементів керування</string>
|
||||||
<string name="scut_pc_not_found">Пристрій не знайдено</string>
|
<string name="scut_pc_not_found">Пристрій не знайдено</string>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
<string name="applist_menu_quit_and_start">結束目前遊戲並開始這個遊戲</string>
|
<string name="applist_menu_quit_and_start">結束目前遊戲並開始這個遊戲</string>
|
||||||
<string name="applist_menu_cancel"> 取消 </string>
|
<string name="applist_menu_cancel"> 取消 </string>
|
||||||
<string name="applist_menu_details">檢視詳細資料</string>
|
<string name="applist_menu_details">檢視詳細資料</string>
|
||||||
<string name="applist_menu_scut">創建捷徑</string>
|
<string name="applist_menu_scut">建立捷徑</string>
|
||||||
<string name="applist_menu_tv_channel">新增至頻道</string>
|
<string name="applist_menu_tv_channel">新增至頻道</string>
|
||||||
<string name="applist_refresh_title">遊戲清單</string>
|
<string name="applist_refresh_title">遊戲清單</string>
|
||||||
<string name="applist_refresh_msg">正在重新整理…</string>
|
<string name="applist_refresh_msg">正在重新整理…</string>
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
<string name="audioconf_stereo">立體聲</string>
|
<string name="audioconf_stereo">立體聲</string>
|
||||||
<string name="audioconf_51surround">5.1 環場音效</string>
|
<string name="audioconf_51surround">5.1 環場音效</string>
|
||||||
<string name="audioconf_71surround">7.1 環場音效</string>
|
<string name="audioconf_71surround">7.1 環場音效</string>
|
||||||
<string name="videoformat_hevcauto">僅在穩定時使用 HEVC</string>
|
<string name="videoformat_hevcauto">自動</string>
|
||||||
<string name="videoformat_hevcalways">強制使用 HEVC (可能會當機)</string>
|
<string name="videoformat_hevcalways">強制使用 HEVC (可能會當機)</string>
|
||||||
<string name="videoformat_hevcnever">不使用 HEVC</string>
|
<string name="videoformat_hevcnever">不使用 HEVC</string>
|
||||||
<string name="resolution_4k">4K</string>
|
<string name="resolution_4k">4K</string>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<item>71</item>
|
<item>71</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- Don't forget to update locales_config.xml when you modify this! -->
|
||||||
<string-array name="language_names" translatable="false">
|
<string-array name="language_names" translatable="false">
|
||||||
<item>Default</item>
|
<item>Default</item>
|
||||||
<item>English</item>
|
<item>English</item>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- Don't forget to update arrays.xml when you modify this file! -->
|
||||||
|
<locale android:name="en"/>
|
||||||
|
<locale android:name="it"/>
|
||||||
|
<locale android:name="ja"/>
|
||||||
|
<locale android:name="ru"/>
|
||||||
|
<locale android:name="nl"/>
|
||||||
|
<locale android:name="zh-CN"/>
|
||||||
|
<locale android:name="zh-TW"/>
|
||||||
|
<locale android:name="ko"/>
|
||||||
|
<locale android:name="es"/>
|
||||||
|
<locale android:name="fr"/>
|
||||||
|
<locale android:name="de"/>
|
||||||
|
<locale android:name="ro"/>
|
||||||
|
<locale android:name="uk"/>
|
||||||
|
<locale android:name="nb-NO"/>
|
||||||
|
<locale android:name="vi"/>
|
||||||
|
<locale android:name="hu"/>
|
||||||
|
<locale android:name="el"/>
|
||||||
|
</locale-config>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
android:title="@string/title_checkbox_enable_pip"
|
android:title="@string/title_checkbox_enable_pip"
|
||||||
android:summary="@string/summary_checkbox_enable_pip"
|
android:summary="@string/summary_checkbox_enable_pip"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
<ListPreference
|
<com.limelight.preferences.LanguagePreference
|
||||||
android:key="list_languages"
|
android:key="list_languages"
|
||||||
android:title="@string/title_language_list"
|
android:title="@string/title_language_list"
|
||||||
android:entries="@array/language_names"
|
android:entries="@array/language_names"
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
- Implemented per-app language preferences on Android 13
|
||||||
|
- Improved automatic HEVC selection logic on older devices
|
||||||
|
- Fixed a few crashing bugs
|
||||||
|
- Updated community contributed translations from Weblate
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
- 3 finger tap can now dismiss the keyboard too
|
||||||
|
- Fixed crash on some Samsung devices when starting to stream
|
||||||
|
- Added meta key handling for DeX on newer Samsung devices
|
||||||
|
- Updated community contributed translations from Weblate
|
||||||
Reference in new issue
Block a user