mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-02-16 18:40:46 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36191781ed | ||
|
|
61b6a49669 | ||
|
|
e97845e46e | ||
|
|
6bba68207d | ||
|
|
0e17cccc06 | ||
|
|
918e922e40 | ||
|
|
a08854ddfd | ||
|
|
eb6f15c2b7 | ||
|
|
2cd9e31684 | ||
|
|
791d6624e2 | ||
|
|
af41021271 | ||
|
|
d726d939f4 | ||
|
|
748085e7bb | ||
|
|
d57d19174b | ||
|
|
efebe1828a | ||
|
|
06007e0597 | ||
|
|
3a868045d7 | ||
|
|
e0a7ff1880 | ||
|
|
88d43bbd40 | ||
|
|
30ff319b13 | ||
|
|
9a0f48b799 | ||
|
|
b52c8a1a8f | ||
|
|
3fde115670 | ||
|
|
b6f4d8ff1e | ||
|
|
a7d85a7dd5 | ||
|
|
9b238ab6c3 | ||
|
|
f82ee97c05 | ||
|
|
35fb96f9f4 | ||
|
|
37371906d5 | ||
|
|
83a9539f4b | ||
|
|
b214fe5301 | ||
|
|
57779b4e89 | ||
|
|
547932f8b2 | ||
|
|
762fa0fe2f | ||
|
|
9cedc57df2 | ||
|
|
ba81f8096a | ||
|
|
c4fa654166 | ||
|
|
8ac440b68b | ||
|
|
165386b941 | ||
|
|
3a7398f321 | ||
|
|
ebb1d0dfa2 | ||
|
|
1ca1ed5d20 | ||
|
|
b416bafb78 | ||
|
|
3a301b74a6 | ||
|
|
71d463f063 | ||
|
|
1fae816223 | ||
|
|
989d6fc169 | ||
|
|
381509b3a6 | ||
|
|
d8ae40376e | ||
|
|
4ea93f5e68 | ||
|
|
cd84c8f30e | ||
|
|
8d4cdca7c3 | ||
|
|
c0239c36fd | ||
|
|
9d9f729e42 | ||
|
|
6c5fe18b6e | ||
|
|
1994bf6522 | ||
|
|
31381e5664 | ||
|
|
fac1b1d7e5 | ||
|
|
40c406051c | ||
|
|
8bac873e67 | ||
|
|
a170e1efd7 | ||
|
|
17bffa8d78 | ||
|
|
289222749b | ||
|
|
81d84600d4 | ||
|
|
0b15fd582d | ||
|
|
cbe4a1cde6 | ||
|
|
89ef16c02e | ||
|
|
58b6ed8d00 | ||
|
|
7d01e1a7a4 | ||
|
|
ab769a1606 | ||
|
|
3ac9abbab1 | ||
|
|
288efd0726 | ||
|
|
d2d0ed65d6 | ||
|
|
e697ed72db | ||
|
|
b657c746be | ||
|
|
947f8db2d5 | ||
|
|
15857efd36 | ||
|
|
3fd0f20e10 | ||
|
|
a2e64fd7df | ||
|
|
a620dc7d0c | ||
|
|
9d7a28e408 | ||
|
|
3244344fc7 | ||
|
|
75057f2d39 | ||
|
|
bbec3402d9 | ||
|
|
dcf4dac8dd | ||
|
|
d98f484aaf | ||
|
|
0218a9ce14 | ||
|
|
0ec6dcd67e | ||
|
|
88f9b68db7 | ||
|
|
3c2fd32d1e | ||
|
|
6557cba307 | ||
|
|
ae6f797436 | ||
|
|
3442a64f4d | ||
|
|
37ddccde0c | ||
|
|
ffc59c6bd6 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
#built application files
|
||||
# built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
*.aab
|
||||
output.json
|
||||
out/
|
||||
|
||||
# files for the dex VM
|
||||
*.dex
|
||||
@@ -30,7 +33,7 @@ Thumbs.db
|
||||
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
|
||||
.gradle
|
||||
build/
|
||||
app/app.iml
|
||||
*.iml
|
||||
|
||||
# Compiled JNI libraries folder
|
||||
**/jniLibs
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
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.3'
|
||||
buildToolsVersion '28.0.0'
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 27
|
||||
targetSdkVersion 28
|
||||
|
||||
versionName "5.6.6"
|
||||
versionCode = 146
|
||||
versionName "5.8.2"
|
||||
versionCode = 167
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
@@ -49,9 +46,27 @@ android {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
// Avoid splitting by language, since we allow users
|
||||
// to manually switch language in settings.
|
||||
enableSplit = false
|
||||
}
|
||||
density {
|
||||
// FIXME: This should not be neccessary but we get
|
||||
// weird crashes due to missing drawable resources
|
||||
// when this split is enabled.
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
|
||||
minifyEnabled true
|
||||
useProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
// To whomever is releasing/using an APK in release mode with
|
||||
@@ -86,8 +101,8 @@ android {
|
||||
// TL;DR: Leave the following line alone!
|
||||
applicationIdSuffix ".unofficial"
|
||||
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
app/proguard-rules.pro
vendored
Normal file
27
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Don't obfuscate code
|
||||
-dontobfuscate
|
||||
|
||||
# Our code
|
||||
-keep class com.limelight.binding.input.evdev.* {*;}
|
||||
|
||||
# Moonlight common
|
||||
-keep class com.limelight.nvstream.jni.* {*;}
|
||||
|
||||
# Okio
|
||||
-keep class sun.misc.Unsafe {*;}
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.* {*;}
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.util.* {*;}
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.rsa.* {*;}
|
||||
-keep class org.bouncycastle.jcajce.provider.digest.** {*;}
|
||||
-keep class org.bouncycastle.jcajce.provider.symmetric.** {*;}
|
||||
-keep class org.bouncycastle.jcajce.spec.* {*;}
|
||||
-keep class org.bouncycastle.jce.** {*;}
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# jMDNS
|
||||
-dontwarn javax.jmdns.impl.DNSCache
|
||||
7
app/src/debug/res/values/strings.xml
Normal file
7
app/src/debug/res/values/strings.xml
Normal 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>
|
||||
@@ -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"
|
||||
@@ -98,10 +99,11 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<!-- This will fall back to sensorLandscape at runtime on Android 4.2 and below -->
|
||||
<activity
|
||||
android:name=".Game"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:noHistory="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:resizeableActivity="true"
|
||||
|
||||
@@ -69,6 +69,13 @@ public class AppViewShortcutTrampoline extends Activity {
|
||||
blockingLoadSpinner = null;
|
||||
}
|
||||
|
||||
// If the managerBinder was destroyed before this callback,
|
||||
// just finish the activity.
|
||||
if (managerBinder == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.state == ComputerDetails.State.ONLINE) {
|
||||
// Close this activity
|
||||
finish();
|
||||
|
||||
@@ -39,6 +39,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.input.InputManager;
|
||||
@@ -69,7 +71,7 @@ import android.widget.Toast;
|
||||
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||
OnSystemUiVisibilityChangeListener, GameGestures
|
||||
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks
|
||||
{
|
||||
private int lastMouseX = Integer.MIN_VALUE;
|
||||
private int lastMouseY = Integer.MIN_VALUE;
|
||||
@@ -95,6 +97,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean displayedFailureDialog = false;
|
||||
private boolean connecting = false;
|
||||
private boolean connected = false;
|
||||
private boolean surfaceCreated = false;
|
||||
private boolean attemptedConnection = false;
|
||||
|
||||
private InputCaptureProvider inputCaptureProvider;
|
||||
private int modifierFlags = 0;
|
||||
@@ -142,10 +146,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// We don't want a title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
// Full-screen and don't let the display go off
|
||||
getWindow().addFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
// Full-screen
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
// If we're going to use immersive mode, we want to have
|
||||
// the entire screen
|
||||
@@ -158,6 +160,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
|
||||
}
|
||||
|
||||
// We specified userLandscape in the manifest which isn't supported until 4.3,
|
||||
// so we must fall back at runtime to sensorLandscape.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
}
|
||||
|
||||
// Listen for UI visibility events
|
||||
getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
|
||||
|
||||
@@ -175,11 +183,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && prefConfig.stretchVideo) {
|
||||
// Allow the activity to layout under notches if the fill-screen option
|
||||
// was turned on by the user
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
|
||||
// Listen for events on the game surface
|
||||
streamView = findViewById(R.id.surfaceView);
|
||||
streamView.setOnGenericMotionListener(this);
|
||||
streamView.setOnTouchListener(this);
|
||||
streamView.setInputCallbacks(this);
|
||||
|
||||
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
||||
|
||||
@@ -296,8 +311,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
int gamepadMask = ControllerHandler.getAttachedControllerMask(this);
|
||||
if (!prefConfig.multiController && gamepadMask != 0) {
|
||||
// If any gamepads are present in non-MC mode, set only gamepad 1.
|
||||
if (!prefConfig.multiController) {
|
||||
// Always set gamepad 1 present for when multi-controller is
|
||||
// disabled for games that don't properly support detection
|
||||
// of gamepads removed and replugged at runtime.
|
||||
gamepadMask = 1;
|
||||
}
|
||||
if (prefConfig.onscreenController) {
|
||||
@@ -350,7 +367,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.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 || prefConfig.width <= 1920) ? 1024 : 1292)
|
||||
@@ -367,7 +384,7 @@ 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);
|
||||
@@ -415,19 +432,46 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
streamView.getHolder().addCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (virtualController != null) {
|
||||
// Refresh layout of OSC for possible new screen size
|
||||
virtualController.refreshLayout();
|
||||
|
||||
// Hide OSC in PiP
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (isInPictureInPictureMode()) {
|
||||
virtualController.hide();
|
||||
}
|
||||
else {
|
||||
virtualController.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserLeaveHint() {
|
||||
super.onUserLeaveHint();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (prefConfig.enablePip && connected) {
|
||||
enterPictureInPictureMode(
|
||||
new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height))
|
||||
.setSourceRectHint(new Rect(
|
||||
streamView.getLeft(), streamView.getTop(),
|
||||
streamView.getRight(), streamView.getBottom()))
|
||||
.build());
|
||||
try {
|
||||
// This has thrown all sorts of weird exceptions on Samsung devices
|
||||
// running Oreo. Just eat them and close gracefully on leave, rather
|
||||
// than crashing.
|
||||
enterPictureInPictureMode(
|
||||
new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height))
|
||||
.setSourceRectHint(new Rect(
|
||||
streamView.getLeft(), streamView.getTop(),
|
||||
streamView.getRight(), streamView.getBottom()))
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,18 +823,30 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
return handleKeyDown(event) || super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent event) {
|
||||
// Pass-through virtual navigation keys
|
||||
if ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle a synthetic back button event that some Android OS versions
|
||||
// create as a result of a right-click. This event WILL repeat if
|
||||
// the right mouse button is held down, so we ignore those.
|
||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
|
||||
event.getRepeatCount() == 0) {
|
||||
conn.sendMouseButtonDown(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);
|
||||
if (detectedGamepad || (event.getDevice() == null ||
|
||||
event.getDevice().getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC
|
||||
)) {
|
||||
if (ControllerHandler.isGameControllerDevice(event.getDevice())) {
|
||||
// Always try the controller handler first, unless it's an alphanumeric keyboard device.
|
||||
// Otherwise, controller handler will eat keyboard d-pad events.
|
||||
handled = controllerHandler.handleButtonDown(event);
|
||||
@@ -800,17 +856,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Try the keyboard handler
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
if (translated == 0) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let this method take duplicate key down events
|
||||
if (handleSpecialKeys(keyCode, true)) {
|
||||
if (handleSpecialKeys(event.getKeyCode(), true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState(event));
|
||||
@@ -821,17 +877,27 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
return handleKeyUp(event) || super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent event) {
|
||||
// Pass-through virtual navigation keys
|
||||
if ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||
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);
|
||||
if (detectedGamepad || (event.getDevice() == null ||
|
||||
event.getDevice().getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC
|
||||
)) {
|
||||
if (ControllerHandler.isGameControllerDevice(event.getDevice())) {
|
||||
// Always try the controller handler first, unless it's an alphanumeric keyboard device.
|
||||
// Otherwise, controller handler will eat keyboard d-pad events.
|
||||
handled = controllerHandler.handleButtonUp(event);
|
||||
@@ -841,16 +907,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Try the keyboard handler
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
if (translated == 0) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handleSpecialKeys(keyCode, false)) {
|
||||
if (handleSpecialKeys(event.getKeyCode(), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event));
|
||||
@@ -893,6 +959,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
{
|
||||
// This case is for mice
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
(event.getPointerCount() >= 1 &&
|
||||
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE))
|
||||
{
|
||||
@@ -908,6 +975,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||
conn.sendMouseScroll(vScrollClicks);
|
||||
}
|
||||
else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER ||
|
||||
event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
|
||||
// On some devices (Galaxy S8 without Oreo pointer capture), we can
|
||||
// get spurious ACTION_HOVER_ENTER events when right clicking with
|
||||
// incorrect X and Y coordinates. Just eat this event without processing it.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
@@ -947,13 +1021,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
lastMouseX = (int)event.getX();
|
||||
lastMouseY = (int)event.getY();
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// We get a normal (non-relative) MotionEvent when starting pointer capture to synchronize the
|
||||
// location of the cursor with our app. We don't want this, so we must discard this event.
|
||||
lastMouseX = (int)event.getX();
|
||||
lastMouseY = (int)event.getY();
|
||||
}
|
||||
else {
|
||||
// First process the history
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
|
||||
}
|
||||
|
||||
// Now process the current values
|
||||
// Don't process the history. We just want the current position now.
|
||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||
}
|
||||
|
||||
@@ -1101,10 +1176,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageStarting(String stage) {
|
||||
if (spinner != null) {
|
||||
spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage);
|
||||
}
|
||||
public void stageStarting(final String stage) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (spinner != null) {
|
||||
spinner.setMessage(getResources().getString(R.string.conn_starting) + " " + stage);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1129,70 +1209,78 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageFailed(String stage, long errorCode) {
|
||||
if (spinner != null) {
|
||||
spinner.dismiss();
|
||||
spinner = null;
|
||||
}
|
||||
public void stageFailed(final String stage, final long errorCode) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (spinner != null) {
|
||||
spinner.dismiss();
|
||||
spinner = null;
|
||||
}
|
||||
|
||||
// Enable cursor visibility again
|
||||
inputCaptureProvider.disableCapture();
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
LimeLog.severe(stage + " failed: " + errorCode);
|
||||
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
LimeLog.severe(stage+" failed: "+errorCode);
|
||||
|
||||
// If video initialization failed and the surface is still valid, display extra information for the user
|
||||
if (stage.contains("video") && streamView.getHolder().getSurface().isValid()) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// If video initialization failed and the surface is still valid, display extra information for the user
|
||||
if (stage.contains("video") && streamView.getHolder().getSurface().isValid()) {
|
||||
Toast.makeText(Game.this, "Video decoder failed to initialize. Your device may not support the selected resolution.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
|
||||
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
|
||||
}
|
||||
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title),
|
||||
getResources().getString(R.string.conn_error_msg) + " " + stage, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionTerminated(long errorCode) {
|
||||
// Enable cursor visibility again
|
||||
inputCaptureProvider.disableCapture();
|
||||
public void connectionTerminated(final long errorCode) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Let the display go to sleep now
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
LimeLog.severe("Connection terminated: "+errorCode);
|
||||
stopConnection();
|
||||
// Enable cursor visibility again
|
||||
inputCaptureProvider.disableCapture();
|
||||
|
||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
||||
getResources().getString(R.string.conn_terminated_msg), true);
|
||||
}
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
LimeLog.severe("Connection terminated: " + errorCode);
|
||||
stopConnection();
|
||||
|
||||
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
||||
getResources().getString(R.string.conn_terminated_msg), true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionStarted() {
|
||||
if (spinner != null) {
|
||||
spinner.dismiss();
|
||||
spinner = null;
|
||||
}
|
||||
|
||||
connecting = false;
|
||||
connected = true;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (spinner != null) {
|
||||
spinner.dismiss();
|
||||
spinner = null;
|
||||
}
|
||||
|
||||
connected = true;
|
||||
connecting = false;
|
||||
|
||||
// Hide the mouse cursor now. Doing it before
|
||||
// dismissing the spinner seems to be undone
|
||||
// when the spinner gets displayed.
|
||||
inputCaptureProvider.enableCapture();
|
||||
|
||||
// Keep the display on
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
hideSystemUi(1000);
|
||||
}
|
||||
});
|
||||
|
||||
hideSystemUi(1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1219,12 +1307,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
}
|
||||
if (!surfaceCreated) {
|
||||
throw new IllegalStateException("Surface changed before creation!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (!connected && !connecting) {
|
||||
connecting = true;
|
||||
if (!attemptedConnection) {
|
||||
attemptedConnection = true;
|
||||
|
||||
decoderRenderer.setRenderTarget(holder);
|
||||
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||
@@ -1232,12 +1320,23 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
// Let the decoder know immediately that the surface is gone
|
||||
decoderRenderer.prepareForStop();
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
surfaceCreated = true;
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
stopConnection();
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (!surfaceCreated) {
|
||||
throw new IllegalStateException("Surface destroyed before creation!");
|
||||
}
|
||||
|
||||
if (attemptedConnection) {
|
||||
// Let the decoder know immediately that the surface is gone
|
||||
decoderRenderer.prepareForStop();
|
||||
|
||||
if (connected) {
|
||||
stopConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -452,7 +452,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
@@ -18,9 +19,11 @@ 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;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@@ -50,18 +53,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private final GameGestures gestures;
|
||||
private boolean hasGameController;
|
||||
|
||||
private final boolean multiControllerEnabled;
|
||||
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) {
|
||||
@@ -148,6 +151,30 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
onInputDeviceAdded(deviceId);
|
||||
}
|
||||
|
||||
private static boolean hasJoystickAxes(InputDevice device) {
|
||||
return (device.getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK &&
|
||||
getMotionRangeForJoystickAxis(device, MotionEvent.AXIS_X) != null &&
|
||||
getMotionRangeForJoystickAxis(device, MotionEvent.AXIS_Y) != null;
|
||||
}
|
||||
|
||||
private static boolean hasGamepadButtons(InputDevice device) {
|
||||
return (device.getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
|
||||
}
|
||||
|
||||
public static boolean isGameControllerDevice(InputDevice device) {
|
||||
if (device == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasJoystickAxes(device) || hasGamepadButtons(device)) {
|
||||
// Has real joystick axes or gamepad buttons
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, we'll try anything that claims to be a non-alphabetic keyboard
|
||||
return device.getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC;
|
||||
}
|
||||
|
||||
public static short getAttachedControllerMask(Context context) {
|
||||
int count = 0;
|
||||
short mask = 0;
|
||||
@@ -160,18 +187,22 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0) {
|
||||
if (hasJoystickAxes(dev)) {
|
||||
LimeLog.info("Counting InputDevice: "+dev.getName());
|
||||
mask |= 1 << count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Count all USB devices that match our drivers
|
||||
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||
if (UsbDriverService.shouldClaimDevice(dev)) {
|
||||
LimeLog.info("Counting UsbDevice: "+dev.getDeviceName());
|
||||
mask |= 1 << count++;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +246,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");
|
||||
@@ -239,7 +270,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");
|
||||
@@ -279,6 +310,79 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return context;
|
||||
}
|
||||
|
||||
private static boolean isExternal(InputDevice dev) {
|
||||
try {
|
||||
// Landroid/view/InputDevice;->isExternal()Z is on the light graylist in Android P
|
||||
return (Boolean)dev.getClass().getMethod("isExternal").invoke(dev);
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ClassCastException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Answer true if we don't know
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreBack(InputDevice dev) {
|
||||
String devName = dev.getName();
|
||||
|
||||
// The Serval has a Select button but the framework doesn't
|
||||
// know about that because it uses a non-standard scancode.
|
||||
if (devName.contains("Razer Serval")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Classify this device as a remote by name if it has no joystick axes
|
||||
if (getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_X) == null &&
|
||||
getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Y) == null &&
|
||||
devName.toLowerCase().contains("remote")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, dynamically try to determine whether we should allow this
|
||||
// back button to function for navigation.
|
||||
//
|
||||
// First, check if this is an internal device we're being called on.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !isExternal(dev)) {
|
||||
InputManager im = (InputManager) activityContext.getSystemService(Context.INPUT_SERVICE);
|
||||
|
||||
boolean foundInternalGamepad = false;
|
||||
boolean foundInternalSelect = false;
|
||||
for (int id : im.getInputDeviceIds()) {
|
||||
InputDevice currentDev = im.getInputDevice(id);
|
||||
|
||||
// Ignore external devices
|
||||
if (currentDev == null || isExternal(currentDev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note that we are explicitly NOT excluding the current device we're examining here,
|
||||
// since the other gamepad buttons may be on our current device and that's fine.
|
||||
boolean[] keys = currentDev.hasKeys(KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_A);
|
||||
if (keys[0]) {
|
||||
foundInternalSelect = true;
|
||||
}
|
||||
if (keys[1]) {
|
||||
foundInternalGamepad = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow the back button to function for navigation if we either:
|
||||
// a) have no internal gamepad (most phones)
|
||||
// b) have an internal gamepad but also have an internal select button (GPD XD)
|
||||
// but not:
|
||||
// c) have an internal gamepad but no internal select button (NVIDIA SHIELD Portable)
|
||||
return !foundInternalGamepad || foundInternalSelect;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private InputDeviceContext createInputDeviceContextForDevice(InputDevice dev) {
|
||||
InputDeviceContext context = new InputDeviceContext();
|
||||
String devName = dev.getName();
|
||||
@@ -411,6 +515,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
context.ignoreBack = shouldIgnoreBack(dev);
|
||||
|
||||
if (devName != null) {
|
||||
// For the Nexus Player (and probably other ATV devices), we should
|
||||
// use the back button as start since it doesn't have a start/menu button
|
||||
@@ -430,30 +536,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// so we increase the deadzone on them to minimize this
|
||||
context.triggerDeadzone = 0.30f;
|
||||
}
|
||||
// Classify this device as a remote by name
|
||||
else if (devName.toLowerCase().contains("remote")) {
|
||||
// It's only a remote if it doesn't any sticks
|
||||
if (!context.hasJoystickAxes) {
|
||||
context.ignoreBack = true;
|
||||
}
|
||||
}
|
||||
// SHIELD controllers will use small stick deadzones
|
||||
else if (devName.contains("SHIELD")) {
|
||||
context.leftStickDeadzoneRadius = 0.07f;
|
||||
context.rightStickDeadzoneRadius = 0.07f;
|
||||
}
|
||||
// Samsung's face buttons appear as a non-virtual button so we'll explicitly ignore
|
||||
// back presses on this device. The Goodix buttons on the Nokia 6 also appear
|
||||
// non-virtual so we'll ignore those too.
|
||||
else if (devName.equals("sec_touchscreen") || devName.equals("sec_touchkey") ||
|
||||
devName.equals("goodix_fp")) {
|
||||
context.ignoreBack = true;
|
||||
}
|
||||
// The Serval has a couple of unknown buttons that are start and select. It also has
|
||||
// a back button which we want to ignore since there's already a select button.
|
||||
else if (devName.contains("Razer Serval")) {
|
||||
context.isServal = true;
|
||||
context.ignoreBack = true;
|
||||
}
|
||||
// The Xbox One S Bluetooth controller has some mappings that need fixing up.
|
||||
// However, Microsoft released a firmware update with no change to VID/PID
|
||||
@@ -522,7 +613,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
private short getActiveControllerMask() {
|
||||
if (multiControllerEnabled) {
|
||||
if (prefConfig.multiController) {
|
||||
return (short)(currentControllers | initialControllers);
|
||||
}
|
||||
else {
|
||||
@@ -609,6 +700,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);
|
||||
@@ -820,16 +921,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// to normal behavior, so ignore triggersIdleNegative for each trigger until
|
||||
// first touch.
|
||||
if (lt != 0) {
|
||||
context.leftTriggerUsed = true;
|
||||
context.leftTriggerAxisUsed = true;
|
||||
}
|
||||
if (rt != 0) {
|
||||
context.rightTriggerUsed = true;
|
||||
context.rightTriggerAxisUsed = true;
|
||||
}
|
||||
if (context.triggersIdleNegative) {
|
||||
if (context.leftTriggerUsed) {
|
||||
if (context.leftTriggerAxisUsed) {
|
||||
lt = (lt + 1) / 2;
|
||||
}
|
||||
if (context.rightTriggerUsed) {
|
||||
if (context.rightTriggerAxisUsed) {
|
||||
rt = (rt + 1) / 2;
|
||||
}
|
||||
}
|
||||
@@ -975,7 +1076,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;
|
||||
@@ -1024,15 +1126,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
if (context.leftTriggerAxis >= 0) {
|
||||
// Suppress this digital event if an analog trigger is present
|
||||
if (context.leftTriggerAxisUsed) {
|
||||
// Suppress this digital event if an analog trigger is active
|
||||
return true;
|
||||
}
|
||||
context.leftTrigger = 0;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
if (context.rightTriggerAxis >= 0) {
|
||||
// Suppress this digital event if an analog trigger is present
|
||||
if (context.rightTriggerAxisUsed) {
|
||||
// Suppress this digital event if an analog trigger is active
|
||||
return true;
|
||||
}
|
||||
context.rightTrigger = 0;
|
||||
@@ -1144,15 +1246,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.inputMap |= ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
if (context.leftTriggerAxis >= 0) {
|
||||
// Suppress this digital event if an analog trigger is present
|
||||
if (context.leftTriggerAxisUsed) {
|
||||
// Suppress this digital event if an analog trigger is active
|
||||
return true;
|
||||
}
|
||||
context.leftTrigger = (byte)0xFF;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
if (context.rightTriggerAxis >= 0) {
|
||||
// Suppress this digital event if an analog trigger is present
|
||||
if (context.rightTriggerAxisUsed) {
|
||||
// Suppress this digital event if an analog trigger is active
|
||||
return true;
|
||||
}
|
||||
context.rightTrigger = (byte)0xFF;
|
||||
@@ -1283,7 +1385,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public int leftTriggerAxis = -1;
|
||||
public int rightTriggerAxis = -1;
|
||||
public boolean triggersIdleNegative;
|
||||
public boolean leftTriggerUsed, rightTriggerUsed;
|
||||
public boolean leftTriggerAxisUsed, rightTriggerAxisUsed;
|
||||
|
||||
public int hatXAxis = -1;
|
||||
public int hatYAxis = -1;
|
||||
|
||||
@@ -43,11 +43,19 @@ public class AndroidNativePointerCaptureProvider extends InputCaptureProvider {
|
||||
|
||||
@Override
|
||||
public float getRelativeAxisX(MotionEvent event) {
|
||||
return event.getX();
|
||||
float x = event.getX();
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
x += event.getHistoricalX(i);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
return event.getY();
|
||||
float y = event.getY();
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
y += event.getHistoricalY(i);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.R;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -27,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();
|
||||
@@ -119,7 +121,7 @@ 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
|
||||
@@ -194,16 +196,17 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
public static 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();
|
||||
@@ -213,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -210,7 +210,7 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
// calculate new radius sizes depending
|
||||
radius_complete = getPercent(getCorrectWidth() / 2, 90);
|
||||
radius_complete = getPercent(getCorrectWidth() / 2, 100);
|
||||
radius_dead_zone = getPercent(getCorrectWidth() / 2, 30);
|
||||
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
@@ -85,6 +86,14 @@ public class VirtualController {
|
||||
});
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
relative_layout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
relative_layout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void removeElements() {
|
||||
for (VirtualControllerElement element : elements) {
|
||||
relative_layout.removeView(element);
|
||||
@@ -116,11 +125,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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.limelight.binding.video;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.jcodec.codecs.h264.H264Utils;
|
||||
@@ -41,7 +40,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
|
||||
private MediaCodec videoDecoder;
|
||||
private Thread rendererThread;
|
||||
private Thread[] spinnerThreads;
|
||||
private boolean needsSpsBitstreamFixup, isExynos4;
|
||||
private boolean adaptivePlayback, directSubmit;
|
||||
private boolean constrainedHighProfile;
|
||||
@@ -132,14 +130,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
this.consecutiveCrashCount = consecutiveCrashCount;
|
||||
this.glRenderer = glRenderer;
|
||||
|
||||
// Disable spinner threads in battery saver mode or 4K
|
||||
if (prefs.batterySaver || prefs.height >= 2160) {
|
||||
spinnerThreads = new Thread[0];
|
||||
}
|
||||
else {
|
||||
spinnerThreads = new Thread[Runtime.getRuntime().availableProcessors()];
|
||||
}
|
||||
|
||||
avcDecoder = findAvcDecoder();
|
||||
if (avcDecoder != null) {
|
||||
LimeLog.info("Selected AVC decoder: "+avcDecoder.getName());
|
||||
@@ -217,15 +207,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
public void notifyVideoForeground() {
|
||||
startSpinnerThreads();
|
||||
foreground = true;
|
||||
}
|
||||
|
||||
public void notifyVideoBackground() {
|
||||
// Signal the spinner threads to stop but
|
||||
// don't wait for them to terminate to avoid
|
||||
// delaying the state transition
|
||||
signalSpinnerStop();
|
||||
foreground = false;
|
||||
}
|
||||
|
||||
@@ -462,73 +447,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
rendererThread.start();
|
||||
}
|
||||
|
||||
private void startSpinnerThread(final int i) {
|
||||
spinnerThreads[i] = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// This thread exists to keep the CPU at a higher DVFS state on devices
|
||||
// where the governor scales clock speed sporadically, causing dropped frames.
|
||||
//
|
||||
// Run until we notice our thread has been removed from the spinner threads
|
||||
// array. Even if we don't notice immediately, we'll notice soon enough.
|
||||
// This will also ensure we terminate even if someone has restarted spinning
|
||||
// before we realized we should stop.
|
||||
while (this == spinnerThreads[i]) {
|
||||
try {
|
||||
Thread.sleep(0, 1);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
spinnerThreads[i].setName("Spinner-"+i);
|
||||
spinnerThreads[i].setPriority(Thread.MIN_PRIORITY);
|
||||
spinnerThreads[i].start();
|
||||
}
|
||||
|
||||
private void startSpinnerThreads() {
|
||||
LimeLog.info("Using "+spinnerThreads.length+" spinner threads");
|
||||
for (int i = 0; i < spinnerThreads.length; i++) {
|
||||
if (spinnerThreads[i] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
startSpinnerThread(i);
|
||||
}
|
||||
}
|
||||
|
||||
private Thread[] signalSpinnerStop() {
|
||||
// Capture current running threads
|
||||
Thread[] runningThreads = Arrays.copyOf(spinnerThreads, spinnerThreads.length);
|
||||
|
||||
// Clear the spinner threads to signal their termination
|
||||
for (int i = 0; i < spinnerThreads.length; i++) {
|
||||
spinnerThreads[i] = null;
|
||||
}
|
||||
|
||||
// Interrupt the threads
|
||||
for (int i = 0; i < runningThreads.length; i++) {
|
||||
if (runningThreads[i] != null) {
|
||||
runningThreads[i].interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
return runningThreads;
|
||||
}
|
||||
|
||||
private void stopSpinnerThreads() {
|
||||
// Signal and wait for the threads to stop
|
||||
Thread[] runningThreads = signalSpinnerStop();
|
||||
for (int i = 0; i < runningThreads.length; i++) {
|
||||
if (runningThreads[i] != null) {
|
||||
try {
|
||||
runningThreads[i].join();
|
||||
} catch (InterruptedException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int dequeueInputBuffer() {
|
||||
int index = -1;
|
||||
long startTime;
|
||||
@@ -570,7 +488,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
@Override
|
||||
public void start() {
|
||||
startRendererThread();
|
||||
startSpinnerThreads();
|
||||
}
|
||||
|
||||
// !!! May be called even if setup()/start() fails !!!
|
||||
@@ -593,9 +510,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException ignored) { }
|
||||
|
||||
// Halt the spinner threads
|
||||
stopSpinnerThreads();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -713,12 +627,17 @@ 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;
|
||||
}
|
||||
else if (initialWidth <= 1280 && initialHeight <= 720) {
|
||||
// Max 5 buffered frames at 1280x720x60
|
||||
LimeLog.info("Patching level_idc to 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.levelIdc = 42;
|
||||
@@ -1053,7 +972,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
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";
|
||||
|
||||
@@ -71,8 +71,8 @@ public class MediaCodecHelper {
|
||||
blacklistedDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
// Blacklist software decoders that don't support H264 high profile,
|
||||
// but exclude the official AOSP emulator from this restriction.
|
||||
if (!Build.HARDWARE.equals("ranchu") || !Build.BRAND.equals("google")) {
|
||||
// but exclude the official AOSP and CrOS emulator from this restriction.
|
||||
if (!Build.HARDWARE.equals("ranchu") && !Build.HARDWARE.equals("cheets")) {
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||
}
|
||||
@@ -131,10 +131,11 @@ public class MediaCodecHelper {
|
||||
}
|
||||
|
||||
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||
// I know the Fire TV 2 works, so I'll just whitelist Amazon devices which seem
|
||||
// to actually be tested. Ugh...
|
||||
// I know the Fire TV 2 and 3 works, so I'll just whitelist Amazon devices which seem
|
||||
// to actually be tested.
|
||||
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||
whitelistedHevcDecoders.add("omx.mtk");
|
||||
whitelistedHevcDecoders.add("omx.amlogic");
|
||||
}
|
||||
|
||||
// These theoretically have good HEVC decoding capabilities (potentially better than
|
||||
@@ -165,6 +166,10 @@ public class MediaCodecHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPowerVR(String glRenderer) {
|
||||
return glRenderer.toLowerCase().contains("powervr");
|
||||
}
|
||||
|
||||
private static String getAdrenoVersionString(String glRenderer) {
|
||||
glRenderer = glRenderer.toLowerCase().trim();
|
||||
|
||||
@@ -258,6 +263,18 @@ public class MediaCodecHelper {
|
||||
else {
|
||||
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
|
||||
}
|
||||
|
||||
// Older MediaTek SoCs have issues with HEVC rendering but the newer chips with
|
||||
// PowerVR GPUs have good HEVC support.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isPowerVR(glRenderer)) {
|
||||
LimeLog.info("Added omx.mtk to HEVC decoders based on PowerVR GPU");
|
||||
whitelistedHevcDecoders.add("omx.mtk");
|
||||
|
||||
// This SoC (MT8176 in GPD XD+) supports AVC RFI too, but the maxNumReferenceFrames setting
|
||||
// required to make it work adds a huge amount of latency.
|
||||
LimeLog.info("Added omx.mtk to RFI list for HEVC");
|
||||
refFrameInvalidationHevcPrefixes.add("omx.mtk");
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
@@ -336,7 +353,7 @@ public class MediaCodecHelper {
|
||||
|
||||
// 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")) {
|
||||
if (Build.DEVICE.equals("b3") || Build.DEVICE.equals("b5")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.limelight.grid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
@@ -46,13 +45,10 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
}
|
||||
LimeLog.info("Art scaling divisor: " + scalingDivisor);
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = (int) scalingDivisor;
|
||||
|
||||
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
||||
new NetworkAssetLoader(context, uniqueId),
|
||||
new MemoryAssetLoader(),
|
||||
new DiskAssetLoader(context.getCacheDir()));
|
||||
new DiskAssetLoader(context));
|
||||
}
|
||||
|
||||
public void cancelQueuedOperations() {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.limelight.grid.assets;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
@@ -19,10 +23,19 @@ public class DiskAssetLoader {
|
||||
private static final int STANDARD_ASSET_WIDTH = 300;
|
||||
private static final int STANDARD_ASSET_HEIGHT = 400;
|
||||
|
||||
private final boolean isLowRamDevice;
|
||||
private final File cacheDir;
|
||||
|
||||
public DiskAssetLoader(File cacheDir) {
|
||||
this.cacheDir = cacheDir;
|
||||
public DiskAssetLoader(Context context) {
|
||||
this.cacheDir = context.getCacheDir();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
this.isLowRamDevice =
|
||||
((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).isLowRamDevice();
|
||||
}
|
||||
else {
|
||||
// Use conservative low RAM behavior on very old devices
|
||||
this.isLowRamDevice = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
@@ -66,28 +79,55 @@ public class DiskAssetLoader {
|
||||
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;
|
||||
Bitmap bmp;
|
||||
|
||||
// For OSes prior to P, we have to use the ugly BitmapFactory API
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
// 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);
|
||||
if (isLowRamDevice) {
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
options.inDither = true;
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
options.inPreferredConfig = Bitmap.Config.HARDWARE;
|
||||
}
|
||||
|
||||
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
if (bmp != null) {
|
||||
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
||||
}
|
||||
}
|
||||
|
||||
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("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
||||
else {
|
||||
// On P, we can get a bitmap back in one step with ImageDecoder
|
||||
try {
|
||||
bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
|
||||
@Override
|
||||
public void onHeaderDecoded(ImageDecoder imageDecoder, ImageDecoder.ImageInfo imageInfo, ImageDecoder.Source source) {
|
||||
imageDecoder.setTargetSize(STANDARD_ASSET_WIDTH, STANDARD_ASSET_HEIGHT);
|
||||
if (isLowRamDevice) {
|
||||
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return bmp;
|
||||
|
||||
@@ -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;
|
||||
@@ -17,6 +24,7 @@ import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.TextView;
|
||||
@@ -39,36 +47,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() {
|
||||
@@ -141,12 +197,7 @@ public class AddComputerManually extends Activity {
|
||||
(keyEvent != null &&
|
||||
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
|
||||
keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
|
||||
if (hostText.getText().length() == 0) {
|
||||
Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
computersToAdd.add(hostText.getText().toString().trim());
|
||||
return handleDoneEvent();
|
||||
}
|
||||
else if (actionId == EditorInfo.IME_ACTION_PREVIOUS) {
|
||||
// This is how the Fire TV dismisses the keyboard
|
||||
@@ -159,8 +210,28 @@ public class AddComputerManually extends Activity {
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.addPcButton).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
handleDoneEvent();
|
||||
}
|
||||
});
|
||||
|
||||
// Bind to the ComputerManager service
|
||||
bindService(new Intent(AddComputerManually.this,
|
||||
ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
// Returns true if the event should be eaten
|
||||
private boolean handleDoneEvent() {
|
||||
String hostAddress = hostText.getText().toString().trim();
|
||||
|
||||
if (hostAddress.length() == 0) {
|
||||
Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
computersToAdd.add(hostAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -23,17 +24,20 @@ public class PreferenceConfiguration {
|
||||
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
||||
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3";
|
||||
private static final String BATTERY_SAVER_PREF_STRING = "checkbox_battery_saver";
|
||||
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 +58,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;
|
||||
@@ -68,13 +74,20 @@ public class PreferenceConfiguration {
|
||||
public boolean listMode, smallIconMode, multiController, enable51Surround, usbDriver;
|
||||
public boolean onscreenController;
|
||||
public boolean onlyL3R3;
|
||||
public boolean batterySaver;
|
||||
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 +160,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 +171,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;
|
||||
@@ -214,10 +242,11 @@ public class PreferenceConfiguration {
|
||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
||||
config.batterySaver = prefs.getBoolean(BATTERY_SAVER_PREF_STRING, DEFAULT_BATTERY_SAVER);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -3,15 +3,21 @@ package com.limelight.ui;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
public class StreamView extends SurfaceView {
|
||||
private double desiredAspectRatio;
|
||||
private InputCallbacks inputCallbacks;
|
||||
|
||||
public void setDesiredAspectRatio(double aspectRatio) {
|
||||
this.desiredAspectRatio = aspectRatio;
|
||||
}
|
||||
|
||||
public void setInputCallbacks(InputCallbacks callbacks) {
|
||||
this.inputCallbacks = callbacks;
|
||||
}
|
||||
|
||||
public StreamView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -52,4 +58,30 @@ public class StreamView extends SurfaceView {
|
||||
|
||||
setMeasuredDimension(measuredWidth, measuredHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||
// This callbacks allows us to override dumb IME behavior like when
|
||||
// Samsung's default keyboard consumes Shift+Space. We'll process
|
||||
// the input event directly if any modifier keys are down.
|
||||
if (inputCallbacks != null && event.getModifiers() != 0) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (inputCallbacks.handleKeyDown(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
if (inputCallbacks.handleKeyUp(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onKeyPreIme(keyCode, event);
|
||||
}
|
||||
|
||||
public interface InputCallbacks {
|
||||
boolean handleKeyUp(KeyEvent event);
|
||||
boolean handleKeyDown(KeyEvent event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.limelight.R;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
@@ -57,6 +59,16 @@ public class UiHelper {
|
||||
rootView.setPadding(horizontalPaddingPixels, verticalPaddingPixels,
|
||||
horizontalPaddingPixels, verticalPaddingPixels);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// Allow this non-streaming activity to layout under notches.
|
||||
//
|
||||
// We should NOT do this for the Game activity unless
|
||||
// the user specifically opts in, because it can obscure
|
||||
// parts of the streaming surface.
|
||||
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
}
|
||||
|
||||
public static void showDecoderCrashDialog(Activity activity) {
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/manuallyAddPcText"
|
||||
android:layout_toLeftOf="@+id/addPcButton"
|
||||
android:layout_toStartOf="@+id/addPcButton"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:ems="10"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
@@ -34,5 +34,15 @@
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
|
||||
<Button
|
||||
android:id="@+id/addPcButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_below="@+id/manuallyAddPcText"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:text="@android:string/ok"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- Portrait orientation only -->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">Sì</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,74 @@
|
||||
<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_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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,82 @@
|
||||
<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_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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -48,7 +51,7 @@
|
||||
Using remote desktop software can also cause this error. Try rebooting your machine or reinstalling GFE.
|
||||
</string>
|
||||
<string name="title_decoding_error">Video Decoder Crashed</string>
|
||||
<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="message_decoding_error">Moonlight has crashed due to an incompatibility with this device\'s video decoder. Ensure GeForce Experience is updated to the latest version on your PC. 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>
|
||||
@@ -98,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>
|
||||
@@ -105,12 +109,10 @@
|
||||
<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>
|
||||
<string name="title_checkbox_battery_saver">Battery saver</string>
|
||||
<string name="summary_checkbox_battery_saver">Uses less battery, but may increase stuttering</string>
|
||||
<string name="title_checkbox_enable_pip">Enable Picture-in-Picture observer mode</string>
|
||||
<string name="summary_checkbox_enable_pip">Allows the stream to be viewed (but not controlled) while multitasking</string>
|
||||
|
||||
@@ -120,17 +122,26 @@
|
||||
|
||||
<string name="category_gamepad_settings">Gamepad Settings</string>
|
||||
<string name="title_checkbox_multi_controller">Multiple controller support</string>
|
||||
<string name="summary_checkbox_multi_controller">When unchecked, all controllers appear as one</string>
|
||||
<string name="summary_checkbox_multi_controller">Uncheck for games with controller detection issues</string>
|
||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</string>
|
||||
<string name="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>
|
||||
|
||||
8
app/src/main/res/xml/network_security_config.xml
Normal file
8
app/src/main/res/xml/network_security_config.xml
Normal 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>
|
||||
@@ -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" />
|
||||
@@ -21,11 +24,6 @@
|
||||
android:key="checkbox_stretch_video"
|
||||
android:title="@string/title_checkbox_stretch_video"
|
||||
android:defaultValue="false" />
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_battery_saver"
|
||||
android:title="@string/title_checkbox_battery_saver"
|
||||
android:summary="@string/summary_checkbox_battery_saver"
|
||||
android:defaultValue="false" />
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_enable_pip"
|
||||
android:title="@string/title_checkbox_enable_pip"
|
||||
@@ -56,21 +54,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
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
|
||||
<!-- Non-root application name -->
|
||||
<!-- FIXME: We should set extractNativeLibs=false but this breaks installation on the Fire TV 3 -->
|
||||
<application android:label="Moonlight" />
|
||||
<application android:label="@string/app_label" />
|
||||
</manifest>
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
<!-- Ensure native libraries are always extracted for root builds,
|
||||
since we must invoke the evdev_reader binary ourselves -->
|
||||
<application
|
||||
android:label="Moonlight (Root)"
|
||||
android:label="@string/app_label_root"
|
||||
android:extractNativeLibs="true" />
|
||||
</manifest>
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.android.tools.build:gradle:3.2.0-alpha18'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
This file serves to document some of the decoder errata when using MediaCodec hardware decoders on certain devices.
|
||||
|
||||
1. num_ref_frames is set to 16 by NVENC which causes decoders to allocate 16+ buffers. This can cause an error or lag on some devices.
|
||||
- Affected decoders: TI OMAP4, Allwinner A20
|
||||
- Affected decoders: TI OMAP4 crashes, Allwinner A20, MT8176 lags (HEVC not affected)
|
||||
|
||||
2. Some decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering fixes this latency issue.
|
||||
2. Some H.264 decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering=1 fixes this latency issue.
|
||||
- Affected decoders: NVIDIA Tegra 3 and 4, Broadcom VideoCore IV
|
||||
|
||||
3. Some decoders strictly require that you pass BUFFER_FLAG_CODEC_CONFIG and crash upon the IDR frame if you don't
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sun Sep 03 12:51:12 PDT 2017
|
||||
#Tue May 08 18:56:31 PDT 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="moonlight-android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="false" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Submodule moonlight-common updated: 21e1bc8c35...165a518adb
Reference in New Issue
Block a user