Compare commits

...

58 Commits

Author SHA1 Message Date
Cameron Gutman
ab769a1606 Version 5.7.2 2018-03-07 18:53:38 -08:00
Nikita Glazkov
3ac9abbab1 Russian translation (#546)
* Fix russian translation

* Complete russian translation
2018-03-07 18:44:33 -08:00
Marco
288efd0726 Added new strings (#542) 2018-03-07 18:43:33 -08:00
Nikita Glazkov
d2d0ed65d6 Different app label for debug builds (#545)
* Different app label for debug builds

* Remove underscores from app labels
2018-03-07 18:41:34 -08:00
Cameron Gutman
e697ed72db Add missing <network-security-config> tag 2018-03-07 18:17:00 -08:00
Cameron Gutman
b657c746be Pass the BouncyCastle provider directly rather than by name, since the latter doesn't work on Android P (at least DP1) 2018-03-07 17:56:50 -08:00
Cameron Gutman
947f8db2d5 Update for Android Studio 3.1 2018-03-07 17:55:49 -08:00
Cameron Gutman
15857efd36 Add network security config allowing plaintext for Android P 2018-03-07 11:47:19 -08:00
Cameron Gutman
3fd0f20e10 Version 5.7.1 2018-03-01 22:18:32 -08:00
Cameron Gutman
a2e64fd7df Fix crash when running Dutch language. Fixes #543 2018-03-01 22:09:49 -08:00
Cameron Gutman
a620dc7d0c Version 5.7 2018-02-25 13:41:09 -08:00
Cameron Gutman
9d7a28e408 Implement deletion of OSC settings 2018-02-25 13:33:52 -08:00
Cameron Gutman
3244344fc7 Add preference dependencies for USB and OSC 2018-02-25 13:12:37 -08:00
Cameron Gutman
75057f2d39 Persist OSC configuration between launches 2018-02-25 13:07:07 -08:00
Cameron Gutman
bbec3402d9 Reduce opacity of OSC configuration button 2018-02-25 12:18:45 -08:00
Cameron Gutman
dcf4dac8dd Only add L3/R3 buttons for the L3/R3-only config, since the analog sticks work for this 2018-02-25 12:17:59 -08:00
Cameron Gutman
d98f484aaf Change OSC configuration button to work better on rounded screen devices 2018-02-25 12:12:23 -08:00
Cameron Gutman
0218a9ce14 Small string update 2018-02-24 21:07:18 -08:00
Cameron Gutman
0ec6dcd67e Add 360p option and change bitrate to kbps 2018-02-24 21:05:45 -08:00
Cameron Gutman
88f9b68db7 Add mouse emulation and bind all USB devices options 2018-02-24 20:17:14 -08:00
Cameron Gutman
3c2fd32d1e Improve error reporting for incorrect IP address 2018-02-24 19:36:23 -08:00
Cameron Gutman
6557cba307 Add support for scrolling with d-pad in mouse emulation mode 2018-02-24 18:57:09 -08:00
Cameron Gutman
ae6f797436 Handle right-clicks that are synthesized into back button presses 2018-02-19 17:29:18 -08:00
Cameron Gutman
3442a64f4d Update common to fix audio dropouts. Fixes #523 2018-02-19 01:21:13 -08:00
Cameron Gutman
37ddccde0c Version 5.6.7 2018-02-14 18:39:04 -08:00
Cameron Gutman
ffc59c6bd6 Update moonlight-common to fix pairing timeout issue 2018-02-14 18:34:45 -08:00
Cameron Gutman
88f84a0c12 Version 5.6.6 2018-02-10 17:21:36 -08:00
Cameron Gutman
03ecf3e5ac Fix crash on Knox devices with USB blocking policies 2018-02-10 16:56:01 -08:00
Cameron Gutman
617c8582b4 Fix crash on MediaTek PAL Android TVs 2018-02-10 16:42:45 -08:00
Cameron Gutman
ef3b28295b Update dependency versions 2018-02-05 18:38:23 -08:00
Cameron Gutman
3bcd2ee068 Ignore bogus refresh rates just to be on the safe side 2018-02-04 15:26:40 -08:00
Cameron Gutman
d4ff58b3ad Version 5.6.5 2018-02-04 12:59:52 -08:00
Cameron Gutman
c797318ece Use frame drop hack to reduce latency and micro-stuttering for now 2018-02-04 12:59:04 -08:00
Cameron Gutman
82387d23f8 Send client's display refresh rate to server for better frame pacing 2018-02-03 22:09:42 -08:00
Cameron Gutman
766e629be5 Use applicationId com.limelight.unofficial for release builds by default 2018-02-03 19:45:18 -08:00
Cameron Gutman
b93aa42c0c Fix detection on HEVC support on some buggy devices 2018-01-28 21:16:28 -08:00
Cameron Gutman
36f132942f Version 5.6.4 2018-01-20 15:13:17 -08:00
Cameron Gutman
e4c251e7ee Ignore NVIDIA mouse capture extension on root builds to avoid broken LineageOS implementation 2018-01-20 02:31:40 -08:00
Cameron Gutman
fb54bd5c78 Send the initial number of connected gamepads during launch to fix some games like L4D2 2018-01-20 01:16:25 -08:00
Cameron Gutman
8d4c86e113 Update common to support sending initial gamepads and fixing WoL 2018-01-20 01:11:39 -08:00
Cameron Gutman
7fafb8e0ff Revert extractNativeLibraries=false change due to install failure on Fire TV 3 2018-01-11 00:03:03 -08:00
Cameron Gutman
fbcbe09255 Version 5.6.3 2018-01-10 00:40:08 -08:00
Cameron Gutman
e336a4446a Update common to work with GFE 3.12 2018-01-10 00:38:15 -08:00
Cameron Gutman
ffb35b2cdd Use smaller packets for streaming at 1080p and below to attempt to mitigate some reported regressions with v5.6.2 2018-01-09 23:38:25 -08:00
Cameron Gutman
2d0af6281c Ensure polling threads terminate even when polling resumes immediately 2017-12-29 14:05:29 -08:00
Cameron Gutman
472a7f6c8a Version 5.6.2 2017-12-27 22:45:07 -08:00
Cameron Gutman
cd06559c66 Also count link-local addresses as local 2017-12-27 22:41:21 -08:00
Cameron Gutman
d833933aaa Allow up to 1 second for fast poll to address connection flakiness 2017-12-27 22:27:35 -08:00
Cameron Gutman
dc3495d59b Improve local vs. remote heuristics 2017-12-27 21:43:12 -08:00
Cameron Gutman
e3a2e40043 Shrink large box art down to the normal size by changing sample size 2017-12-27 21:28:38 -08:00
Cameron Gutman
31e1fb743e Update common to address some null PC name crashes 2017-12-27 20:36:07 -08:00
Cameron Gutman
bc59f11096 Disable RFI on b3_att_us 2017-12-27 20:34:02 -08:00
Cameron Gutman
6d97775aa9 Try disabling RFI if the previous run crashes 2017-12-27 20:32:34 -08:00
Cameron Gutman
3fff34e08a Don't extract native libraries for non-root build 2017-12-27 19:40:49 -08:00
Cameron Gutman
15e856dccb Move AudioTrack flush to cleanup() callback since all sample submission has ceased by then 2017-12-06 20:43:58 -08:00
Cameron Gutman
07d04171c3 Force HEVC enabled if HDR is requested 2017-12-05 17:38:25 -08:00
Cameron Gutman
42bd93cb3a Update common to fix mDNS race condition 2017-12-05 17:33:55 -08:00
Cameron Gutman
7d289f1134 Fix race conditions when frames are submitted after stop() has been called 2017-12-05 17:28:04 -08:00
47 changed files with 960 additions and 318 deletions

View File

@@ -1,18 +1,14 @@
import com.android.builder.model.ProductFlavor
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion '27.0.1'
defaultConfig {
minSdkVersion 16
targetSdkVersion 27
versionName "5.6.1"
versionCode = 139
versionName "5.7.2"
versionCode = 150
}
flavorDimensions "root"
@@ -54,6 +50,38 @@ android {
applicationIdSuffix ".debug"
}
release {
// To whomever is releasing/using an APK in release mode with
// Moonlight's official application ID, please stop. I see every
// single one of your crashes in my Play Console and it makes
// Moonlight's reliability look worse and makes it more difficult
// to distinguish real crashes from your crashy VR app. Seriously,
// 44 of the *same* native crash in 72 hours and a few each of
// several other crashes.
//
// This is technically not your fault. I would have hoped Google
// would validate the signature of the APK before attributing
// the crash to it. I asked their Play Store support about this
// and they said they don't and don't have plans to, so that sucks.
//
// In any case, it's bad form to release an APK using someone
// else's application ID. There is no legitimate reason, that
// anyone would need to comment out the following line, except me
// when I release an official signed Moonlight build. If you feel
// like doing so would solve something, I can tell you it will not.
// You can't upgrade an app while retaining data without having the
// same signature as the official version. Nor can you post it on
// the Play Store, since that application ID is already taken.
// Reputable APK hosting websites similarly validate the signature
// is consistent with the Play Store and won't allow an APK that
// isn't signed the same as the original.
//
// I wish any and all people using Moonlight as the basis of other
// cool projects the best of luck with their efforts. All I ask
// is to please change the applicationId before you publish.
//
// TL;DR: Leave the following line alone!
applicationIdSuffix ".unofficial"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
@@ -67,9 +95,9 @@ android {
}
dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.57'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.57'
implementation files('libs/jcodec-0.1.9-patched.jar')
implementation 'org.bouncycastle:bcprov-jdk15on:1.59'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.59'
implementation 'org.jcodec:jcodec:0.2.3'
implementation project(':moonlight-common')
}

Binary file not shown.

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_label" translatable="false">Moonlight (Debug)</string>
<string name="app_label_root" translatable="false">Moonlight (Root Debug)</string>
</resources>

View File

@@ -32,6 +32,7 @@
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules"
android:networkSecurityConfig="@xml/network_security_config"
android:isGame="true"
android:banner="@drawable/atv_banner"
android:appCategory="game"

View File

@@ -294,19 +294,72 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
Toast.makeText(this, "No H.265 decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show();
}
int gamepadMask = ControllerHandler.getAttachedControllerMask(this);
if (!prefConfig.multiController && gamepadMask != 0) {
// If any gamepads are present in non-MC mode, set only gamepad 1.
gamepadMask = 1;
}
if (prefConfig.onscreenController) {
// If we're using OSC, always set at least gamepad 1.
gamepadMask |= 1;
}
// Set to the optimal mode for streaming
float displayRefreshRate = prepareDisplayForRendering();
LimeLog.info("Display refresh rate: "+displayRefreshRate);
// HACK: Despite many efforts to ensure low latency consistent frame
// delivery, the best non-lossy mechanism is to buffer 1 extra frame
// in the output pipeline. Android does some buffering on its end
// in SurfaceFlinger and it's difficult (impossible?) to inspect
// the precise state of the buffer queue to the screen after we
// release a frame for rendering.
//
// Since buffering a frame adds latency and we are primarily a
// latency-optimized client, rather than one designed for picture-perfect
// accuracy, we will synthetically induce a negative pressure on the display
// output pipeline by driving the decoder input pipeline under the speed
// that the display can refresh. This ensures a constant negative pressure
// to keep latency down but does induce a periodic frame loss. However, this
// periodic frame loss is *way* less than what we'd already get in Marshmallow's
// display pipeline where frames are dropped outside of our control if they land
// on the same V-sync.
//
// Hopefully, we can get rid of this once someone comes up with a better way
// to track the state of the pipeline and time frames.
int roundedRefreshRate = Math.round(displayRefreshRate);
if (!prefConfig.disableFrameDrop && prefConfig.fps >= roundedRefreshRate) {
if (roundedRefreshRate <= 49) {
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
decoderRenderer.enableLegacyFrameDropRendering();
LimeLog.info("Bogus refresh rate: "+roundedRefreshRate);
}
// HACK: Avoid crashing on some MTK devices
else if (roundedRefreshRate == 50 && decoderRenderer.is49FpsBlacklisted()) {
// Use the old rendering strategy on these broken devices
decoderRenderer.enableLegacyFrameDropRendering();
}
else {
prefConfig.fps = roundedRefreshRate - 1;
LimeLog.info("Adjusting FPS target for screen to "+prefConfig.fps);
}
}
StreamConfiguration config = new StreamConfiguration.Builder()
.setResolution(prefConfig.width, prefConfig.height)
.setRefreshRate(prefConfig.fps)
.setApp(new NvApp(appName, appId, willStreamHdr))
.setBitrate(prefConfig.bitrate * 1000)
.setBitrate(prefConfig.bitrate)
.setEnableSops(prefConfig.enableSops)
.enableLocalAudioPlayback(prefConfig.playHostAudio)
.setMaxPacketSize(remote ? 1024 : 1292)
.setMaxPacketSize((remote || prefConfig.width <= 1920) ? 1024 : 1292)
.setRemote(remote)
.setHevcBitratePercentageMultiplier(75)
.setHevcSupported(decoderRenderer.isHevcSupported())
.setEnableHdr(willStreamHdr)
.setAttachedGamepadMask(gamepadMask)
.setClientRefreshRateX100((int)(displayRefreshRate * 100))
.setAudioConfiguration(prefConfig.enable51Surround ?
MoonBridge.AUDIO_CONFIGURATION_51_SURROUND :
MoonBridge.AUDIO_CONFIGURATION_STEREO)
@@ -314,14 +367,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize the connection
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this));
controllerHandler = new ControllerHandler(this, conn, this, prefConfig.multiController, prefConfig.deadzonePercentage);
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
inputManager.registerInputDeviceListener(controllerHandler, null);
// Set to the optimal mode for streaming
prepareDisplayForRendering();
// Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
touchContextMap[i] = new TouchContext(conn, i,
@@ -396,9 +446,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
private void prepareDisplayForRendering() {
private float prepareDisplayForRendering() {
Display display = getWindowManager().getDefaultDisplay();
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
float displayRefreshRate;
// On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -436,6 +487,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
displayRefreshRate = bestMode.getRefreshRate();
}
// On L, we can at least tell the OS that we want 60 Hz
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -448,6 +500,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
LimeLog.info("Selected refresh rate: "+bestRefreshRate);
windowLayoutParams.preferredRefreshRate = bestRefreshRate;
displayRefreshRate = bestRefreshRate;
}
else {
// Otherwise, the active display refresh rate is just
// whatever is currently in use.
displayRefreshRate = display.getRefreshRate();
}
// Apply the display mode change
@@ -483,6 +541,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Set the surface to scale based on the aspect ratio of the stream
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
}
return displayRefreshRate;
}
@SuppressLint("InlinedApi")
@@ -724,6 +784,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return super.onKeyDown(keyCode, event);
}
// Handle a synthetic back button event that some Android OS versions
// create as a result of a right-click.
if (event.getSource() == InputDevice.SOURCE_MOUSE && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
return true;
}
boolean handled = false;
boolean detectedGamepad = event.getDevice() != null && ((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK ||
@@ -766,6 +833,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return super.onKeyUp(keyCode, event);
}
// Handle a synthetic back button event that some Android OS versions
// create as a result of a right-click.
if (event.getSource() == InputDevice.SOURCE_MOUSE && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
return true;
}
boolean handled = false;
boolean detectedGamepad = event.getDevice() != null && ((event.getDevice().getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK ||
(event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD);

View File

@@ -176,14 +176,14 @@ public class AndroidAudioRenderer implements AudioRenderer {
public void start() {}
@Override
public void stop() {
// Immediately drop all pending data
track.pause();
track.flush();
}
public void stop() {}
@Override
public void cleanup() {
// Immediately drop all pending data
track.pause();
track.flush();
track.release();
}
}

View File

@@ -13,8 +13,8 @@ import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -54,10 +54,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
private static final Object globalCryptoLock = new Object();
static {
// Install the Bouncy Castle provider
Security.addProvider(new BouncyCastleProvider());
}
private static final Provider bcProvider = new BouncyCastleProvider();
public AndroidCryptoProvider(Context c) {
String dataPath = c.getFilesDir().getAbsolutePath();
@@ -96,10 +93,10 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
}
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", bcProvider);
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
pemCertBytes = certBytes;
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
KeyFactory keyFactory = KeyFactory.getInstance("RSA", bcProvider);
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
} catch (CertificateException e) {
// May happen if the cert is corrupt
@@ -113,10 +110,6 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
// May happen if the key is corrupt
LimeLog.warning("Corrupted key");
return false;
} catch (NoSuchProviderException e) {
// Should never happen
e.printStackTrace();
return false;
}
return true;
@@ -129,17 +122,13 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", bcProvider);
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e1) {
// Should never happen
e1.printStackTrace();
return false;
} catch (NoSuchProviderException e) {
// Should never happen
e.printStackTrace();
return false;
}
Date now = new Date();
@@ -160,8 +149,8 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
try {
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(bcProvider).build(keyPair.getPrivate());
cert = new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) {
// Nothing should go wrong here

View File

@@ -2,6 +2,8 @@ package com.limelight.binding.input;
import android.content.Context;
import android.hardware.input.InputManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.SystemClock;
import android.util.SparseArray;
import android.view.InputDevice;
@@ -12,9 +14,11 @@ import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.binding.input.driver.UsbDriverListener;
import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.nvstream.input.MouseButtonPacket;
import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.GameGestures;
import com.limelight.utils.Vector2d;
@@ -47,18 +51,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final GameGestures gestures;
private boolean hasGameController;
private final boolean multiControllerEnabled;
private short currentControllers;
private final PreferenceConfiguration prefConfig;
private short currentControllers, initialControllers;
public ControllerHandler(Context activityContext, NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
public ControllerHandler(Context activityContext, NvConnection conn, GameGestures gestures, PreferenceConfiguration prefConfig) {
this.activityContext = activityContext;
this.conn = conn;
this.gestures = gestures;
this.multiControllerEnabled = multiControllerEnabled;
this.prefConfig = prefConfig;
// HACK: For now we're hardcoding a 10% deadzone. Some deadzone
// is required for controller batching support to work.
deadzonePercentage = 10;
int deadzonePercentage = 10;
int[] ids = InputDevice.getDeviceIds();
for (int id : ids) {
@@ -102,6 +106,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// consume these. Instead, let's ignore them since that's probably the
// most likely case.
defaultContext.ignoreBack = true;
// Get the initially attached set of gamepads. As each gamepad receives
// its initial InputEvent, we will move these from this set onto the
// currentControllers set which will allow them to properly unplug
// if they are removed.
initialControllers = getAttachedControllerMask(activityContext);
}
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
@@ -139,8 +149,51 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
onInputDeviceAdded(deviceId);
}
public static short getAttachedControllerMask(Context context) {
int count = 0;
short mask = 0;
// Count all input devices that are gamepads
InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
for (int id : im.getInputDeviceIds()) {
InputDevice dev = im.getInputDevice(id);
if (dev == null) {
continue;
}
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0) {
LimeLog.info("Counting InputDevice: "+dev.getName());
mask |= 1 << count++;
}
}
// Count all USB devices that match our drivers
if (PreferenceConfiguration.readPreferences(context).usbDriver) {
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
for (UsbDevice dev : usbManager.getDeviceList().values()) {
// We explicitly ask not to claim devices that appear as InputDevices
// otherwise we will double count them.
if (UsbDriverService.shouldClaimDevice(dev, false)) {
LimeLog.info("Counting UsbDevice: "+dev.getDeviceName());
mask |= 1 << count++;
}
}
}
LimeLog.info("Enumerated "+count+" gamepads");
return mask;
}
private void releaseControllerNumber(GenericControllerContext context) {
// If this device sent data as a gamepad, zero the values before removing
// If we reserved a controller number, remove that reservation
if (context.reservedControllerNumber) {
LimeLog.info("Controller number "+context.controllerNumber+" is now available");
currentControllers &= ~(1 << context.controllerNumber);
}
// If this device sent data as a gamepad, zero the values before removing.
// We must do this after clearing the currentControllers entry so this
// causes the device to be removed on the server PC.
if (context.assignedControllerNumber) {
conn.sendControllerInput(context.controllerNumber, getActiveControllerMask(),
(short) 0,
@@ -148,12 +201,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
(short) 0, (short) 0,
(short) 0, (short) 0);
}
// If we reserved a controller number, remove that reservation
if (context.reservedControllerNumber) {
LimeLog.info("Controller number "+context.controllerNumber+" is now available");
currentControllers &= ~(1 << context.controllerNumber);
}
}
// Called before sending input but after we've determined that this
@@ -173,7 +220,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
LimeLog.info("Built-in buttons hardcoded as controller 0");
context.controllerNumber = 0;
}
else if (multiControllerEnabled && devContext.hasJoystickAxes) {
else if (prefConfig.multiController && devContext.hasJoystickAxes) {
context.controllerNumber = 0;
LimeLog.info("Reserving the next available controller number");
@@ -181,6 +228,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if ((currentControllers & (1 << i)) == 0) {
// Found an unused controller value
currentControllers |= (1 << i);
// Take this value out of the initial gamepad set
initialControllers &= ~(1 << i);
context.controllerNumber = i;
context.reservedControllerNumber = true;
break;
@@ -193,7 +244,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
else {
if (multiControllerEnabled) {
if (prefConfig.multiController) {
context.controllerNumber = 0;
LimeLog.info("Reserving the next available controller number");
@@ -201,6 +252,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if ((currentControllers & (1 << i)) == 0) {
// Found an unused controller value
currentControllers |= (1 << i);
// Take this value out of the initial gamepad set
initialControllers &= ~(1 << i);
context.controllerNumber = i;
context.reservedControllerNumber = true;
break;
@@ -472,8 +527,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
private short getActiveControllerMask() {
if (multiControllerEnabled) {
return currentControllers;
if (prefConfig.multiController) {
return (short)(currentControllers | initialControllers);
}
else {
// Only Player 1 is active with multi-controller disabled
@@ -559,6 +614,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
}
if ((changedMask & ControllerPacket.UP_FLAG) != 0) {
if ((inputMap & ControllerPacket.UP_FLAG) != 0) {
conn.sendMouseScroll((byte) 1);
}
}
if ((changedMask & ControllerPacket.DOWN_FLAG) != 0) {
if ((inputMap & ControllerPacket.DOWN_FLAG) != 0) {
conn.sendMouseScroll((byte) -1);
}
}
conn.sendControllerInput(controllerNumber, getActiveControllerMask(),
(short)0, (byte)0, (byte)0, (short)0, (short)0, (short)0, (short)0);
@@ -925,7 +990,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Make sure it's real by checking that the key is actually down before taking
// any action.
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS) {
SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_MOUSE_MODE_MS &&
prefConfig.mouseEmulation) {
toggleMouseEmulation(context);
}
context.inputMap &= ~ControllerPacket.PLAY_FLAG;

View File

@@ -3,6 +3,7 @@ package com.limelight.binding.input.capture;
import android.app.Activity;
import com.limelight.LimeLog;
import com.limelight.LimelightBuildProps;
import com.limelight.R;
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
import com.limelight.binding.input.evdev.EvdevListener;
@@ -13,7 +14,9 @@ public class InputCaptureManager {
LimeLog.info("Using Android O+ native mouse capture");
return new AndroidNativePointerCaptureProvider(activity.findViewById(R.id.surfaceView));
}
else if (ShieldCaptureProvider.isCaptureProviderSupported()) {
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}

View File

@@ -14,8 +14,11 @@ import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.view.InputDevice;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.R;
import com.limelight.preferences.PreferenceConfiguration;
import java.util.ArrayList;
@@ -25,6 +28,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
"com.limelight.USB_PERMISSION";
private UsbManager usbManager;
private PreferenceConfiguration prefConfig;
private final UsbEventReceiver receiver = new UsbEventReceiver();
private final UsbDriverBinder binder = new UsbDriverBinder();
@@ -117,11 +121,21 @@ public class UsbDriverService extends Service implements UsbDriverListener {
private void handleUsbDeviceState(UsbDevice device) {
// Are we able to operate it?
if (shouldClaimDevice(device)) {
if (shouldClaimDevice(device, prefConfig.bindAllUsb)) {
// Do we have permission yet?
if (!usbManager.hasPermission(device)) {
// Let's ask for permission
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
try {
// This function is not documented as throwing any exceptions (denying access
// is indicated by calling the PendingIntent with a false result). However,
// Samsung Knox has some policies which block this request, but rather than
// just returning a false result or returning 0 enumerated devices,
// they throw an undocumented SecurityException from this call, crashing
// the whole app. :(
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
} catch (SecurityException e) {
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
}
return;
}
@@ -157,7 +171,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
}
}
private boolean isRecognizedInputDevice(UsbDevice device) {
private static boolean isRecognizedInputDevice(UsbDevice device) {
// On KitKat and later, we can determine if this VID and PID combo
// matches an existing input device and defer to the built-in controller
// support in that case. Prior to KitKat, we'll always return true to be safe.
@@ -182,16 +196,17 @@ public class UsbDriverService extends Service implements UsbDriverListener {
}
}
private boolean shouldClaimDevice(UsbDevice device) {
public static boolean shouldClaimDevice(UsbDevice device, boolean claimAllAvailable) {
// We always bind to XB1 controllers but only bind to XB360 controllers
// if we know the kernel isn't already driving this device.
return XboxOneController.canClaimDevice(device) ||
(!isRecognizedInputDevice(device) && Xbox360Controller.canClaimDevice(device));
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
}
@Override
public void onCreate() {
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
this.prefConfig = PreferenceConfiguration.readPreferences(this);
// Register for USB attach broadcasts and permission completions
IntentFilter filter = new IntentFilter();
@@ -201,7 +216,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
// Enumerate existing devices
for (UsbDevice dev : usbManager.getDeviceList().values()) {
if (shouldClaimDevice(dev)) {
if (shouldClaimDevice(dev, prefConfig.bindAllUsb)) {
// Start the process of claiming this device
handleUsbDeviceState(dev);
}

View File

@@ -164,8 +164,8 @@ public class AnalogStick extends VirtualControllerElement {
}
}
public AnalogStick(VirtualController controller, Context context) {
super(controller, context);
public AnalogStick(VirtualController controller, Context context, int elementId) {
super(controller, context, elementId);
// reset stick position
position_stick_x = getWidth() / 2;
position_stick_y = getHeight() / 2;

View File

@@ -120,8 +120,8 @@ public class DigitalButton extends VirtualControllerElement {
}
}
public DigitalButton(VirtualController controller, int layer, Context context) {
super(controller, context);
public DigitalButton(VirtualController controller, int elementId, int layer, Context context) {
super(controller, context, elementId);
this.layer = layer;
}

View File

@@ -27,7 +27,7 @@ public class DigitalPad extends VirtualControllerElement {
private final Paint paint = new Paint();
public DigitalPad(VirtualController controller, Context context) {
super(controller, context);
super(controller, context, EID_DPAD);
}
public void addDigitalPadListener(DigitalPadListener listener) {

View File

@@ -10,7 +10,7 @@ import com.limelight.nvstream.input.ControllerPacket;
public class LeftAnalogStick extends AnalogStick {
public LeftAnalogStick(final VirtualController controller, final Context context) {
super(controller, context);
super(controller, context, EID_LS);
addAnalogStickListener(new AnalogStick.AnalogStickListener() {
@Override

View File

@@ -8,7 +8,7 @@ import android.content.Context;
public class LeftTrigger extends DigitalButton {
public LeftTrigger(final VirtualController controller, final int layer, final Context context) {
super(controller, layer, context);
super(controller, EID_LT, layer, context);
addDigitalButtonListener(new DigitalButton.DigitalButtonListener() {
@Override
public void onClick() {

View File

@@ -10,7 +10,7 @@ import com.limelight.nvstream.input.ControllerPacket;
public class RightAnalogStick extends AnalogStick {
public RightAnalogStick(final VirtualController controller, final Context context) {
super(controller, context);
super(controller, context, EID_RS);
addAnalogStickListener(new AnalogStick.AnalogStickListener() {
@Override

View File

@@ -8,7 +8,7 @@ import android.content.Context;
public class RightTrigger extends DigitalButton {
public RightTrigger(final VirtualController controller, final int layer, final Context context) {
super(controller, layer, context);
super(controller, EID_RT, layer, context);
addDigitalButtonListener(new DigitalButton.DigitalButtonListener() {
@Override
public void onClick() {

View File

@@ -45,7 +45,6 @@ public class VirtualController {
ControllerMode currentMode = ControllerMode.Active;
ControllerInputContext inputContext = new ControllerInputContext();
private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null;
private Button buttonConfigure = null;
private List<VirtualControllerElement> elements = new ArrayList<>();
@@ -60,6 +59,7 @@ public class VirtualController {
frame_layout.addView(relative_layout);
buttonConfigure = new Button(context);
buttonConfigure.setAlpha(0.25f);
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
buttonConfigure.setOnClickListener(new View.OnClickListener() {
@Override
@@ -68,6 +68,7 @@ public class VirtualController {
if (currentMode == ControllerMode.Configuration) {
currentMode = ControllerMode.Active;
VirtualControllerConfigurationLoader.saveProfile(VirtualController.this, context);
message = "Exiting configuration mode";
} else {
currentMode = ControllerMode.Configuration;
@@ -116,11 +117,19 @@ public class VirtualController {
DisplayMetrics screen = context.getResources().getDisplayMetrics();
int buttonSize = (int)(screen.heightPixels*0.05f);
layoutParamsButtonConfigure = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
relative_layout.addView(buttonConfigure, layoutParamsButtonConfigure);
int buttonSize = (int)(screen.heightPixels*0.06f);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
params.leftMargin = 15;
params.topMargin = 15;
relative_layout.addView(buttonConfigure, params);
// Start with the default layout
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
// Apply user preferences onto the default layout
VirtualControllerConfigurationLoader.loadFromPreferences(this, context);
}
public ControllerMode getControllerMode() {

View File

@@ -4,14 +4,19 @@
package com.limelight.binding.input.virtual_controller;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.DisplayMetrics;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.preferences.PreferenceConfiguration;
import org.json.JSONException;
import org.json.JSONObject;
public class VirtualControllerConfigurationLoader {
private static final String PROFILE_PATH = "profiles";
public static final String OSC_PREFERENCE = "OSC";
private static int getPercent(
int percent,
@@ -59,6 +64,7 @@ public class VirtualControllerConfigurationLoader {
}
private static DigitalButton createDigitalButton(
final int elementId,
final int keyShort,
final int keyLong,
final int layer,
@@ -66,7 +72,7 @@ public class VirtualControllerConfigurationLoader {
final int icon,
final VirtualController controller,
final Context context) {
DigitalButton button = new DigitalButton(controller, layer, context);
DigitalButton button = new DigitalButton(controller, elementId, layer, context);
button.setText(text);
button.setIcon(icon);
@@ -162,6 +168,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_A,
ControllerPacket.A_FLAG, 0, 1, "A", -1, controller, context),
getPercent(BUTTON_BASE_X, screen.widthPixels) + getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_BASE_Y, screen.heightPixels) + 2 * getPercent(BUTTON_HEIGHT, screen.heightPixels),
@@ -170,6 +177,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_B,
ControllerPacket.B_FLAG, 0, 1, "B", -1, controller, context),
getPercent(BUTTON_BASE_X, screen.widthPixels) + 2 * getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_BASE_Y, screen.heightPixels) + getPercent(BUTTON_HEIGHT, screen.heightPixels),
@@ -178,6 +186,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_X,
ControllerPacket.X_FLAG, 0, 1, "X", -1, controller, context),
getPercent(BUTTON_BASE_X, screen.widthPixels),
getPercent(BUTTON_BASE_Y, screen.heightPixels) + getPercent(BUTTON_HEIGHT, screen.heightPixels),
@@ -186,6 +195,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_Y,
ControllerPacket.Y_FLAG, 0, 1, "Y", -1, controller, context),
getPercent(BUTTON_BASE_X, screen.widthPixels) + getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_BASE_Y, screen.heightPixels),
@@ -210,6 +220,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_LB,
ControllerPacket.LB_FLAG, 0, 1, "LB", -1, controller, context),
getPercent(BUTTON_BASE_X, screen.widthPixels),
getPercent(BUTTON_BASE_Y, screen.heightPixels) + 2 * getPercent(BUTTON_HEIGHT, screen.heightPixels),
@@ -218,6 +229,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_RB,
ControllerPacket.RB_FLAG, 0, 1, "RB", -1, controller, context),
getPercent(BUTTON_BASE_X, screen.widthPixels) + 2 * getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_BASE_Y, screen.heightPixels) + 2 * getPercent(BUTTON_HEIGHT, screen.heightPixels),
@@ -240,6 +252,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_BACK,
ControllerPacket.BACK_FLAG, 0, 2, "BACK", -1, controller, context),
getPercent(40, screen.widthPixels),
getPercent(90, screen.heightPixels),
@@ -248,6 +261,7 @@ public class VirtualControllerConfigurationLoader {
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_START,
ControllerPacket.PLAY_FLAG, 0, 3, "START", -1, controller, context),
getPercent(40, screen.widthPixels) + getPercent(10, screen.widthPixels),
getPercent(90, screen.heightPixels),
@@ -255,52 +269,60 @@ public class VirtualControllerConfigurationLoader {
getPercent(10, screen.heightPixels)
);
}
else {
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_LSB,
ControllerPacket.LS_CLK_FLAG, 0, 1, "L3", -1, controller, context),
getPercent(2, screen.widthPixels),
getPercent(80, screen.heightPixels),
getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_HEIGHT, screen.heightPixels)
);
controller.addElement(createDigitalButton(
ControllerPacket.LS_CLK_FLAG, 0, 1, "L3", -1, controller, context),
getPercent(2, screen.widthPixels),
getPercent(80, screen.heightPixels),
getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_HEIGHT, screen.heightPixels)
);
controller.addElement(createDigitalButton(
ControllerPacket.RS_CLK_FLAG, 0, 1, "R3", -1, controller, context),
getPercent(89, screen.widthPixels),
getPercent(80, screen.heightPixels),
getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_HEIGHT, screen.heightPixels)
);
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_RSB,
ControllerPacket.RS_CLK_FLAG, 0, 1, "R3", -1, controller, context),
getPercent(89, screen.widthPixels),
getPercent(80, screen.heightPixels),
getPercent(BUTTON_WIDTH, screen.widthPixels),
getPercent(BUTTON_HEIGHT, screen.heightPixels)
);
}
}
/*
NOT IMPLEMENTED YET,
this should later be used to store and load a profile for the virtual controller
public static void saveProfile(final String name,
final VirtualController controller,
public static void saveProfile(final VirtualController controller,
final Context context) {
SharedPreferences.Editor prefEditor = context.getSharedPreferences(OSC_PREFERENCE, Activity.MODE_PRIVATE).edit();
SharedPreferences preferences = context.getSharedPreferences(PROFILE_PATH + "/" +
name, Activity.MODE_PRIVATE);
JSONArray elementConfigurations = new JSONArray();
for (VirtualControllerElement element : controller.getElements()) {
JSONObject elementConfiguration = new JSONObject();
String prefKey = ""+element.elementId;
try {
elementConfiguration.put("TYPE", element.getClass().getName());
elementConfiguration.put("CONFIGURATION", element.getConfiguration());
elementConfigurations.put(elementConfiguration);
} catch (Exception e) {
prefEditor.putString(prefKey, element.getConfiguration().toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
SharedPreferences.Editor editor= preferences.edit();
editor.putString("ELEMENTS", elementConfigurations.toString());
prefEditor.apply();
}
public static void loadFromPreferences(final VirtualController controller) {
public static void loadFromPreferences(final VirtualController controller, final Context context) {
SharedPreferences pref = context.getSharedPreferences(OSC_PREFERENCE, Activity.MODE_PRIVATE);
for (VirtualControllerElement element : controller.getElements()) {
String prefKey = ""+element.elementId;
String jsonConfig = pref.getString(prefKey, null);
if (jsonConfig != null) {
try {
element.loadConfiguration(new JSONObject(jsonConfig));
} catch (JSONException e) {
e.printStackTrace();
// Remove the corrupt element from the preferences
pref.edit().remove(prefKey).apply();
}
}
}
}
*/
}

View File

@@ -14,10 +14,30 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import org.json.JSONException;
import org.json.JSONObject;
public abstract class VirtualControllerElement extends View {
protected static boolean _PRINT_DEBUG_INFORMATION = false;
public static final int EID_DPAD = 1;
public static final int EID_LT = 2;
public static final int EID_RT = 3;
public static final int EID_LB = 4;
public static final int EID_RB = 5;
public static final int EID_A = 6;
public static final int EID_B = 7;
public static final int EID_X = 8;
public static final int EID_Y = 9;
public static final int EID_BACK = 10;
public static final int EID_START = 11;
public static final int EID_LS = 12;
public static final int EID_RS = 13;
public static final int EID_LSB = 14;
public static final int EID_RSB = 15;
protected VirtualController virtualController;
protected final int elementId;
private final Paint paint = new Paint();
@@ -40,10 +60,11 @@ public abstract class VirtualControllerElement extends View {
private Mode currentMode = Mode.Normal;
protected VirtualControllerElement(VirtualController controller, Context context) {
protected VirtualControllerElement(VirtualController controller, Context context, int elementId) {
super(context);
this.virtualController = controller;
this.elementId = elementId;
}
protected void moveElement(int pressed_x, int pressed_y, int x, int y) {
@@ -278,13 +299,28 @@ public abstract class VirtualControllerElement extends View {
return getWidth() > getHeight() ? getHeight() : getWidth();
}
/**
public JSONObject getConfiguration () {
JSONObject configuration = new JSONObject();
return configuration;
}
public void loadConfiguration (JSONObject configuration) {
}
*/
public JSONObject getConfiguration() throws JSONException {
JSONObject configuration = new JSONObject();
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
configuration.put("LEFT", layoutParams.leftMargin);
configuration.put("TOP", layoutParams.topMargin);
configuration.put("WIDTH", layoutParams.width);
configuration.put("HEIGHT", layoutParams.height);
return configuration;
}
public void loadConfiguration(JSONObject configuration) throws JSONException {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = configuration.getInt("LEFT");
layoutParams.topMargin = configuration.getInt("TOP");
layoutParams.width = configuration.getInt("WIDTH");
layoutParams.height = configuration.getInt("HEIGHT");
requestLayout();
}
}

View File

@@ -56,6 +56,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
private int consecutiveCrashCount;
private String glRenderer;
private boolean foreground = true;
private boolean legacyFrameDropRendering = false;
private boolean needsBaselineSpsHack;
private SeqParameterSet savedSps;
@@ -100,10 +101,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// for even required levels of HEVC.
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
if (decoderInfo != null) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, requestedHdr)) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr) {
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
}
else {
@@ -165,6 +167,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
refFrameInvalidationAvc = MediaCodecHelper.decoderSupportsRefFrameInvalidationAvc(avcDecoder.getName(), prefs.height);
refFrameInvalidationHevc = MediaCodecHelper.decoderSupportsRefFrameInvalidationHevc(avcDecoder.getName());
if (consecutiveCrashCount % 2 == 1) {
refFrameInvalidationAvc = refFrameInvalidationHevc = false;
LimeLog.warning("Disabling RFI due to previous crash");
}
if (directSubmit) {
LimeLog.info("Decoder "+avcDecoder.getName()+" will use direct submit");
}
@@ -185,6 +192,15 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
return avcDecoder != null;
}
public boolean is49FpsBlacklisted() {
return avcDecoder != null && MediaCodecHelper.decoderBlacklistedFor49Fps(avcDecoder.getName());
}
public void enableLegacyFrameDropRendering() {
LimeLog.info("Legacy frame drop rendering enabled");
legacyFrameDropRendering = true;
}
public boolean isHevcMain10Hdr10Supported() {
if (hevcDecoder == null) {
return false;
@@ -404,7 +420,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
// Render the last buffer
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && prefs.disableFrameDrop) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !legacyFrameDropRendering) {
// Use a PTS that will cause this frame to never be dropped if frame dropping
// is disabled
videoDecoder.releaseOutputBuffer(lastIndex, 0);
@@ -573,25 +589,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// May be called already, but we'll call it now to be safe
prepareForStop();
try {
// Invalidate pending decode buffers
videoDecoder.flush();
} catch (Exception e) {
e.printStackTrace();
}
// Wait for the renderer thread to shut down
try {
rendererThread.join();
} catch (InterruptedException ignored) { }
try {
// Stop the video decoder
videoDecoder.stop();
} catch (Exception e) {
e.printStackTrace();
}
// Halt the spinner threads
stopSpinnerThreads();
}
@@ -641,15 +643,15 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// High Profile which allows the decoder to assume there will be no B-frames and
// reduce delay and buffering accordingly. Some devices (Marvell, Exynos 4) don't
// like it so we only set them on devices that are confirmed to benefit from it.
if (sps.profile_idc == 100 && constrainedHighProfile) {
if (sps.profileIdc == 100 && constrainedHighProfile) {
LimeLog.info("Setting constraint set flags for constrained high profile");
sps.constraint_set_4_flag = true;
sps.constraint_set_5_flag = true;
sps.constraintSet4Flag = true;
sps.constraintSet5Flag = true;
}
else {
// Force the constraints unset otherwise (some may be set by default)
sps.constraint_set_4_flag = false;
sps.constraint_set_5_flag = false;
sps.constraintSet4Flag = false;
sps.constraintSet5Flag = false;
}
}
@@ -657,6 +659,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
@Override
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
int frameNumber, long receiveTimeMs) {
if (stopping) {
// Don't bother if we're stopping
return MoonBridge.DR_OK;
}
totalFramesReceived++;
// We can receive the same "frame" multiple times if it's an IDR frame.
@@ -706,15 +713,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// for known resolution combinations. Reference frame invalidation may need
// these, so leave them be for those decoders.
if (!refFrameInvalidationActive) {
if (initialWidth == 1280 && initialHeight == 720) {
if (initialWidth <= 720 && initialHeight <= 480) {
// Max 5 buffered frames at 720x480x60
LimeLog.info("Patching level_idc to 31");
sps.levelIdc = 31;
}
if (initialWidth <= 1280 && initialHeight <= 720) {
// Max 5 buffered frames at 1280x720x60
LimeLog.info("Patching level_idc to 32");
sps.level_idc = 32;
sps.levelIdc = 32;
}
else if (initialWidth == 1920 && initialHeight == 1080) {
else if (initialWidth <= 1920 && initialHeight <= 1080) {
// Max 4 buffered frames at 1920x1080x64
LimeLog.info("Patching level_idc to 42");
sps.level_idc = 42;
sps.levelIdc = 42;
}
else {
// Leave the profile alone (currently 5.0)
@@ -732,14 +744,14 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// where we've enabled reference frame invalidation.
if (!refFrameInvalidationActive) {
LimeLog.info("Patching num_ref_frames in SPS");
sps.num_ref_frames = 1;
sps.numRefFrames = 1;
}
// GFE 2.5.11 changed the SPS to add additional extensions
// Some devices don't like these so we remove them here.
sps.vuiParams.video_signal_type_present_flag = false;
sps.vuiParams.colour_description_present_flag = false;
sps.vuiParams.chroma_loc_info_present_flag = false;
sps.vuiParams.videoSignalTypePresentFlag = false;
sps.vuiParams.colourDescriptionPresentFlag = false;
sps.vuiParams.chromaLocInfoPresentFlag = false;
if ((needsSpsBitstreamFixup || isExynos4) && !refFrameInvalidationActive) {
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
@@ -749,22 +761,22 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
if (sps.vuiParams.bitstreamRestriction == null) {
LimeLog.info("Adding bitstream restrictions");
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
sps.vuiParams.bitstreamRestriction.motionVectorsOverPicBoundariesFlag = true;
sps.vuiParams.bitstreamRestriction.log2MaxMvLengthHorizontal = 16;
sps.vuiParams.bitstreamRestriction.log2MaxMvLengthVertical = 16;
sps.vuiParams.bitstreamRestriction.numReorderFrames = 0;
}
else {
LimeLog.info("Patching bitstream restrictions");
}
// Some devices throw errors if max_dec_frame_buffering < num_ref_frames
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = sps.num_ref_frames;
// Some devices throw errors if maxDecFrameBuffering < numRefFrames
sps.vuiParams.bitstreamRestriction.maxDecFrameBuffering = sps.numRefFrames;
// These values are the defaults for the fields, but they are more aggressive
// than what GFE sends in 2.5.11, but it doesn't seem to cause picture problems.
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2;
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1;
sps.vuiParams.bitstreamRestriction.maxBytesPerPicDenom = 2;
sps.vuiParams.bitstreamRestriction.maxBitsPerMbDenom = 1;
// log2_max_mv_length_horizontal and log2_max_mv_length_vertical are set to more
// conservative values by GFE 2.5.11. We'll let those values stand.
@@ -778,7 +790,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// If we need to hack this SPS to say we're baseline, do so now
if (needsBaselineSpsHack) {
LimeLog.info("Hacking SPS to baseline");
sps.profile_idc = 66;
sps.profileIdc = 66;
savedSps = sps;
}
@@ -933,7 +945,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
inputBuffer.put(new byte[]{0x00, 0x00, 0x00, 0x01, 0x67});
// Switch the H264 profile back to high
savedSps.profile_idc = 100;
savedSps.profileIdc = 100;
// Patch the SPS constraint flags
doProfileSpecificSpsPatching(savedSps);
@@ -1043,9 +1055,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
str += "Build fingerprint: "+Build.FINGERPRINT+"\n";
str += "Foreground: "+renderer.foreground+"\n";
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "FPS target: "+renderer.refreshRate+"\n";
str += "Bitrate: "+renderer.prefs.bitrate+" Mbps \n";
str += "Bitrate: "+renderer.prefs.bitrate+" Kbps \n";
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
str += "Total frames received: "+renderer.totalFramesReceived+"\n";
str += "Total frames rendered: "+renderer.totalFramesRendered+"\n";

View File

@@ -36,6 +36,7 @@ public class MediaCodecHelper {
private static final List<String> whitelistedHevcDecoders;
private static final List<String> refFrameInvalidationAvcPrefixes;
private static final List<String> refFrameInvalidationHevcPrefixes;
private static final List<String> blacklisted49FpsDecoderPrefixes;
private static boolean isLowEndSnapdragon = false;
private static boolean initialized = false;
@@ -153,27 +154,62 @@ public class MediaCodecHelper {
// Qualcomm is currently the only decoders in this group.
}
private static boolean isLowEndSnapdragonRenderer(String glRenderer) {
static {
blacklisted49FpsDecoderPrefixes = new LinkedList<>();
// We see a bunch of crashes on MediaTek Android TVs running
// at 49 FPS (PAL 50 Hz - 1). Blacklist this frame rate for
// these devices and hope they fix it in Oreo.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
blacklisted49FpsDecoderPrefixes.add("omx.mtk");
}
}
private static String getAdrenoVersionString(String glRenderer) {
glRenderer = glRenderer.toLowerCase().trim();
if (!glRenderer.contains("adreno")) {
return false;
return null;
}
Pattern modelNumberPattern = Pattern.compile("(.*)([0-9]{3})(.*)");
Matcher matcher = modelNumberPattern.matcher(glRenderer);
if (!matcher.matches()) {
return false;
return null;
}
String modelNumber = matcher.group(2);
LimeLog.info("Found Adreno GPU: "+modelNumber);
return modelNumber;
}
private static boolean isLowEndSnapdragonRenderer(String glRenderer) {
String modelNumber = getAdrenoVersionString(glRenderer);
if (modelNumber == null) {
// Not an Adreno GPU
return false;
}
// The current logic is to identify low-end SoCs based on a zero in the x0x place.
return modelNumber.charAt(1) == '0';
}
// This is a workaround for some broken devices that report
// only GLES 3.0 even though the GPU is an Adreno 4xx series part.
// An example of such a device is the Huawei Honor 5x with the
// Snapdragon 616 SoC (Adreno 405).
private static boolean isGLES31SnapdragonRenderer(String glRenderer) {
String modelNumber = getAdrenoVersionString(glRenderer);
if (modelNumber == null) {
// Not an Adreno GPU
return false;
}
// Snapdragon 4xx and higher support GLES 3.1
return modelNumber.charAt(0) >= '4';
}
public static void initialize(Context context, String glRenderer) {
if (initialized) {
return;
@@ -208,9 +244,10 @@ public class MediaCodecHelper {
// 3xx - bad
// 4xx - good
//
// Unfortunately, it's not that easy to get that information here, so I'll use an
// approximation by checking the GLES level (<= 3.0 is bad).
if (configInfo.reqGlEsVersion > 0x30000) {
// The "good" GPUs support GLES 3.1, but we can't just check that directly
// (see comment on isGLES31SnapdragonRenderer).
//
if (isGLES31SnapdragonRenderer(glRenderer)) {
// We prefer reference frame invalidation support (which is only doable on AVC on
// older Qualcomm chips) vs. enabling HEVC by default. The user can override using the settings
// to force HEVC on. If HDR or mobile data will be used, we'll override this and use
@@ -246,7 +283,7 @@ public class MediaCodecHelper {
public static long getMonotonicMillis() {
return System.nanoTime() / 1000000L;
}
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo) {
// Possibly enable adaptive playback on KitKat and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -287,11 +324,22 @@ public class MediaCodecHelper {
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
}
public static boolean decoderBlacklistedFor49Fps(String decoderName) {
return isDecoderInList(blacklisted49FpsDecoderPrefixes, decoderName);
}
public static boolean decoderSupportsRefFrameInvalidationAvc(String decoderName, int videoHeight) {
// Reference frame invalidation is broken on low-end Snapdragon SoCs at 1080p.
if (videoHeight > 720 && isLowEndSnapdragon) {
return false;
}
// This device seems to crash constantly at 720p, so try disabling
// RFI to see if we can get that under control.
if (Build.PRODUCT.equalsIgnoreCase("b3_att_us")) {
return false;
}
return isDecoderInList(refFrameInvalidationAvcPrefixes, decoderName);
}
@@ -299,7 +347,7 @@ public class MediaCodecHelper {
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
}
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, boolean willStreamHdr) {
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData) {
// TODO: Shield Tablet K1/LTE?
//
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
@@ -335,7 +383,7 @@ public class MediaCodecHelper {
// typically because it can't support reference frame invalidation.
// However, we will use it for HDR and for streaming over mobile networks
// since it works fine otherwise.
if ((meteredData || willStreamHdr) && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
if (meteredData && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
LimeLog.info("Selected deprioritized decoder");
return true;
}

View File

@@ -3,8 +3,10 @@ package com.limelight.computers;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -35,7 +37,7 @@ public class ComputerManagerService extends Service {
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
private static final int MDNS_QUERY_PERIOD_MS = 1000;
private static final int FAST_POLL_TIMEOUT = 500;
private static final int FAST_POLL_TIMEOUT = 1000;
private static final int OFFLINE_POLL_TRIES = 5;
private static final int INITIAL_POLL_TRIES = 2;
private static final int EMPTY_LIST_THRESHOLD = 3;
@@ -132,7 +134,7 @@ public class ComputerManagerService extends Service {
public void run() {
int offlineCount = 0;
while (!isInterrupted() && pollingActive) {
while (!isInterrupted() && pollingActive && tuple.thread == this) {
try {
// Only allow one request to the machine at a time
synchronized (tuple.networkLock) {
@@ -367,6 +369,10 @@ public class ComputerManagerService extends Service {
return true;
}
else {
if (!manuallyAdded) {
LimeLog.warning("Auto-discovered PC failed to respond: "+addr);
}
return false;
}
}
@@ -386,6 +392,7 @@ public class ComputerManagerService extends Service {
if (tuple.thread != null) {
// Interrupt the thread on this entry
tuple.thread.interrupt();
tuple.thread = null;
}
pollingTuples.remove(tuple);
break;
@@ -500,14 +507,26 @@ public class ComputerManagerService extends Service {
return ComputerDetails.Reachability.OFFLINE;
}
private static boolean isAddressLikelyLocal(String str) {
try {
// This will tend to be wrong for IPv6 but falling back to
// remote will be fine in that case. For IPv4, it should be
// pretty accurate due to NAT prevalence.
InetAddress addr = InetAddress.getByName(str);
return addr.isSiteLocalAddress() || addr.isLinkLocalAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
return false;
}
}
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails;
ComputerDetails.Reachability reachability;
// If the local address is routable across the Internet,
// always consider this PC remote to be conservative
if (details.localAddress.equals(details.remoteAddress)) {
reachability = ComputerDetails.Reachability.REMOTE;
reachability = isAddressLikelyLocal(details.localAddress) ?
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
}
else {
// Do a TCP-level connection to the HTTP server to see if it's listening
@@ -553,7 +572,14 @@ public class ComputerManagerService extends Service {
return null;
}
if (polledDetails.remoteAddress.equals(reachableAddr)) {
// If both addresses are the same, guess whether we're local based on
// IP address heuristics.
if (reachableAddr.equals(polledDetails.localAddress) &&
reachableAddr.equals(polledDetails.remoteAddress)) {
polledDetails.reachability = isAddressLikelyLocal(reachableAddr) ?
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.remoteAddress.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.localAddress.equals(reachableAddr)) {
@@ -815,6 +841,7 @@ public class ComputerManagerService extends Service {
} while (waitPollingDelay());
}
};
thread.setName("App list polling thread for " + computer.localAddress);
thread.start();
}

View File

@@ -13,7 +13,11 @@ import java.io.OutputStream;
public class DiskAssetLoader {
// 5 MB
private final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
private static final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
// Standard box art is 300x400
private static final int STANDARD_ASSET_WIDTH = 300;
private static final int STANDARD_ASSET_HEIGHT = 400;
private final File cacheDir;
@@ -25,33 +29,65 @@ public class DiskAssetLoader {
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
}
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
InputStream in = null;
Bitmap bmp = null;
try {
// Make sure the cached asset doesn't exceed the maximum size
if (CacheHelper.getFileSize(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png") > MAX_ASSET_SIZE) {
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
return null;
}
// https://developer.android.com/topic/performance/graphics/load-bitmap.html
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
bmp = BitmapFactory.decodeStream(in, null, options);
} catch (IOException ignored) {
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {}
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculates the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
// Don't bother with anything if it doesn't exist
if (!file.exists()) {
return null;
}
// Make sure the cached asset doesn't exceed the maximum size
if (file.length() > MAX_ASSET_SIZE) {
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
file.delete();
return null;
}
// Lookup bounds of the downloaded image
BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options();
decodeOnlyOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), decodeOnlyOptions);
if (decodeOnlyOptions.outWidth <= 0 || decodeOnlyOptions.outHeight <= 0) {
// Dimensions set to -1 on error. Return value always null.
return null;
}
LimeLog.info("Tuple "+tuple+" has cached art of size: "+decodeOnlyOptions.outWidth+"x"+decodeOnlyOptions.outHeight);
// Load the image scaled to the appropriate size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calculateInSampleSize(decodeOnlyOptions,
STANDARD_ASSET_WIDTH / sampleSize,
STANDARD_ASSET_HEIGHT / sampleSize);
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inDither = true;
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (bmp != null) {
LimeLog.info("Disk cache hit for tuple: "+tuple);
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
}
return bmp;

View File

@@ -1,5 +1,12 @@
package com.limelight.preferences;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.concurrent.LinkedBlockingQueue;
import com.limelight.computers.ComputerManagerService;
@@ -39,36 +46,84 @@ public class AddComputerManually extends Activity {
}
};
private boolean isWrongSubnetSiteLocalAddress(String address) {
try {
InetAddress targetAddress = InetAddress.getByName(address);
if (!(targetAddress instanceof Inet4Address) || !targetAddress.isSiteLocalAddress()) {
return false;
}
// We have a site-local address. Look for a matching local interface.
for (NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
if (!(addr.getAddress() instanceof Inet4Address) || !addr.getAddress().isSiteLocalAddress()) {
// Skip non-site-local or non-IPv4 addresses
continue;
}
byte[] targetAddrBytes = targetAddress.getAddress();
byte[] ifaceAddrBytes = addr.getAddress().getAddress();
// Compare prefix to ensure it's the same
boolean addressMatches = true;
for (int i = 0; i < addr.getNetworkPrefixLength(); i++) {
if ((ifaceAddrBytes[i / 8] & (1 << (i % 8))) != (targetAddrBytes[i / 8] & (1 << (i % 8)))) {
addressMatches = false;
break;
}
}
if (addressMatches) {
return false;
}
}
}
// Couldn't find a matching interface
return true;
} catch (SocketException e) {
e.printStackTrace();
return false;
} catch (UnknownHostException e) {
return false;
}
}
private void doAddPc(String host) {
String msg;
boolean finish = false;
boolean wrongSiteLocal = false;
boolean success;
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
getResources().getString(R.string.msg_add_pc), false);
if (!managerBinder.addComputerBlocking(host, true)){
msg = getResources().getString(R.string.addpc_fail);
}
else {
msg = getResources().getString(R.string.addpc_success);
finish = true;
success = managerBinder.addComputerBlocking(host, true);
if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
}
dialog.dismiss();
final boolean toastFinish = finish;
final String toastMsg = msg;
AddComputerManually.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show();
if (wrongSiteLocal) {
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_wrong_sitelocal), false);
}
else if (!success) {
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_fail), false);
}
else {
AddComputerManually.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_success), Toast.LENGTH_LONG).show();
if (toastFinish && !isFinishing()) {
if (!isFinishing()) {
// Close the activity
AddComputerManually.this.finish();
}
}
});
}
});
}
}
private void startAddThread() {

View File

@@ -0,0 +1,41 @@
package com.limelight.preferences;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.widget.Toast;
import com.limelight.R;
import static com.limelight.binding.input.virtual_controller.VirtualControllerConfigurationLoader.OSC_PREFERENCE;
public class ConfirmDeleteOscPreference extends DialogPreference {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ConfirmDeleteOscPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ConfirmDeleteOscPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ConfirmDeleteOscPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ConfirmDeleteOscPreference(Context context) {
super(context);
}
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
getContext().getSharedPreferences(OSC_PREFERENCE, Context.MODE_PRIVATE).edit().clear().apply();
Toast.makeText(getContext(), R.string.toast_reset_osc_success, Toast.LENGTH_SHORT).show();
}
}
}

View File

@@ -8,7 +8,8 @@ import android.preference.PreferenceManager;
public class PreferenceConfiguration {
static final String RES_FPS_PREF_STRING = "list_resolution_fps";
static final String BITRATE_PREF_STRING = "seekbar_bitrate";
static final String BITRATE_PREF_STRING = "seekbar_bitrate_kbps";
private static final String BITRATE_PREF_OLD_STRING = "seekbar_bitrate";
private static final String STRETCH_PREF_STRING = "checkbox_stretch_video";
private static final String SOPS_PREF_STRING = "checkbox_enable_sops";
private static final String DISABLE_TOASTS_PREF_STRING = "checkbox_disable_warnings";
@@ -27,13 +28,17 @@ public class PreferenceConfiguration {
private static final String DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
private static final String ENABLE_HDR_PREF_STRING = "checkbox_enable_hdr";
private static final String ENABLE_PIP_PREF_STRING = "checkbox_enable_pip";
private static final String BIND_ALL_USB_STRING = "checkbox_usb_bind_all";
private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation";
private static final int BITRATE_DEFAULT_720_30 = 5;
private static final int BITRATE_DEFAULT_720_60 = 10;
private static final int BITRATE_DEFAULT_1080_30 = 10;
private static final int BITRATE_DEFAULT_1080_60 = 20;
private static final int BITRATE_DEFAULT_4K_30 = 40;
private static final int BITRATE_DEFAULT_4K_60 = 80;
private static final int BITRATE_DEFAULT_360_30 = 1000;
private static final int BITRATE_DEFAULT_360_60 = 2000;
private static final int BITRATE_DEFAULT_720_30 = 5000;
private static final int BITRATE_DEFAULT_720_60 = 10000;
private static final int BITRATE_DEFAULT_1080_30 = 10000;
private static final int BITRATE_DEFAULT_1080_60 = 20000;
private static final int BITRATE_DEFAULT_4K_30 = 40000;
private static final int BITRATE_DEFAULT_4K_60 = 80000;
private static final String DEFAULT_RES_FPS = "720p60";
private static final int DEFAULT_BITRATE = BITRATE_DEFAULT_720_60;
@@ -54,6 +59,8 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_DISABLE_FRAME_DROP = false;
private static final boolean DEFAULT_ENABLE_HDR = false;
private static final boolean DEFAULT_ENABLE_PIP = false;
private static final boolean DEFAULT_BIND_ALL_USB = false;
private static final boolean DEFAULT_MOUSE_EMULATION = true;
public static final int FORCE_H265_ON = -1;
public static final int AUTOSELECT_H265 = 0;
@@ -72,9 +79,17 @@ public class PreferenceConfiguration {
public boolean disableFrameDrop;
public boolean enableHdr;
public boolean enablePip;
public boolean bindAllUsb;
public boolean mouseEmulation;
public static int getDefaultBitrate(String resFpsString) {
if (resFpsString.equals("720p30")) {
if (resFpsString.equals("360p30")) {
return BITRATE_DEFAULT_360_30;
}
else if (resFpsString.equals("360p60")) {
return BITRATE_DEFAULT_360_60;
}
else if (resFpsString.equals("720p30")) {
return BITRATE_DEFAULT_720_30;
}
else if (resFpsString.equals("720p60")) {
@@ -147,6 +162,7 @@ public class PreferenceConfiguration {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit()
.remove(BITRATE_PREF_STRING)
.remove(BITRATE_PREF_OLD_STRING)
.remove(RES_FPS_PREF_STRING)
.remove(VIDEO_FORMAT_PREF_STRING)
.remove(ENABLE_HDR_PREF_STRING)
@@ -157,9 +173,23 @@ public class PreferenceConfiguration {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
PreferenceConfiguration config = new PreferenceConfiguration();
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, getDefaultBitrate(context));
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, prefs.getInt(BITRATE_PREF_OLD_STRING, 0) * 1000);
if (config.bitrate == 0) {
config.bitrate = getDefaultBitrate(context);
}
String str = prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS);
if (str.equals("720p30")) {
if (str.equals("360p30")) {
config.width = 640;
config.height = 360;
config.fps = 30;
}
else if (str.equals("360p60")) {
config.width = 640;
config.height = 360;
config.fps = 60;
}
else if (str.equals("720p30")) {
config.width = 1280;
config.height = 720;
config.fps = 30;
@@ -218,6 +248,8 @@ public class PreferenceConfiguration {
config.disableFrameDrop = prefs.getBoolean(DISABLE_FRAME_DROP_PREF_STRING, DEFAULT_DISABLE_FRAME_DROP);
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION);
return config;
}

View File

@@ -16,7 +16,8 @@ import android.widget.TextView;
// Based on a Stack Overflow example: http://stackoverflow.com/questions/1974193/slider-on-my-preferencescreen
public class SeekBarPreference extends DialogPreference
{
private static final String SCHEMA_URL = "http://schemas.android.com/apk/res/android";
private static final String ANDROID_SCHEMA_URL = "http://schemas.android.com/apk/res/android";
private static final String SEEKBAR_SCHEMA_URL = "http://schemas.moonlight-stream.com/apk/res/seekbar";
private SeekBar seekBar;
private TextView valueText;
@@ -27,6 +28,7 @@ public class SeekBarPreference extends DialogPreference
private final int defaultValue;
private final int maxValue;
private final int minValue;
private final int stepSize;
private int currentValue;
public SeekBarPreference(Context context, AttributeSet attrs) {
@@ -34,27 +36,28 @@ public class SeekBarPreference extends DialogPreference
this.context = context;
// Read the message from XML
int dialogMessageId = attrs.getAttributeResourceValue(SCHEMA_URL, "dialogMessage", 0);
int dialogMessageId = attrs.getAttributeResourceValue(ANDROID_SCHEMA_URL, "dialogMessage", 0);
if (dialogMessageId == 0) {
dialogMessage = attrs.getAttributeValue(SCHEMA_URL, "dialogMessage");
dialogMessage = attrs.getAttributeValue(ANDROID_SCHEMA_URL, "dialogMessage");
}
else {
dialogMessage = context.getString(dialogMessageId);
}
// Get the suffix for the number displayed in the dialog
int suffixId = attrs.getAttributeResourceValue(SCHEMA_URL, "text", 0);
int suffixId = attrs.getAttributeResourceValue(ANDROID_SCHEMA_URL, "text", 0);
if (suffixId == 0) {
suffix = attrs.getAttributeValue(SCHEMA_URL, "text");
suffix = attrs.getAttributeValue(ANDROID_SCHEMA_URL, "text");
}
else {
suffix = context.getString(suffixId);
}
// Get default, min, and max seekbar values
defaultValue = attrs.getAttributeIntValue(SCHEMA_URL, "defaultValue", PreferenceConfiguration.getDefaultBitrate(context));
maxValue = attrs.getAttributeIntValue(SCHEMA_URL, "max", 100);
minValue = 1;
defaultValue = attrs.getAttributeIntValue(ANDROID_SCHEMA_URL, "defaultValue", PreferenceConfiguration.getDefaultBitrate(context));
maxValue = attrs.getAttributeIntValue(ANDROID_SCHEMA_URL, "max", 100);
minValue = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "min", 1);
stepSize = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "step", 1);
}
@Override
@@ -89,6 +92,12 @@ public class SeekBarPreference extends DialogPreference
return;
}
int roundedValue = ((value + (stepSize - 1))/stepSize)*stepSize;
if (roundedValue != value) {
seekBar.setProgress(roundedValue);
return;
}
String t = String.valueOf(value);
valueText.setText(suffix == null ? t : t.concat(suffix.length() > 1 ? " "+suffix : suffix));
}

View File

@@ -13,7 +13,7 @@ import java.io.OutputStream;
import java.io.Reader;
public class CacheHelper {
private static File openPath(boolean createPath, File root, String... path) {
public static File openPath(boolean createPath, File root, String... path) {
File f = root;
for (int i = 0; i < path.length; i++) {
String component = path[i];

View File

@@ -89,7 +89,7 @@
<string name="summary_resolution_list">Establecer unos valores demasiado altos puede causar lag o cierres inesperados</string>
<string name="title_seekbar_bitrate">Seleccionar bitrate de vídeo</string>
<string name="summary_seekbar_bitrate">Usa bitrate bajo para reducir "parpadeo". Incrementa el bitrate para mayor calidad de imagen.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Ajustar vídeo a pantalla completa</string>
<string name="title_checkbox_disable_warnings">Desactivar mensajes de advertencia</string>
<string name="summary_checkbox_disable_warnings">Desactivar mensajes de advertencia en pantalla durante la transmisión</string>

View File

@@ -100,7 +100,7 @@
<string name="summary_resolution_list">Le réglage de valeurs trop élevées pour votre appareil peut provoquer un retard ou un plantage</string>
<string name="title_seekbar_bitrate">Sélectionnez le bitrate vidéo à obtenir</string>
<string name="summary_seekbar_bitrate">Bitrate inférieur pour réduire la saccade. Augmentez le bitrate pour augmenter la qualité de l\'image.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string>
<string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string>
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string>

View File

@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Shortcut strings -->
<string name="scut_deleted_pc">PC eliminato</string>
<string name="scut_not_paired">PC non accoppiato</string>
<!-- Help strings -->
<string name="help_loading_title">Visualizza assistenza</string>
<string name="help_loading_msg">Caricamento pagina di assistenza…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Lista applicazioni</string>
<string name="pcview_menu_pair_pc">Accoppia PC</string>
@@ -10,21 +17,22 @@
<!-- Pair messages -->
<string name="pairing">Accoppiamento…</string>
<string name="pair_pc_offline">PC offline</string>
<string name="pair_pc_offline">Il PC è offline</string>
<string name="pair_pc_ingame">PC con applicazione avviata. Devi chiudere l\'applicazione prima dell\'accoppiamento.</string>
<string name="pair_pairing_title">Accoppiamento</string>
<string name="pair_pairing_msg">Inserisci il seguente PIN sul PC:</string>
<string name="pair_incorrect_pin">PIN non corretto</string>
<string name="pair_fail">Accoppiamento fallito</string>
<string name="pair_already_in_progress">Accoppiamento già in corso</string>
<!-- WOL messages -->
<string name="wol_pc_online">PC già avviato</string>
<string name="wol_no_mac">Impossibile risvegliare il PC perchè GFE non ha inviato nessun indirizzo MAC</string>
<string name="wol_no_mac">Impossibile risvegliare il PC perché GFE non ha inviato nessun indirizzo MAC</string>
<string name="wol_waking_pc">Risveglio PC…</string>
<string name="wol_waking_msg">Il PC potrebbe impiegare qualche secondo per risvegliarsi.
Se non succede niente, assicurati che l\'opzione Wake-On-LAN sia configurata correttamente.
Se non succede niente, assicurati che l\'opzione Wake-On-LAN sia configurata correttamente.
</string>
<string name="wol_fail">Invio pacchetto Wake-On-LAN fallito</string>
<string name="wol_fail">Invio pacchetti Wake-On-LAN fallito</string>
<!-- Unpair messages -->
<string name="unpairing">Disaccoppiamento…</string>
@@ -37,33 +45,41 @@
<string name="error_manager_not_running">Il servizio ComputerManager non è avviato. Attendi qualche secondo o riavvia l\'applicazione.</string>
<string name="error_unknown_host">Risoluzione nome host fallita</string>
<string name="error_404">GFE ha ritornato un errore HTTP 404 error. Assicurati che il PC stia usando una GPU supportata.
Usare un software di remote-desktop può causare questo errore. Prova a riavviare il PC o a reinstallare GFE.
Usare un software di desktop remoto può causare questo errore. Prova a riavviare il PC o a reinstallare GFE.
</string>
<string name="title_decoding_error">Il decodificatore video ha smesso di funzionare</string>
<string name="message_decoding_error">Moonlight ha smesso di funzionare per colpa di un problema al decodificatore video di questo dispositivo. Prova a modificare le impostazioni di stream se il problema persiste.</string>
<string name="title_decoding_reset">Ripristino impostazioni video</string>
<string name="message_decoding_reset">Il decodificatore video del dispositivo continua a funzionare in modo anomalo con le impostazioni di streaming selezionate. Le impostazioni di streaming sono state ripristinate ai valori predefiniti.</string>
<string name="error_usb_prohibited">L\'accesso USB è vietato dall\'amministratore del dispositivo. Verifica le impostazioni Knox o MDM.</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Connessione</string>
<string name="conn_establishing_msg">Connessione in corso</string>
<string name="conn_establishing_title">Connessione in corso</string>
<string name="conn_establishing_msg">Avvio connessione</string>
<string name="conn_metered">Attenzione: la rete attiva prevede costi aggiuntivi in base all\'utilizzo!</string>
<string name="conn_client_latency">Latenza frame media client-side:</string>
<string name="conn_client_latency_hw">latenza decoder hardware:</string>
<string name="conn_hardware_latency">Latenza decoder hardware media:</string>
<string name="conn_client_latency">Latenza decodifica fotogrammi media:</string>
<string name="conn_client_latency_hw">latenza decodificatore hardware:</string>
<string name="conn_hardware_latency">Latenza decodificatore hardware media:</string>
<string name="conn_starting">Avvio in corso…</string>
<string name="conn_error_title">Errore connessione</string>
<string name="conn_error_msg">Avvio fallito</string>
<string name="conn_terminated_title">Connessione terminata</string>
<string name="conn_terminated_title">Connessione interrotta</string>
<string name="conn_terminated_msg">La connessione è stata interrotta</string>
<!-- General strings -->
<string name="ip_hint">Indirizzo IP del PC</string>
<string name="searching_pc">Ricerca PC in corso…</string>
<string name="searching_pc">Ricerca di PC con GameStream avviato…\n\n
Assicurati che GameStream sia abilitato nelle impostazioni SHIELD di GeForce Experience.</string>
<string name="yes"></string>
<string name="no">No</string>
<string name="lost_connection">Connessione con il PC persa</string>
<string name="help">Assistenza</string>
<!-- AppList activity -->
<string name="title_applist">Applicazioni su</string>
<string name="applist_menu_resume">Riprendi Sessione</string>
<string name="applist_menu_quit">Chiudi Sessione</string>
<string name="applist_connect_msg">Connessione al PC in corso…</string>
<string name="applist_menu_resume">Riprendi sessione</string>
<string name="applist_menu_quit">Chiudi sessione</string>
<string name="applist_menu_quit_and_start">Chiudi sessione corrente e avvia</string>
<string name="applist_menu_cancel">Annulla</string>
<string name="applist_refresh_title">Lista applicazioni</string>
@@ -76,43 +92,76 @@
<string name="applist_quit_confirmation">Sei sicuro di voler chiudere l\'applicazione avviata? Tutti i dati non salvati saranno persi.</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Aggiungi PC Manualmente</string>
<string name="title_add_pc">Aggiungi PC manualmente</string>
<string name="msg_add_pc">Connessione al PC in corso…</string>
<string name="addpc_fail">Impossibile connettersi al PC. Assicurati che il firewall del PC sia configurato correttamente.</string>
<string name="addpc_fail">Impossibile connettersi al PC specificato. Assicurati che le porte nel firewall del PC siano configurate correttamente.</string>
<string name="addpc_success">PC aggiunto con successo</string>
<string name="addpc_unknown_host">Impossibile risovere l\'indirizzo del PC. Assicurati di aver scritto correttamente l\'indirizzo.</string>
<string name="addpc_enter_ip">Devi inserire un indirizzo IP</string>
<string name="addpc_wrong_sitelocal">Quell\'indirizzo non sembra corretto. È necessario utilizzare l\'indirizzo IP pubblico del router per lo streaming su Internet.</string>
<!-- Preferences -->
<string name="category_basic_settings">Impostazioni Base</string>
<string name="category_basic_settings">Impostazioni generali</string>
<string name="title_resolution_list">Risoluzione e FPS</string>
<string name="summary_resolution_list">Valori troppo elevati possono causare lag o crash</string>
<string name="title_seekbar_bitrate">Bitrate video</string>
<string name="summary_seekbar_bitrate">Abbassa il bitrate per ridurre lo stuttering; alza il bitrate per aumenteare la qualità dell\'immagine</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="title_checkbox_stretch_video">Forza video in full-screen</string>
<string name="title_seekbar_bitrate">Velocità di trasmissione video</string>
<string name="summary_seekbar_bitrate">Abbassa la velocità di trasmissione per ridurre lo stuttering; alzala per migliorare la qualità dell\'immagine</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Forza video a schermo intero</string>
<string name="title_checkbox_disable_warnings">Disabilita messaggi di warning</string>
<string name="summary_checkbox_disable_warnings">Disabilita i messaggi di warning sullo schermo durante lo streaming</string>
<string name="title_checkbox_battery_saver">Risparmio batteria</string>
<string name="summary_checkbox_battery_saver">Usa meno batteria, ma può aumentare lo stuttering</string>
<string name="title_checkbox_enable_pip">Abilita modalità spettatore Picture-in-Picture</string>
<string name="summary_checkbox_enable_pip">Permette di osservare (ma non di controllare) la stream in multitasking</string>
<string name="category_gamepad_settings">Impostazioni Gamepad</string>
<string name="title_checkbox_multi_controller">Supporto controller multipli</string>
<string name="summary_checkbox_multi_controller">Quando disabilitato, tutti i controllers appaiono come uno solo</string>
<string name="title_seekbar_deadzone">Aggiusta deadzone degli stick analogici</string>
<string name="category_audio_settings">Impostazioni audio</string>
<string name="title_checkbox_51_surround">Abilita l\'audio 5.1 surround</string>
<string name="summary_checkbox_51_surround">Se riscontri problemi, disabilitalo. Richiede GFE 2.7 o versioni sucessive.</string>
<string name="category_gamepad_settings">Impostazioni controller</string>
<string name="title_checkbox_multi_controller">Supporto a più controller</string>
<string name="summary_checkbox_multi_controller">Quando disabilitato, tutti i controller appaiono come uno solo</string>
<string name="title_seekbar_deadzone">Regola i punti morti degli stick analogici</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Driver del controller Xbox 360/One</string>
<string name="summary_checkbox_xb1_driver">Abilita un driver USB integrato per dispositivi senza supporto al controller Xbox nativo</string>
<string name="title_checkbox_usb_bind_all">Sovrascrivi il supporto ai controller su Android</string>
<string name="summary_checkbox_usb_bind_all">Forza i driver USB di Moonlight di assumere il controllo su tutti i controller Xbox supportati</string>
<string name="title_checkbox_mouse_emulation">Emulazione del mouse tramite controller</string>
<string name="summary_checkbox_mouse_emulation">Tenendo premuto il pulsante Start, il controller passerà alla modalità mouse</string>
<string name="category_on_screen_controls_settings">Impostazioni dei controlli a schermo</string>
<string name="title_checkbox_show_onscreen_controls">Mostra controlli a schermo</string>
<string name="summary_checkbox_show_onscreen_controls">Mostra l\'overlay virtuale del controller su schermo</string>
<string name="title_only_l3r3">Mostra solo L3 e R3</string>
<string name="summary_only_l3r3">Nasconde tutti i pulsanti virtuali tranne L3 e R3</string>
<string name="title_reset_osc">Ripristina il layout personalizzato dei controlli a schermo</string>
<string name="summary_reset_osc">Ripristina tutti i controlli a schermo nelle loro posizioni e dimensioni predefinite</string>
<string name="dialog_title_reset_osc">Ripristino del layout</string>
<string name="dialog_text_reset_osc">Sei sicuro di voler ripristinare ai valori predefiniti i layout dei controlli a schermo?</string>
<string name="toast_reset_osc_success">I controlli a schermo sono stati ripristinati</string>
<string name="category_ui_settings">Impostazioni Interfaccia</string>
<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 applicazioni e computers in una lista invece di una 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 vista a griglia per avere più oggetti sullo schermo</string>
<string name="summary_checkbox_small_icon_mode">Usa icone piccole nella griglia per avere più oggetti a schermo</string>
<string name="category_host_settings">Impostazioni Host</string>
<string name="category_host_settings">Impostazioni del PC host</string>
<string name="title_checkbox_enable_sops">Ottimizza le impostazioni dei giochi</string>
<string name="summary_checkbox_enable_sops">Permetti a GFE di modificare le impostazioni dei giochi per uno streaming ottimale</string>
<string name="title_checkbox_host_audio">Riproduci audio sul PC</string>
<string name="summary_checkbox_host_audio">Riproduci l\'audio sul computer e su questo dispositivo</string>
<string name="summary_checkbox_host_audio">Riproduce l\'audio sul PC e su questo dispositivo</string>
<string name="category_advanced_settings">Impostazioni Avanzate</string>
<string name="category_advanced_settings">Impostazioni avanzate</string>
<string name="title_disable_frame_drop">Non saltare i fotogrammi</string>
<string name="summary_disable_frame_drop">Potrebbe ridurre il micro-stuttering su alcuni dispositivi, ma può aumentare la latenza</string>
<string name="title_video_format">Modifica impostazioni H.265</string>
<string name="summary_video_format">H.265 riduce i requisiti di larghezza di banda video ma richiede un dispositivo molto recente</string>
<string name="title_enable_hdr">Abilita HDR (sperimentale)</string>
<string name="summary_enable_hdr">Utilizza l\'HDR quando il gioco e la scheda video del PC lo supportano. L\'HDR richiede una scheda video serie GTX 1000 o sucessive.</string>
</resources>

View File

@@ -85,7 +85,7 @@
<string name="summary_resolution_list">品質が高いほどラグとクラッシュが発生しやすくなります</string>
<string name="title_seekbar_bitrate">映像のビットレート</string>
<string name="summary_seekbar_bitrate">ビットレートを低くすればカクつきが抑制され、高くすれば画質が向上します</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">映像を全画面に拡大</string>
<string name="title_checkbox_disable_warnings">警告を無効化</string>
<string name="summary_checkbox_disable_warnings">ストリーミング中に画面に警告メッセージを表示しない</string>

View File

@@ -100,7 +100,7 @@
<string name="summary_resolution_list">세팅 값이 자신의 PC 성능보다 너무 높으면 렉이나 깨짐을 유발할 수 있습니다.</string>
<string name="title_seekbar_bitrate">비트레이트 타겟 지정</string>
<string name="summary_seekbar_bitrate">낮은 비트레이트는 끊김을 줄이고, 높은 비트레이트는 품질을 높입니다.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">전체 화면으로 렌더링 스크린 늘이기</string>
<string name="title_checkbox_disable_warnings">경고 메세지 끄기</string>
<string name="summary_checkbox_disable_warnings">화면 상의 연결 경고 메세지를 스트리밍 중에 비활성화합니다.</string>

View File

@@ -1,14 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="resolution_names">
<item>720p 30 FPS</item>
<item>720p 60 FPS</item>
<item>1080p 30 FPS</item>
<item>1080p 60 FPS</item>
<item>4K 30 FPS</item>
<item>4K 60 FPS</item>
</string-array>
<string-array name="decoder_names">
<item>Selecteer Decoder Automatisch</item>
<item>Forceer Software Decoderen</item>

View File

@@ -89,7 +89,7 @@
<string name="summary_resolution_list">Te hoge instellingen kunnen crashes en haperingen veroorzaken.</string>
<string name="title_seekbar_bitrate">Selecteer doel video bitsnelheid</string>
<string name="summary_seekbar_bitrate">Verlaag bitsnelheid om haperingen te verminderen. Verhoog de bitsnelheid voor een betere videokwaliteit.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Rek video uit tot volledig scherm</string>
<string name="title_checkbox_disable_warnings">Verberg waarschuwingsberichten</string>
<string name="summary_checkbox_disable_warnings">Verberg on-screen verbindingswaarschuwingen tijdens het streamen</string>

View File

@@ -28,7 +28,7 @@
<!-- Unpair messages -->
<string name="unpairing">Разрыв пары…</string>
<string name="unpair_success">Разрыв пары закончился успешно.</string>
<string name="unpair_success">Разрыв пары закончился успешно</string>
<string name="unpair_fail">Разрыв пары не удался</string>
<string name="unpair_error">Устройство не было спарено</string>
@@ -36,14 +36,14 @@
<string name="error_pc_offline">Компьютер выключен или находится не в сети</string>
<string name="error_manager_not_running">Сервис ComputerManager не запущен. Пожалуйста, подождите несколько секунд или перезапустите приложение.</string>
<string name="error_unknown_host">Не удалось найти хост</string>
<string name="error_404">GFE вернул ошибку HTTP 404. Убедитесь что ваш PC is испольщует поддерживаемый GPU.
Использование программ для удалённого доступа также можнт вызывать эту ошибку. Попробуйте перезагрузить компьютер или переустановить GFE.
<string name="error_404">GFE вернул ошибку HTTP 404. Убедитесь что Ваш PC использует поддерживаемый GPU.
Использование программ для удалённого доступа также может вызывать эту ошибку. Попробуйте перезагрузить компьютер или переустановить GFE.
</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Создание соединения.</string>
<string name="conn_establishing_title">Создание соединения</string>
<string name="conn_establishing_msg">Подключение</string>
<string name="conn_metered">Внимание: Происходит измерение вашего сетевого соединения!</string>
<string name="conn_metered">Внимание: Происходит измерение Вашего сетевого соединения!</string>
<string name="conn_client_latency">Средняя задержка декодирования кадра: </string>
<string name="conn_client_latency_hw">задержка аппаратного декодирования:</string>
<string name="conn_hardware_latency">Средняя задержка апаратного декодирования:</string>
@@ -54,8 +54,9 @@
<string name="conn_terminated_msg">Подключение было прервано</string>
<!-- General strings -->
<string name="ip_hint">IP адресс компьютера с GeForce</string>
<string name="searching_pc">Поиск компьютеров</string>
<string name="ip_hint">IP-адрес компьютера с GeForce</string>
<string name="searching_pc">Поиск компьютеров с запущенным GameStream…\n\n
Убедитесь что GameStream включен в настройках GeForce Experience в разделе SHIELD.</string>
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="lost_connection">Потеряно соединение с PC</string>
@@ -79,49 +80,84 @@
<string name="title_add_pc">Добавление PC вручную</string>
<string name="msg_add_pc">Соединение с PC…</string>
<string name="addpc_fail">Не удалось подключиться к выбранному компьютеру. Удостоверьтесь, что необходимые порты разрешены в настройках брандмауэра.</string>
<string name="addpc_success">Компьютер добавлен успешно.</string>
<string name="addpc_unknown_host">Не удалось найти PC по указанному адресу. Убедитесь, что вы не совершили ошибок во время его написания.</string>
<string name="addpc_success">Компьютер добавлен успешно</string>
<string name="addpc_unknown_host">Не удалось найти PC по указанному адресу. Убедитесь, что Вы не совершили ошибок во время его написания.</string>
<string name="addpc_enter_ip">Вы должны ввести IP адрес</string>
<!-- Preferences -->
<string name="category_basic_settings">Базовые Настройки</string>
<string name="title_resolution_list">Выберите разрешение и частоту кадров.</string>
<string name="summary_resolution_list">Выбор слишком высокого значеня для своего устройства может вызвать тормоза или вылеты.</string>
<string name="title_seekbar_bitrate">Выберите битрейт видео.</string>
<string name="category_basic_settings">Общие Настройки</string>
<string name="title_resolution_list">Выберите разрешение и частоту кадров</string>
<string name="summary_resolution_list">Выбор слишком высокого значеня для своего устройства может вызвать тормоза или вылеты</string>
<string name="title_seekbar_bitrate">Выберите битрейт видео</string>
<string name="summary_seekbar_bitrate">Низкий битрейт уменьшит зависания. Увеличение битрейта улучшит качество изображения.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Растягивать видео на весь экран</string>
<string name="title_checkbox_disable_warnings">Отключить сообщения с предупреждениями</string>
<string name="summary_checkbox_disable_warnings">Выключить экранные предупреждения о соединении во время стрима.</string>
<string name="summary_checkbox_disable_warnings">Выключить экранные предупреждения о соединении во время трансляции</string>
<string name="category_audio_settings">Аудио Настройки</string>
<string name="title_checkbox_51_surround">Включить объёмный звук 5.1</string>
<string name="summary_checkbox_51_surround">Отключите, если появляются аудио проблемы. Требуется GFE 2.7 или выше.</string>
<string name="category_gamepad_settings">Настройки Гемпада</string>
<string name="category_gamepad_settings">Настройки Геймпада</string>
<string name="title_checkbox_multi_controller">Поддержка нескольких контроллеров</string>
<string name="summary_checkbox_multi_controller">Когда отключена, все контроллеры определяются как один. </string>
<string name="title_seekbar_deadzone">Регулировать мертвую зону аналогового стика.</string>
<string name="summary_checkbox_multi_controller">Когда отключена, все контроллеры определяются как один</string>
<string name="title_seekbar_deadzone">Регулировать мертвую зону аналогового стика</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Драйвер контроллера от Xbox One</string>
<string name="summary_checkbox_xb1_driver">Включить встроенный USB драйвер для устройств без встроенной поддержки контроллера от Xbox One.</string>
<string name="title_checkbox_xb1_driver">Драйвер контроллеров Xbox 360/One</string>
<string name="summary_checkbox_xb1_driver">Включить встроенный USB драйвер для устройств без собственной поддержки контроллеров Xbox</string>
<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_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>
<string name="summary_checkbox_small_icon_mode">Использовать маленькие иконки в сетке для отображения большего числа элементов на экране</string>
<string name="category_host_settings">Настройки Хоста</string>
<string name="title_checkbox_enable_sops">Оптимизировать игровые настройки</string>
<string name="summary_checkbox_enable_sops">Разрешить GFE изменять настройки игр для оптимальной потоковой передачи</string>
<string name="summary_checkbox_enable_sops">Разрешить GFE изменять настройки игр для оптимальной трансляции</string>
<string name="title_checkbox_host_audio">Проигрывать звук на PC</string>
<string name="summary_checkbox_host_audio">Проигрывать звук на компьютере и текущем устройстве.</string>
<string name="summary_checkbox_host_audio">Проигрывать звук на компьютере и текущем устройстве</string>
<string name="category_advanced_settings">Расширенные Настройки</string>
<string name="title_video_format">Изменить настройки H.265</string>
<string name="summary_video_format">H.265 снижает требования к пропускной способности, но требует очень свежих устройств.</string>
<string name="summary_video_format">H.265 снижает требования к пропускной способности, но требует очень нового устройства</string>
<string name="category_on_screen_controls_settings">Настройки Экранных Кнопок</string>
<string name="title_checkbox_show_onscreen_controls">Показывать экранные кнопки</string>
<string name="summary_checkbox_show_onscreen_controls">Отображать оверлей виртуального контроллера на сенсорном экране</string>
<string name="title_only_l3r3">Показывать только L3 и R3</string>
<string name="summary_only_l3r3">Скрывать все экранные кнопки кроме L3 и R3</string>
<string name="scut_deleted_pc">PC удален</string>
<string name="scut_not_paired">PC не сопряжен</string>
<string name="help_loading_title">Просмотр Помощи</string>
<string name="help_loading_msg">Загрузка страницы помощи…</string>
<string name="pair_already_in_progress">Сопряжение уже в процессе</string>
<string name="help">Помощь</string>
<string name="applist_connect_msg">Подключение к PC…</string>
<string name="title_decoding_error">Сбой Видео Декодера</string>
<string name="message_decoding_error">Произошел сбой Moonlight из-за проблем с видео декодером данного устройства. Попробуйте изменить настройки трансляции если сбои будут продолжаться.</string>
<string name="title_decoding_reset">Видео Настройки Сброшены</string>
<string name="message_decoding_reset">Видео декодер Вашего устройства давал сбои с выбранными настройками. Настройки трансляции были сброшены до значений по умолчанию.</string>
<string name="error_usb_prohibited">USB доступ запрещен администратором устройства. Проверьте настройки Knox или MDM.</string>
<string name="addpc_wrong_sitelocal">Адрес указан неверно. Вы должны ввести публичный IP-адрес Вашего роутера для передачи через интернет.</string>
<string name="title_checkbox_battery_saver">Экономия батареи</string>
<string name="summary_checkbox_battery_saver">Использует меньше заряда батареи, но может увеличить зависания</string>
<string name="title_checkbox_enable_pip">Включить просмотр в режиме \"Картинка в картинке\"</string>
<string name="summary_checkbox_enable_pip">Позволяет просматривать трансляцию (но не управлять ей) во время работы в других приложениях</string>
<string name="title_checkbox_usb_bind_all">Переопределить поддержку контроллеров Android</string>
<string name="summary_checkbox_usb_bind_all">Заставляет USB драйвер Moonlight взять на себя работу со всеми поддерживаемыми Xbox геймпадами</string>
<string name="title_checkbox_mouse_emulation">Эмуляция мыши на геймпаде</string>
<string name="summary_checkbox_mouse_emulation">Долгое нажатие кнопки Start переключит геймпад в режим мыши</string>
<string name="title_reset_osc">Сбросить схему расположения экранных кнопок</string>
<string name="summary_reset_osc">Возвращает все экранные элементы управления к их расположениям по умолчанию</string>
<string name="dialog_title_reset_osc">Сбросить Схему</string>
<string name="dialog_text_reset_osc">Вы уверены что хотите удалить сохранненную схему расположения кнопок?</string>
<string name="toast_reset_osc_success">Экранные элементы управления возвращены к положениям по умолчанию</string>
<string name="title_disable_frame_drop">Никогда не пропускать кадры</string>
<string name="summary_disable_frame_drop">Может уменьшить микрозависания на некоторых устройствах, но также увеличить задержку</string>
<string name="title_enable_hdr">Включить HDR (Экспериментально)</string>
<string name="summary_enable_hdr">Транслировать в HDR если игра и GPU компьютера поддерживают это. HDR требует видеокарты GTX 1000 серии или более новой.</string>
</resources>

View File

@@ -100,7 +100,7 @@
<string name="summary_resolution_list"> 过高的设定会引起串流卡顿甚至软件闪退 </string>
<string name="title_seekbar_bitrate"> 选择目标视频码率 </string>
<string name="summary_seekbar_bitrate"> 低码率减少卡顿,高码率提高画质 </string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video"> 将画面拉伸至全屏 </string>
<string name="title_checkbox_disable_warnings"> 禁用错误提示 </string>
<string name="summary_checkbox_disable_warnings"> 串流过程中禁用连接错误提示 </string>

View File

@@ -100,7 +100,7 @@
<string name="summary_resolution_list"> 過高的設定會引起串流卡頓甚至軟體閃退 </string>
<string name="title_seekbar_bitrate"> 選擇目標視頻碼率 </string>
<string name="summary_seekbar_bitrate"> 低碼率減少卡頓,高碼率提高畫質 </string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video"> 將畫面拉伸至全屏 </string>
<string name="title_checkbox_disable_warnings"> 禁用錯誤提示 </string>
<string name="summary_checkbox_disable_warnings"> 串流過程中禁用連接錯誤提示 </string>

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="resolution_names">
<item>360p 30 FPS</item>
<item>360p 60 FPS</item>
<item>720p 30 FPS</item>
<item>720p 60 FPS</item>
<item>1080p 30 FPS</item>
@@ -9,6 +11,8 @@
<item>4K 60 FPS</item>
</string-array>
<string-array name="resolution_values" translatable="false">
<item>360p30</item>
<item>360p60</item>
<item>720p30</item>
<item>720p60</item>
<item>1080p30</item>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_label" translatable="false">Moonlight</string>
<string name="app_label_root" translatable="false">Moonlight (Root)</string>
<!-- Shortcut strings -->
<string name="scut_deleted_pc">PC deleted</string>
<string name="scut_not_paired">PC not paired</string>
@@ -51,6 +54,7 @@
<string name="message_decoding_error">Moonlight has crashed due to a problem with this device\'s video decoder. Try adjusting the streaming settings if the crashes continue.</string>
<string name="title_decoding_reset">Video Settings Reset</string>
<string name="message_decoding_reset">Your device\'s video decoder continues to crash at your selected streaming settings. Your streaming settings have been reset to default.</string>
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Establishing Connection</string>
@@ -97,6 +101,7 @@
<string name="addpc_success">Successfully added computer</string>
<string name="addpc_unknown_host">Unable to resolve PC address. Make sure you didn\'t make a typo in the address.</string>
<string name="addpc_enter_ip">You must enter an IP address</string>
<string name="addpc_wrong_sitelocal">That address doesn\'t look right. You must use your router\'s public IP address for streaming over the Internet.</string>
<!-- Preferences -->
<string name="category_basic_settings">Basic Settings</string>
@@ -104,7 +109,7 @@
<string name="summary_resolution_list">Setting values too high for your device may cause lag or crashing</string>
<string name="title_seekbar_bitrate">Select target video bitrate</string>
<string name="summary_seekbar_bitrate">Lower bitrate to reduce stuttering. Raise bitrate to increase image quality.</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="suffix_seekbar_bitrate">Kbps</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>
@@ -123,13 +128,22 @@
<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="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox controller support.</string>
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox controller support</string>
<string name="title_checkbox_usb_bind_all">Override Android controller support</string>
<string name="summary_checkbox_usb_bind_all">Forces Moonlight\'s USB driver to take over all supported Xbox gamepads</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="category_on_screen_controls_settings">On-screen Controls Settings</string>
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
<string name="summary_checkbox_show_onscreen_controls">Show virtual controller overlay on touchscreen</string>
<string name="title_only_l3r3">Only show L3 and R3</string>
<string name="summary_only_l3r3">Hide all virtual buttons except L3 and R3</string>
<string name="title_reset_osc">Clear saved on-screen controls layout</string>
<string name="summary_reset_osc">Resets all on-screen controls to their default size and position</string>
<string name="dialog_title_reset_osc">Reset Layout</string>
<string name="dialog_text_reset_osc">Are you sure you want to delete your saved on-screen controls layout?</string>
<string name="toast_reset_osc_success">On-screen controls reset to default</string>
<string name="category_ui_settings">UI Settings</string>
<string name="title_language_list">Language</string>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:seekbar="http://schemas.moonlight-stream.com/apk/res/seekbar">
<PreferenceCategory android:title="@string/category_basic_settings"
android:key="category_basic_settings">
@@ -11,9 +12,11 @@
android:entryValues="@array/resolution_values"
android:defaultValue="720p60" />
<com.limelight.preferences.SeekBarPreference
android:key="seekbar_bitrate"
android:key="seekbar_bitrate_kbps"
android:dialogMessage="@string/summary_seekbar_bitrate"
android:max="100"
seekbar:min="500"
seekbar:step="500"
android:max="100000"
android:summary="@string/summary_seekbar_bitrate"
android:text="@string/suffix_seekbar_bitrate"
android:title="@string/title_seekbar_bitrate" />
@@ -56,21 +59,40 @@
android:title="@string/title_checkbox_xb1_driver"
android:summary="@string/summary_checkbox_xb1_driver"
android:defaultValue="true" />
<CheckBoxPreference
android:key="checkbox_usb_bind_all"
android:dependency="checkbox_usb_driver"
android:title="@string/title_checkbox_usb_bind_all"
android:summary="@string/summary_checkbox_usb_bind_all"
android:defaultValue="false" />
<CheckBoxPreference
android:key="checkbox_mouse_emulation"
android:title="@string/title_checkbox_mouse_emulation"
android:summary="@string/summary_checkbox_mouse_emulation"
android:defaultValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
android:key="category_onscreen_controls">
<CheckBoxPreference
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue="false"
android:key="checkbox_show_onscreen_controls"
android:summary="@string/summary_checkbox_show_onscreen_controls"
android:title="@string/title_checkbox_show_onscreen_controls" />
<CheckBoxPreference
android:defaultValue="false"
android:dependency="checkbox_show_onscreen_controls"
android:key="checkbox_only_show_L3R3"
android:summary="@string/summary_only_l3r3"
android:title="@string/title_only_l3r3" />
<com.limelight.preferences.ConfirmDeleteOscPreference
android:title="@string/title_reset_osc"
android:summary="@string/summary_reset_osc"
android:dialogTitle="@string/dialog_title_reset_osc"
android:dialogMessage="@string/dialog_text_reset_osc"
android:positiveButtonText="@string/yes"
android:negativeButtonText="@string/no"
/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_host_settings">
<CheckBoxPreference

View File

@@ -2,5 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Non-root application name -->
<application android:label="Moonlight" />
<!-- FIXME: We should set extractNativeLibs=false but this breaks installation on the Fire TV 3 -->
<application android:label="@string/app_label" />
</manifest>

View File

@@ -2,5 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Root application name -->
<application android:label="Moonlight (Root)" />
<!-- Ensure native libraries are always extracted for root builds,
since we must invoke the evdev_reader binary ourselves -->
<application
android:label="@string/app_label_root"
android:extractNativeLibs="true" />
</manifest>

View File

@@ -2,6 +2,7 @@
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
@@ -11,5 +12,6 @@ buildscript {
allprojects {
repositories {
jcenter()
google()
}
}