Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b581011c5 | |||
| 67bcc56c6d | |||
| 7d8cfa3c6a | |||
| f659af29da | |||
| 94b202f7b6 | |||
| ca86fdafab | |||
| 370dbb1a10 | |||
| f77543cd9b | |||
| 7104e0d725 | |||
| 230a67cac0 | |||
| a695f38974 | |||
| 151c09f098 | |||
| 8200f5690d | |||
| 77a8cf2704 | |||
| fe424961e1 | |||
| b668cb78ff | |||
| 48278419b0 | |||
| 632da03667 | |||
| d36b73fc1b | |||
| 292ed35555 | |||
| 02d0ad496f | |||
| eb2fc7af40 | |||
| 6550deedbb | |||
| 80acd9b9eb | |||
| b961636f02 | |||
| f4df0714b5 | |||
| 91dd7b7049 | |||
| 121bef7d2d | |||
| 3a9eabf50b | |||
| c8198b4091 | |||
| e4538e4a51 | |||
| b47f3ef397 | |||
| 6e096c0ac3 | |||
| ee3b4686bf | |||
| 759b77eafe | |||
| de15ec666f | |||
| 3de86f15af | |||
| 1b9dff719c | |||
| abcf4e3d4a | |||
| 9823abe686 | |||
| 723e08f69b | |||
| daf0a0891e | |||
| 49dc68f77f | |||
| f81a1c36cc | |||
| 9a11a771dd | |||
| b56a4b8b49 | |||
| 1aa963992b | |||
| 1b601324d0 | |||
| 1aea723ef0 | |||
| 1e828a10b9 | |||
| 970423f873 | |||
| 2d7493fd1e | |||
| fa7eb1c4b1 | |||
| 4916af3697 | |||
| 8484bf1cb9 | |||
| 005afb3c73 | |||
| 84b0d004b9 |
@@ -1,48 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Follow the troubleshooting guide before reporting a bug
|
||||
|
||||
---
|
||||
**READ ME FIRST!**
|
||||
If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting
|
||||
|
||||
If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to reproduce**
|
||||
Any special steps that are required for the bug to appear.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
|
||||
|
||||
**Affected games**
|
||||
List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
|
||||
|
||||
**Other Moonlight clients**
|
||||
- Does the issue occur when using Moonlight on PC or iOS?
|
||||
|
||||
**Moonlight settings (please complete the following information)**
|
||||
- Have any settings been adjusted from defaults?
|
||||
- If so, which settings have been changed?
|
||||
- Does the problem still occur after reverting settings back to default?
|
||||
|
||||
**Gamepad-related issues (please complete if problem is gamepad-related)**
|
||||
- Do you have any gamepads connected to your host PC directly?
|
||||
- If gamepad input is not working, does it work if you use Moonlight's on-screen controls?
|
||||
- Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad?
|
||||
- Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide
|
||||
|
||||
**Device details (please complete the following information)**
|
||||
- Android version: [e.g. Android 10]
|
||||
- Device model: [e.g. Samsung Galaxy S21]
|
||||
|
||||
**Server PC details (please complete the following information)**
|
||||
- OS: [e.g. Windows 10 1809]
|
||||
- GeForce Experience version: [e.g. 3.16.0.140]
|
||||
- Nvidia GPU driver: [e.g. 417.35]
|
||||
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
|
||||
|
||||
**Additional context**
|
||||
Anything else you think may be relevant to the issue or special about your specific setup.
|
||||
@@ -0,0 +1,176 @@
|
||||
name: Bug report
|
||||
description: Follow the troubleshooting guide before reporting a bug
|
||||
title: "[Issue]: "
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out this bug form!
|
||||
|
||||
**READ ME FIRST!**
|
||||
If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting
|
||||
|
||||
If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
|
||||
- type: textarea
|
||||
id: describe-bug
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Any special steps that are required for the bug to appear.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: affected-games
|
||||
attributes:
|
||||
label: Affected games
|
||||
description: List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: other-clients
|
||||
attributes:
|
||||
label: Other Moonlight clients
|
||||
description: Does the issue occur when using Moonlight on PC or iOS?
|
||||
options:
|
||||
- "PC"
|
||||
- "iOS"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: settings-adjusted
|
||||
attributes:
|
||||
label: Moonlight adjusted settings
|
||||
description: Have any settings been adjusted from defaults?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: settings-adjusted-settings
|
||||
attributes:
|
||||
label: Moonlight adjusted settings (please complete the following information)
|
||||
description: If the settings have been adjusted, which settings have been changed?
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: settings-default
|
||||
attributes:
|
||||
label: Moonlight default settings
|
||||
description: Does the problem still occur after reverting settings back to default?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: gamepad-connected
|
||||
attributes:
|
||||
label: Gamepad-related connection issue
|
||||
description: Do you have any gamepads connected to your host PC directly?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: gamepad-on-screen
|
||||
attributes:
|
||||
label: Gamepad-related input issue
|
||||
description: If gamepad input is not working, does it work if you use Moonlight's on-screen controls?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: gamepad-test
|
||||
attributes:
|
||||
label: Gamepad-related streaming issue
|
||||
description: |
|
||||
Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad?
|
||||
Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: android
|
||||
attributes:
|
||||
label: Android version
|
||||
description: What is the Android version?
|
||||
placeholder: e.g. Android 10
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: device
|
||||
attributes:
|
||||
label: Device model
|
||||
description: What is the device model?
|
||||
placeholder: e.g. Samsung Galaxy S21
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-os
|
||||
attributes:
|
||||
label: Server PC OS version
|
||||
description: What is the PC OS version?
|
||||
placeholder: e.g. Windows 10 1809
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-geforce
|
||||
attributes:
|
||||
label: Server PC GeForce Experience version
|
||||
description: What is the GeForce Experience version?
|
||||
placeholder: e.g. 3.16.0.140
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-driver
|
||||
attributes:
|
||||
label: Server PC Nvidia GPU driver version
|
||||
description: What is the Nvidia GPU driver version?
|
||||
placeholder: e.g. 417.35
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-antivirus
|
||||
attributes:
|
||||
label: Server PC antivirus and firewall software
|
||||
description: Which antivirus and firewall software are installed on the Server PC?
|
||||
placeholder: e.g. Windows Defender and Windows Firewall
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: |
|
||||
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: Shell
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Anything else you think may be relevant to the issue or special about your specific setup.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature request]: "
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out this feature form!
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
+4
-2
@@ -5,12 +5,14 @@ android {
|
||||
|
||||
compileSdk 33
|
||||
|
||||
namespace 'com.limelight'
|
||||
|
||||
defaultConfig {
|
||||
minSdk 16
|
||||
targetSdk 33
|
||||
|
||||
versionName "10.11"
|
||||
versionCode = 301
|
||||
versionName "11.0"
|
||||
versionCode = 306
|
||||
|
||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||
ndk.debugSymbolLevel = 'FULL'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.limelight">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
@@ -62,6 +62,7 @@ import android.os.IBinder;
|
||||
import android.util.Rational;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
@@ -125,6 +126,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private int suppressPipRefCount = 0;
|
||||
private String pcName;
|
||||
private String appName;
|
||||
private float desiredRefreshRate;
|
||||
|
||||
private InputCaptureProvider inputCaptureProvider;
|
||||
private int modifierFlags = 0;
|
||||
@@ -246,7 +248,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
View backgroundTouchView = findViewById(R.id.backgroundTouchView);
|
||||
backgroundTouchView.setOnTouchListener(this);
|
||||
|
||||
boolean needsInputBatching = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Request unbuffered input event dispatching for all input classes we handle here.
|
||||
// Without this, input events are buffered to be delivered in lock-step with VBlank,
|
||||
@@ -265,10 +266,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
InputDevice.SOURCE_CLASS_POSITION | // Touchpads
|
||||
InputDevice.SOURCE_CLASS_TRACKBALL // Mice (pointer capture)
|
||||
);
|
||||
|
||||
// Since the OS isn't going to batch for us, we have to batch mouse events to
|
||||
// avoid triggering a bug in GeForce Experience that can lead to massive latency.
|
||||
needsInputBatching = true;
|
||||
}
|
||||
|
||||
notificationOverlayView = findViewById(R.id.notificationOverlay);
|
||||
@@ -474,14 +471,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setAudioEncryption(true)
|
||||
.setColorSpace(decoderRenderer.getPreferredColorSpace())
|
||||
.setColorRange(decoderRenderer.getPreferredColorRange())
|
||||
.setPersistGamepadsAfterDisconnect(!prefConfig.multiController)
|
||||
.build();
|
||||
|
||||
// Initialize the connection
|
||||
conn = new NvConnection(getApplicationContext(),
|
||||
new ComputerDetails.AddressTuple(host, port),
|
||||
httpsPort, uniqueId, config,
|
||||
PlatformBinding.getCryptoProvider(this), serverCert,
|
||||
needsInputBatching);
|
||||
PlatformBinding.getCryptoProvider(this), serverCert);
|
||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
||||
keyboardTranslator = new KeyboardTranslator();
|
||||
|
||||
@@ -809,6 +806,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
|
||||
boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate());
|
||||
|
||||
LimeLog.info("Current display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
||||
|
||||
for (Display.Mode candidate : display.getSupportedModes()) {
|
||||
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
|
||||
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
|
||||
@@ -885,9 +885,30 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
|
||||
refreshRateIsEqual = isRefreshRateEqualMatch(candidate.getRefreshRate());
|
||||
}
|
||||
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||
|
||||
LimeLog.info("Best display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
||||
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
|
||||
|
||||
// Only apply new window layout parameters if we've actually changed the display mode
|
||||
if (display.getMode().getModeId() != bestMode.getModeId()) {
|
||||
// If we only changed refresh rate and we're on an OS that supports Surface.setFrameRate()
|
||||
// use that instead of using preferredDisplayModeId to avoid the possibility of triggering
|
||||
// bugs that can cause the system to switch from 4K60 to 4K24 on Chromecast 4K.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
|
||||
display.getMode().getPhysicalWidth() != bestMode.getPhysicalWidth() ||
|
||||
display.getMode().getPhysicalHeight() != bestMode.getPhysicalHeight()) {
|
||||
// Apply the display mode change
|
||||
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
|
||||
getWindow().setAttributes(windowLayoutParams);
|
||||
}
|
||||
else {
|
||||
LimeLog.info("Using setFrameRate() instead of preferredDisplayModeId due to matching resolution");
|
||||
}
|
||||
}
|
||||
else {
|
||||
LimeLog.info("Current display mode is already the best display mode");
|
||||
}
|
||||
|
||||
displayRefreshRate = bestMode.getRefreshRate();
|
||||
}
|
||||
// On L, we can at least tell the OS that we want a refresh rate
|
||||
@@ -907,9 +928,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
bestRefreshRate = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Selected refresh rate: "+bestRefreshRate);
|
||||
windowLayoutParams.preferredRefreshRate = bestRefreshRate;
|
||||
displayRefreshRate = bestRefreshRate;
|
||||
|
||||
// Apply the refresh rate change
|
||||
getWindow().setAttributes(windowLayoutParams);
|
||||
}
|
||||
else {
|
||||
// Otherwise, the active display refresh rate is just
|
||||
@@ -917,14 +942,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
displayRefreshRate = display.getRefreshRate();
|
||||
}
|
||||
|
||||
// Enable HDMI ALLM (game mode) on Android R
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
windowLayoutParams.preferMinimalPostProcessing = true;
|
||||
}
|
||||
|
||||
// Apply the display mode change
|
||||
getWindow().setAttributes(windowLayoutParams);
|
||||
|
||||
// From 4.4 to 5.1 we can't ask for a 4K display mode, so we'll
|
||||
// need to hint the OS to provide one.
|
||||
boolean aspectRatioMatch = false;
|
||||
@@ -956,6 +973,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
|
||||
}
|
||||
|
||||
// Set the desired refresh rate that will get passed into setFrameRate() later
|
||||
desiredRefreshRate = displayRefreshRate;
|
||||
|
||||
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) ||
|
||||
getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
// TVs may take a few moments to switch refresh rates, and we can probably assume
|
||||
@@ -1280,33 +1300,43 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
handled = controllerHandler.handleButtonDown(event);
|
||||
}
|
||||
|
||||
// Try the keyboard handler if it wasn't handled as a game controller
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let this method take duplicate key down events
|
||||
if (handleSpecialKeys(event.getKeyCode(), true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Eat repeat down events
|
||||
if (event.getRepeatCount() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte modifiers = getModifierState(event);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
// We'll send it as a raw key event if we have a key mapping, otherwise we'll send it
|
||||
// as UTF-8 text (if it's a printable character).
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
// Make sure it has a valid Unicode representation and it's not a dead character
|
||||
// (which we don't support). If those are true, we can send it as UTF-8 text.
|
||||
//
|
||||
// NB: We need to be sure this happens before the getRepeatCount() check because
|
||||
// UTF-8 events don't auto-repeat on the host side.
|
||||
int unicodeChar = event.getUnicodeChar();
|
||||
if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0 && (unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK) != 0) {
|
||||
conn.sendUtf8Text(""+(char)unicodeChar);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
|
||||
|
||||
// Eat repeat down events
|
||||
if (event.getRepeatCount() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState(event),
|
||||
keyboardTranslator.hasNormalizedMapping(event.getKeyCode(), event.getDeviceId()) ? 0 : MoonBridge.SS_KBE_FLAG_NON_NORMALIZED);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1350,13 +1380,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
handled = controllerHandler.handleButtonUp(event);
|
||||
}
|
||||
|
||||
// Try the keyboard handler if it wasn't handled as a game controller
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handleSpecialKeys(event.getKeyCode(), false)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1366,16 +1391,43 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
byte modifiers = getModifierState(event);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
// If we sent this event as UTF-8 on key down, also report that it was handled
|
||||
// when we get the key up event for it.
|
||||
int unicodeChar = event.getUnicodeChar();
|
||||
return (unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0 && (unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK) != 0;
|
||||
}
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
|
||||
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event),
|
||||
keyboardTranslator.hasNormalizedMapping(event.getKeyCode(), event.getDeviceId()) ? 0 : MoonBridge.SS_KBE_FLAG_NON_NORMALIZED);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
||||
return handleKeyMultiple(event) || super.onKeyMultiple(keyCode, repeatCount, event);
|
||||
}
|
||||
|
||||
private boolean handleKeyMultiple(KeyEvent event) {
|
||||
// We can receive keys from a software keyboard that don't correspond to any existing
|
||||
// KEYCODE value. Android will give those to us as an ACTION_MULTIPLE KeyEvent.
|
||||
//
|
||||
// Despite the fact that the Android docs say this is unused since API level 29, these
|
||||
// events are still sent as of Android 13 for the above case.
|
||||
//
|
||||
// For other cases of ACTION_MULTIPLE, we will not report those as handled so hopefully
|
||||
// they will be passed to us again as regular singular key events.
|
||||
if (event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN || event.getCharacters() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
conn.sendUtf8Text(event.getCharacters());
|
||||
return true;
|
||||
}
|
||||
|
||||
private TouchContext getTouchContext(int actionIndex)
|
||||
{
|
||||
if (actionIndex < touchContextMap.length) {
|
||||
@@ -1501,6 +1553,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||
// Send the vertical scroll packet
|
||||
conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120));
|
||||
conn.sendMouseHighResHScroll((short)(event.getAxisValue(MotionEvent.AXIS_HSCROLL) * 120));
|
||||
}
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
@@ -1792,7 +1845,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent event) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
// Tell the OS not to buffer input events for us
|
||||
//
|
||||
@@ -2052,9 +2105,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHdrMode(boolean enabled) {
|
||||
public void setHdrMode(boolean enabled, byte[] hdrMetadata) {
|
||||
LimeLog.info("Display HDR mode: " + (enabled ? "enabled" : "disabled"));
|
||||
decoderRenderer.setHdrMode(enabled);
|
||||
decoderRenderer.setHdrMode(enabled, hdrMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2077,11 +2130,37 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
float desiredFrameRate;
|
||||
|
||||
surfaceCreated = true;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Tell the OS about our frame rate to allow it to adapt the display refresh rate appropriately
|
||||
holder.getSurface().setFrameRate(prefConfig.fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
|
||||
// Android will pick the lowest matching refresh rate for a given frame rate value, so we want
|
||||
// to report the true FPS value if refresh rate reduction is enabled. We also report the true
|
||||
// FPS value if there's no suitable matching refresh rate. In that case, Android could try to
|
||||
// select a lower refresh rate that avoids uneven pull-down (ex: 30 Hz for a 60 FPS stream on
|
||||
// a display that maxes out at 50 Hz).
|
||||
if (mayReduceRefreshRate() || desiredRefreshRate < prefConfig.fps) {
|
||||
desiredFrameRate = prefConfig.fps;
|
||||
}
|
||||
else {
|
||||
// Otherwise, we will pretend that our frame rate matches the refresh rate we picked in
|
||||
// prepareDisplayForRendering(). This will usually be the highest refresh rate that our
|
||||
// frame rate evenly divides into, which ensures the lowest possible display latency.
|
||||
desiredFrameRate = desiredRefreshRate;
|
||||
}
|
||||
|
||||
// Tell the OS about our frame rate to allow it to adapt the display refresh rate appropriately
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// We want to change frame rate even if it's not seamless, since prepareDisplayForRendering()
|
||||
// will not set the display mode on S+ if it only differs by the refresh rate. It depends
|
||||
// on us to trigger the frame rate switch here.
|
||||
holder.getSurface().setFrameRate(desiredFrameRate,
|
||||
Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
|
||||
Surface.CHANGE_FRAME_RATE_ALWAYS);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
holder.getSurface().setFrameRate(desiredFrameRate,
|
||||
Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2141,10 +2220,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseScroll(byte amount) {
|
||||
public void mouseVScroll(byte amount) {
|
||||
conn.sendMouseScroll(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseHScroll(byte amount) {
|
||||
conn.sendMouseHScroll(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||
short keyMap = keyboardTranslator.translate(keyCode, -1);
|
||||
@@ -2155,10 +2239,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
if (buttonDown) {
|
||||
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_DOWN, getModifierState());
|
||||
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_DOWN, getModifierState(), (byte)0);
|
||||
}
|
||||
else {
|
||||
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_UP, getModifierState());
|
||||
conn.sendKeyboardInput(keyMap, KeyboardPacket.KEY_UP, getModifierState(), (byte)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2217,6 +2301,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return handleKeyDown(keyEvent);
|
||||
case KeyEvent.ACTION_UP:
|
||||
return handleKeyUp(keyEvent);
|
||||
case KeyEvent.ACTION_MULTIPLE:
|
||||
return handleKeyMultiple(keyEvent);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -71,12 +71,6 @@ public class HelpActivity extends Activity {
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return !(url.toUpperCase().startsWith("https://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()) ||
|
||||
url.toUpperCase().startsWith("http://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()));
|
||||
}
|
||||
});
|
||||
|
||||
webView.loadUrl(getIntent().getData().toString());
|
||||
|
||||
@@ -118,6 +118,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
private final static int VIEW_DETAILS_ID = 8;
|
||||
private final static int FULL_APP_LIST_ID = 9;
|
||||
private final static int TEST_NETWORK_ID = 10;
|
||||
private final static int GAMESTREAM_EOL_ID = 11;
|
||||
|
||||
private void initializeViews() {
|
||||
setContentView(R.layout.activity_pc_view);
|
||||
@@ -352,9 +353,13 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
if (computer.details.state == ComputerDetails.State.OFFLINE ||
|
||||
computer.details.state == ComputerDetails.State.UNKNOWN) {
|
||||
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
|
||||
menu.add(Menu.NONE, GAMESTREAM_EOL_ID, 2, getResources().getString(R.string.pcview_menu_eol));
|
||||
}
|
||||
else if (computer.details.pairState != PairState.PAIRED) {
|
||||
menu.add(Menu.NONE, PAIR_ID, 1, getResources().getString(R.string.pcview_menu_pair_pc));
|
||||
if (computer.details.nvidiaServer) {
|
||||
menu.add(Menu.NONE, GAMESTREAM_EOL_ID, 2, getResources().getString(R.string.pcview_menu_eol));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (computer.details.runningGameId != 0) {
|
||||
@@ -362,6 +367,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
||||
}
|
||||
|
||||
if (computer.details.nvidiaServer) {
|
||||
menu.add(Menu.NONE, GAMESTREAM_EOL_ID, 3, getResources().getString(R.string.pcview_menu_eol));
|
||||
}
|
||||
|
||||
menu.add(Menu.NONE, FULL_APP_LIST_ID, 4, getResources().getString(R.string.pcview_menu_app_list));
|
||||
}
|
||||
|
||||
@@ -412,7 +421,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
// Spin the dialog off in a thread because it blocks
|
||||
Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title),
|
||||
getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false);
|
||||
getResources().getString(R.string.pair_pairing_msg)+" "+pinStr+"\n\n"+
|
||||
getResources().getString(R.string.pair_pairing_help), false);
|
||||
|
||||
PairingManager pm = httpConn.getPairingManager();
|
||||
|
||||
@@ -656,6 +666,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
ServerHelper.doNetworkTest(PcView.this);
|
||||
return true;
|
||||
|
||||
case GAMESTREAM_EOL_ID:
|
||||
HelpLauncher.launchGameStreamEolFaq(PcView.this);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -104,18 +104,20 @@ public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean needsShift(int keycode) {
|
||||
switch (keycode)
|
||||
{
|
||||
case KeyEvent.KEYCODE_AT:
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
case KeyEvent.KEYCODE_PLUS:
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
public boolean hasNormalizedMapping(int keycode, int deviceId) {
|
||||
if (deviceId >= 0) {
|
||||
KeyboardMapping mapping = keyboardMappings.get(deviceId);
|
||||
if (mapping != null) {
|
||||
// Try to map this device-specific keycode onto a QWERTY layout.
|
||||
// GFE assumes incoming keycodes are from a QWERTY keyboard.
|
||||
int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode);
|
||||
if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,20 +345,7 @@ public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||
translated = 0x6E;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_AT:
|
||||
translated = 2 + VK_0;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
translated = 3 + VK_0;
|
||||
break;
|
||||
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
translated = 8 + VK_0;
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("No key for "+keycode);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -44,7 +44,10 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
||||
// with SOURCE_TOUCHSCREEN, SOURCE_KEYBOARD, and SOURCE_MOUSE.
|
||||
// Upon enabling pointer capture, that device will switch to
|
||||
// SOURCE_KEYBOARD and SOURCE_TOUCHPAD.
|
||||
if (device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
// Only skip on non ChromeOS devices cause the ChromeOS pointer else
|
||||
// gets disabled removing relative mouse capabilities
|
||||
// on Chromebooks with touchscreens
|
||||
if (device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) && !targetView.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.R;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
@@ -183,6 +184,9 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
else if (Xbox360Controller.canClaimDevice(device)) {
|
||||
controller = new Xbox360Controller(device, connection, nextDeviceId++, this);
|
||||
}
|
||||
else if (Xbox360WirelessDongle.canClaimDevice(device)) {
|
||||
controller = new Xbox360WirelessDongle(device, connection, nextDeviceId++, this);
|
||||
}
|
||||
else {
|
||||
// Unreachable
|
||||
return;
|
||||
@@ -248,9 +252,32 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean kernelSupportsXbox360W() {
|
||||
// Check if this kernel is 4.2+ to see if the xpad driver sets Xbox 360 wireless LEDs
|
||||
// https://github.com/torvalds/linux/commit/75b7f05d2798ee3a1cc5bbdd54acd0e318a80396
|
||||
String kernelVersion = System.getProperty("os.version");
|
||||
if (kernelVersion != null) {
|
||||
if (kernelVersion.startsWith("2.") || kernelVersion.startsWith("3.") ||
|
||||
kernelVersion.startsWith("4.0.") || kernelVersion.startsWith("4.1.")) {
|
||||
// Even if LED devices are present, the driver won't set the initial LED state.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We know we have a kernel that should set Xbox 360 wireless LEDs, but we still don't
|
||||
// know if CONFIG_JOYSTICK_XPAD_LEDS was enabled during the kernel build. Unfortunately
|
||||
// it's not possible to detect this reliably due to Android's app sandboxing. Reading
|
||||
// /proc/config.gz and enumerating /sys/class/leds are both blocked by SELinux on any
|
||||
// relatively modern device. We will assume that CONFIG_JOYSTICK_XPAD_LEDS=y on these
|
||||
// kernels and users can override by using the settings option to claim all devices.
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean shouldClaimDevice(UsbDevice device, boolean claimAllAvailable) {
|
||||
return ((!kernelSupportsXboxOne() || !isRecognizedInputDevice(device) || claimAllAvailable) && XboxOneController.canClaimDevice(device)) ||
|
||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device)) ||
|
||||
// We must not call isRecognizedInputDevice() because wireless controllers don't share the same product ID as the dongle
|
||||
((!kernelSupportsXbox360W() || claimAllAvailable) && Xbox360WirelessDongle.canClaimDevice(device));
|
||||
}
|
||||
|
||||
private void start() {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.limelight.binding.input.driver;
|
||||
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.os.Build;
|
||||
import android.view.InputDevice;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class Xbox360WirelessDongle extends AbstractController {
|
||||
private UsbDevice device;
|
||||
private UsbDeviceConnection connection;
|
||||
|
||||
private static final int XB360W_IFACE_SUBCLASS = 93;
|
||||
private static final int XB360W_IFACE_PROTOCOL = 129; // Wireless only
|
||||
|
||||
private static final int[] SUPPORTED_VENDORS = {
|
||||
0x045e, // Microsoft
|
||||
};
|
||||
|
||||
public static boolean canClaimDevice(UsbDevice device) {
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (device.getVendorId() == supportedVid &&
|
||||
device.getInterfaceCount() >= 1 &&
|
||||
device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
device.getInterface(0).getInterfaceSubclass() == XB360W_IFACE_SUBCLASS &&
|
||||
device.getInterface(0).getInterfaceProtocol() == XB360W_IFACE_PROTOCOL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Xbox360WirelessDongle(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
|
||||
super(deviceId, listener, device.getVendorId(), device.getProductId());
|
||||
this.device = device;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
private void sendLedCommandToEndpoint(UsbEndpoint endpoint, int controllerIndex) {
|
||||
byte[] commandBuffer = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x08,
|
||||
(byte) (0x40 + (2 + (controllerIndex % 4))),
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00};
|
||||
|
||||
int res = connection.bulkTransfer(endpoint, commandBuffer, commandBuffer.length, 3000);
|
||||
if (res != commandBuffer.length) {
|
||||
LimeLog.warning("LED set transfer failed: "+res);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLedCommandToInterface(UsbInterface iface, int controllerIndex) {
|
||||
// Claim this interface to kick xpad off it (temporarily)
|
||||
if (!connection.claimInterface(iface, true)) {
|
||||
LimeLog.warning("Failed to claim interface: "+iface.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the out endpoint for this interface
|
||||
for (int i = 0; i < iface.getEndpointCount(); i++) {
|
||||
UsbEndpoint endpt = iface.getEndpoint(i);
|
||||
if (endpt.getDirection() == UsbConstants.USB_DIR_OUT) {
|
||||
// Send the LED command
|
||||
sendLedCommandToEndpoint(endpt, controllerIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Release the interface to allow xpad to take over again
|
||||
connection.releaseInterface(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
int controllerIndex = 0;
|
||||
|
||||
// On KitKat, there is a controller number associated with input devices.
|
||||
// We can use this to approximate the likely controller number. This won't
|
||||
// be completely accurate because there's no guarantee the order of interfaces
|
||||
// matches the order that devices were enumerated by xpad, but it's probably
|
||||
// better than nothing.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
for (int id : InputDevice.getDeviceIds()) {
|
||||
InputDevice inputDev = InputDevice.getDevice(id);
|
||||
if (inputDev == null) {
|
||||
// Device was removed while looping
|
||||
continue;
|
||||
}
|
||||
|
||||
// Newer xpad versions use a special product ID (0x02a1) for controllers
|
||||
// rather than copying the product ID of the dongle itself.
|
||||
if (inputDev.getVendorId() == device.getVendorId() &&
|
||||
(inputDev.getProductId() == device.getProductId() ||
|
||||
inputDev.getProductId() == 0x02a1) &&
|
||||
inputDev.getControllerNumber() > 0) {
|
||||
controllerIndex = inputDev.getControllerNumber() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send LED commands on the out endpoint of each interface. There is one interface
|
||||
// corresponding to each possible attached controller.
|
||||
for (int i = 0; i < device.getInterfaceCount(); i++) {
|
||||
UsbInterface iface = device.getInterface(i);
|
||||
|
||||
// Skip the non-input interfaces
|
||||
if (iface.getInterfaceClass() != UsbConstants.USB_CLASS_VENDOR_SPEC ||
|
||||
iface.getInterfaceSubclass() != XB360W_IFACE_SUBCLASS ||
|
||||
iface.getInterfaceProtocol() != XB360W_IFACE_PROTOCOL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sendLedCommandToInterface(iface, controllerIndex++);
|
||||
}
|
||||
|
||||
// "Fail" to give control back to the kernel driver
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rumble(short lowFreqMotor, short highFreqMotor) {
|
||||
// Unreachable.
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ public interface EvdevListener {
|
||||
|
||||
void mouseMove(int deltaX, int deltaY);
|
||||
void mouseButtonEvent(int buttonId, boolean down);
|
||||
void mouseScroll(byte amount);
|
||||
void mouseVScroll(byte amount);
|
||||
void mouseHScroll(byte amount);
|
||||
void keyboardEvent(boolean buttonDown, short keyCode);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.limelight.binding.video;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -49,6 +51,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
private byte[] ppsBuffer;
|
||||
private boolean submittedCsd;
|
||||
private boolean submitCsdNextCall;
|
||||
private byte[] currentHdrMetadata;
|
||||
|
||||
private int nextInputBufferIndex = -1;
|
||||
private ByteBuffer nextInputBuffer;
|
||||
@@ -73,7 +76,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
private boolean foreground = true;
|
||||
private PerfOverlayListener perfListener;
|
||||
|
||||
private static final int CR_TIMEOUT_MS = 5000;
|
||||
private static final int CR_MAX_TRIES = 10;
|
||||
private static final int CR_RECOVERY_TYPE_NONE = 0;
|
||||
private static final int CR_RECOVERY_TYPE_FLUSH = 1;
|
||||
@@ -379,10 +381,64 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, initialHeight);
|
||||
}
|
||||
|
||||
// Android 7.0 adds color options to the MediaFormat
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_RANGE,
|
||||
getPreferredColorRange() == MoonBridge.COLOR_RANGE_FULL ?
|
||||
MediaFormat.COLOR_RANGE_FULL : MediaFormat.COLOR_RANGE_LIMITED);
|
||||
|
||||
// If the stream is HDR-capable, the decoder will detect transitions in color standards
|
||||
// rather than us hardcoding them into the MediaFormat.
|
||||
if (getActiveVideoFormat() != MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||
// Set color format keys when not in HDR mode, since we know they won't change
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
|
||||
switch (getPreferredColorSpace()) {
|
||||
case MoonBridge.COLORSPACE_REC_601:
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_NTSC);
|
||||
break;
|
||||
case MoonBridge.COLORSPACE_REC_709:
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT709);
|
||||
break;
|
||||
case MoonBridge.COLORSPACE_REC_2020:
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return videoFormat;
|
||||
}
|
||||
|
||||
private void configureAndStartDecoder(MediaFormat format) {
|
||||
// Set HDR metadata if present
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (currentHdrMetadata != null) {
|
||||
ByteBuffer hdrStaticInfo = ByteBuffer.allocate(25).order(ByteOrder.LITTLE_ENDIAN);
|
||||
ByteBuffer hdrMetadata = ByteBuffer.wrap(currentHdrMetadata).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// Create a HDMI Dynamic Range and Mastering InfoFrame as defined by CTA-861.3
|
||||
hdrStaticInfo.put((byte) 0); // Metadata type
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // RX
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // RY
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // GX
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // GY
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // BX
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // BY
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // White X
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // White Y
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Max mastering luminance
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Min mastering luminance
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Max content luminance
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Max frame average luminance
|
||||
|
||||
hdrStaticInfo.rewind();
|
||||
format.setByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO, hdrStaticInfo);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
format.removeKey(MediaFormat.KEY_HDR_STATIC_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Configuring with format: "+format);
|
||||
|
||||
videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
|
||||
@@ -673,14 +729,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
else {
|
||||
// If we haven't quiesced all threads yet, wait to be signalled after recovery.
|
||||
// The final thread to be quiesced will handle the codec recovery.
|
||||
LimeLog.info("Waiting to quiesce decoder threads: "+codecRecoveryThreadQuiescedFlags);
|
||||
long startTime = SystemClock.uptimeMillis();
|
||||
while (codecRecoveryType.get() != CR_RECOVERY_TYPE_NONE) {
|
||||
try {
|
||||
if (SystemClock.uptimeMillis() - startTime >= CR_TIMEOUT_MS) {
|
||||
throw new IllegalStateException("Decoder failed to recover within timeout");
|
||||
}
|
||||
codecRecoveryMonitor.wait(CR_TIMEOUT_MS);
|
||||
LimeLog.info("Waiting to quiesce decoder threads: "+codecRecoveryThreadQuiescedFlags);
|
||||
codecRecoveryMonitor.wait(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -1140,8 +1192,33 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHdrMode(boolean enabled) {
|
||||
// TODO: Set HDR metadata?
|
||||
public void setHdrMode(boolean enabled, byte[] hdrMetadata) {
|
||||
// HDR metadata is only supported in Android 7.0 and later, so don't bother
|
||||
// restarting the codec on anything earlier than that.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (currentHdrMetadata != null && (!enabled || hdrMetadata == null)) {
|
||||
currentHdrMetadata = null;
|
||||
}
|
||||
else if (enabled && hdrMetadata != null && !Arrays.equals(currentHdrMetadata, hdrMetadata)) {
|
||||
currentHdrMetadata = hdrMetadata;
|
||||
}
|
||||
else {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reach this point, we need to restart the MediaCodec instance to
|
||||
// pick up the HDR metadata change. This will happen on the next input
|
||||
// or output buffer.
|
||||
|
||||
// HACK: Reset codec recovery attempt counter, since this is an expected "recovery"
|
||||
codecRecoveryAttempts = 0;
|
||||
|
||||
// Promote None/Flush to Restart and leave Reset alone
|
||||
if (!codecRecoveryType.compareAndSet(CR_RECOVERY_TYPE_NONE, CR_RECOVERY_TYPE_RESTART)) {
|
||||
codecRecoveryType.compareAndSet(CR_RECOVERY_TYPE_FLUSH, CR_RECOVERY_TYPE_RESTART);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean queueNextInputBuffer(long timestampUs, int codecFlags) {
|
||||
|
||||
@@ -356,7 +356,14 @@ public class MediaCodecHelper {
|
||||
if (configInfo.reqGlEsVersion >= 0x30000) {
|
||||
LimeLog.info("Added omx.nvidia/c2.nvidia to reference frame invalidation support list");
|
||||
refFrameInvalidationAvcPrefixes.add("omx.nvidia");
|
||||
refFrameInvalidationHevcPrefixes.add("omx.nvidia");
|
||||
|
||||
// Exclude HEVC RFI on Pixel C and Tegra devices prior to Android 11. Misbehaving RFI
|
||||
// on these devices can cause hundreds of milliseconds of latency, so it's not worth
|
||||
// using it unless we're absolutely sure that it will not cause increased latency.
|
||||
if (!Build.DEVICE.equalsIgnoreCase("dragon") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
refFrameInvalidationHevcPrefixes.add("omx.nvidia");
|
||||
}
|
||||
|
||||
refFrameInvalidationAvcPrefixes.add("c2.nvidia"); // Unconfirmed
|
||||
refFrameInvalidationHevcPrefixes.add("c2.nvidia"); // Unconfirmed
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
@@ -18,18 +19,28 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ComputerDatabaseManager {
|
||||
private static final String COMPUTER_DB_NAME = "computers3.db";
|
||||
private static final String COMPUTER_DB_NAME = "computers4.db";
|
||||
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
||||
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
||||
private static final String ADDRESSES_COLUMN_NAME = "Addresses";
|
||||
private interface AddressFields {
|
||||
String LOCAL = "local";
|
||||
String REMOTE = "remote";
|
||||
String MANUAL = "manual";
|
||||
String IPv6 = "ipv6";
|
||||
|
||||
String ADDRESS = "address";
|
||||
String PORT = "port";
|
||||
}
|
||||
|
||||
private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress";
|
||||
private static final String SERVER_CERT_COLUMN_NAME = "ServerCert";
|
||||
|
||||
private static final char ADDRESS_DELIMITER = ';';
|
||||
private static final char PORT_DELIMITER = '_';
|
||||
|
||||
private SQLiteDatabase computerDb;
|
||||
|
||||
public ComputerDatabaseManager(Context c) {
|
||||
@@ -64,24 +75,54 @@ public class ComputerDatabaseManager {
|
||||
for (ComputerDetails computer : oldComputers) {
|
||||
updateComputer(computer);
|
||||
}
|
||||
oldComputers = LegacyDatabaseReader3.migrateAllComputers(c);
|
||||
for (ComputerDetails computer : oldComputers) {
|
||||
updateComputer(computer);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteComputer(ComputerDetails details) {
|
||||
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{details.uuid});
|
||||
}
|
||||
|
||||
public static JSONObject tupleToJson(ComputerDetails.AddressTuple tuple) throws JSONException {
|
||||
if (tuple == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put(AddressFields.ADDRESS, tuple.address);
|
||||
json.put(AddressFields.PORT, tuple.port);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public static ComputerDetails.AddressTuple tupleFromJson(JSONObject json, String name) throws JSONException {
|
||||
if (!json.has(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject address = json.getJSONObject(name);
|
||||
return new ComputerDetails.AddressTuple(
|
||||
address.getString(AddressFields.ADDRESS), address.getInt(AddressFields.PORT));
|
||||
}
|
||||
|
||||
public boolean updateComputer(ComputerDetails details) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid);
|
||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||
|
||||
StringBuilder addresses = new StringBuilder();
|
||||
addresses.append(details.localAddress != null ? splitTupleToAddress(details.localAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.remoteAddress != null ? splitTupleToAddress(details.remoteAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.manualAddress != null ? splitTupleToAddress(details.manualAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.ipv6Address != null ? splitTupleToAddress(details.ipv6Address) : "");
|
||||
try {
|
||||
JSONObject addresses = new JSONObject();
|
||||
addresses.put(AddressFields.LOCAL, tupleToJson(details.localAddress));
|
||||
addresses.put(AddressFields.REMOTE, tupleToJson(details.remoteAddress));
|
||||
addresses.put(AddressFields.MANUAL, tupleToJson(details.manualAddress));
|
||||
addresses.put(AddressFields.IPv6, tupleToJson(details.ipv6Address));
|
||||
values.put(ADDRESSES_COLUMN_NAME, addresses.toString());
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
values.put(ADDRESSES_COLUMN_NAME, addresses.toString());
|
||||
values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress);
|
||||
try {
|
||||
if (details.serverCert != null) {
|
||||
@@ -97,44 +138,20 @@ public class ComputerDatabaseManager {
|
||||
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
|
||||
private static String readNonEmptyString(String input) {
|
||||
if (input.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static ComputerDetails.AddressTuple splitAddressToTuple(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = input.split(""+PORT_DELIMITER, -1);
|
||||
if (parts.length == 1) {
|
||||
return new ComputerDetails.AddressTuple(parts[0], NvHTTP.DEFAULT_HTTP_PORT);
|
||||
}
|
||||
else {
|
||||
return new ComputerDetails.AddressTuple(parts[0], Integer.parseInt(parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private static String splitTupleToAddress(ComputerDetails.AddressTuple tuple) {
|
||||
return tuple.address+PORT_DELIMITER+tuple.port;
|
||||
}
|
||||
|
||||
private ComputerDetails getComputerFromCursor(Cursor c) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
details.uuid = c.getString(0);
|
||||
details.name = c.getString(1);
|
||||
|
||||
String[] addresses = c.getString(2).split(""+ADDRESS_DELIMITER, -1);
|
||||
|
||||
details.localAddress = splitAddressToTuple(readNonEmptyString(addresses[0]));
|
||||
details.remoteAddress = splitAddressToTuple(readNonEmptyString(addresses[1]));
|
||||
details.manualAddress = splitAddressToTuple(readNonEmptyString(addresses[2]));
|
||||
details.ipv6Address = splitAddressToTuple(readNonEmptyString(addresses[3]));
|
||||
try {
|
||||
JSONObject addresses = new JSONObject(c.getString(2));
|
||||
details.localAddress = tupleFromJson(addresses, AddressFields.LOCAL);
|
||||
details.remoteAddress = tupleFromJson(addresses, AddressFields.REMOTE);
|
||||
details.manualAddress = tupleFromJson(addresses, AddressFields.MANUAL);
|
||||
details.ipv6Address = tupleFromJson(addresses, AddressFields.IPv6);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// External port is persisted in the remote address field
|
||||
if (details.remoteAddress != null) {
|
||||
|
||||
@@ -431,11 +431,6 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDiscoveryFailure(Exception e) {
|
||||
LimeLog.severe("mDNS discovery failed");
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.limelight.computers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class LegacyDatabaseReader3 {
|
||||
private static final String COMPUTER_DB_NAME = "computers3.db";
|
||||
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||
|
||||
private static final char ADDRESS_DELIMITER = ';';
|
||||
private static final char PORT_DELIMITER = '_';
|
||||
|
||||
private static String readNonEmptyString(String input) {
|
||||
if (input.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static ComputerDetails.AddressTuple splitAddressToTuple(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = input.split(""+PORT_DELIMITER, -1);
|
||||
if (parts.length == 1) {
|
||||
return new ComputerDetails.AddressTuple(parts[0], NvHTTP.DEFAULT_HTTP_PORT);
|
||||
}
|
||||
else {
|
||||
return new ComputerDetails.AddressTuple(parts[0], Integer.parseInt(parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private static String splitTupleToAddress(ComputerDetails.AddressTuple tuple) {
|
||||
return tuple.address+PORT_DELIMITER+tuple.port;
|
||||
}
|
||||
|
||||
private static ComputerDetails getComputerFromCursor(Cursor c) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
details.uuid = c.getString(0);
|
||||
details.name = c.getString(1);
|
||||
|
||||
String[] addresses = c.getString(2).split(""+ADDRESS_DELIMITER, -1);
|
||||
|
||||
details.localAddress = splitAddressToTuple(readNonEmptyString(addresses[0]));
|
||||
details.remoteAddress = splitAddressToTuple(readNonEmptyString(addresses[1]));
|
||||
details.manualAddress = splitAddressToTuple(readNonEmptyString(addresses[2]));
|
||||
details.ipv6Address = splitAddressToTuple(readNonEmptyString(addresses[3]));
|
||||
|
||||
// External port is persisted in the remote address field
|
||||
if (details.remoteAddress != null) {
|
||||
details.externalPort = details.remoteAddress.port;
|
||||
}
|
||||
else {
|
||||
details.externalPort = NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(3);
|
||||
|
||||
try {
|
||||
byte[] derCertData = c.getBlob(4);
|
||||
|
||||
if (derCertData != null) {
|
||||
details.serverCert = (X509Certificate) CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(new ByteArrayInputStream(derCertData));
|
||||
}
|
||||
} catch (CertificateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// This signifies we don't have dynamic state (like pair state)
|
||||
details.state = ComputerDetails.State.UNKNOWN;
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
public static List<ComputerDetails> getAllComputers(SQLiteDatabase computerDb) {
|
||||
try (final Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null)) {
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = getComputerFromCursor(c);
|
||||
|
||||
// If a critical field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
computerList.add(details);
|
||||
}
|
||||
|
||||
return computerList;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ComputerDetails> migrateAllComputers(Context c) {
|
||||
try (final SQLiteDatabase computerDb = SQLiteDatabase.openDatabase(
|
||||
c.getDatabasePath(COMPUTER_DB_NAME).getPath(),
|
||||
null, SQLiteDatabase.OPEN_READONLY)
|
||||
) {
|
||||
// Open the existing database
|
||||
return getAllComputers(computerDb);
|
||||
} catch (SQLiteException e) {
|
||||
return new LinkedList<ComputerDetails>();
|
||||
} finally {
|
||||
// Close and delete the old DB
|
||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,13 +54,6 @@ public class DiscoveryService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
||||
if (boundListener != null) {
|
||||
boundListener.notifyComputerRemoved(computer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDiscoveryFailure(Exception e) {
|
||||
if (boundListener != null) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import javax.crypto.SecretKey;
|
||||
public class ConnectionContext {
|
||||
public ComputerDetails.AddressTuple serverAddress;
|
||||
public int httpsPort;
|
||||
public boolean isNvidiaServerSoftware;
|
||||
public X509Certificate serverCert;
|
||||
public StreamConfiguration streamConfig;
|
||||
public NvConnectionListener connListener;
|
||||
|
||||
@@ -15,7 +15,6 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -33,7 +32,7 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.HostHttpResponseException;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
@@ -48,21 +47,13 @@ public class NvConnection {
|
||||
private ConnectionContext context;
|
||||
private static Semaphore connectionAllowed = new Semaphore(1);
|
||||
private final boolean isMonkey;
|
||||
private final boolean batchMouseInput;
|
||||
private final Context appContext;
|
||||
|
||||
private static final int MOUSE_BATCH_PERIOD_MS = 5;
|
||||
private Timer mouseInputTimer;
|
||||
private final Object mouseInputLock = new Object();
|
||||
private short relMouseX, relMouseY, relMouseWidth, relMouseHeight;
|
||||
private short absMouseX, absMouseY, absMouseWidth, absMouseHeight;
|
||||
|
||||
public NvConnection(Context appContext, ComputerDetails.AddressTuple host, int httpsPort, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert, boolean batchMouseInput)
|
||||
public NvConnection(Context appContext, ComputerDetails.AddressTuple host, int httpsPort, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert)
|
||||
{
|
||||
this.appContext = appContext;
|
||||
this.cryptoProvider = cryptoProvider;
|
||||
this.uniqueId = uniqueId;
|
||||
this.batchMouseInput = batchMouseInput;
|
||||
|
||||
this.context = new ConnectionContext();
|
||||
this.context.serverAddress = host;
|
||||
@@ -96,11 +87,6 @@ public class NvConnection {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Stop sending additional input
|
||||
if (mouseInputTimer != null) {
|
||||
mouseInputTimer.cancel();
|
||||
}
|
||||
|
||||
// Interrupt any pending connection. This is thread-safe.
|
||||
MoonBridge.interruptConnection();
|
||||
|
||||
@@ -115,24 +101,6 @@ public class NvConnection {
|
||||
connectionAllowed.release();
|
||||
}
|
||||
|
||||
private void flushMousePosition() {
|
||||
synchronized (mouseInputLock) {
|
||||
if (relMouseX != 0 || relMouseY != 0) {
|
||||
if (relMouseWidth != 0 || relMouseHeight != 0) {
|
||||
MoonBridge.sendMouseMoveAsMousePosition(relMouseX, relMouseY, relMouseWidth, relMouseHeight);
|
||||
}
|
||||
else {
|
||||
MoonBridge.sendMouseMove(relMouseX, relMouseY);
|
||||
}
|
||||
relMouseX = relMouseY = relMouseWidth = relMouseHeight = 0;
|
||||
}
|
||||
if (absMouseX != 0 || absMouseY != 0 || absMouseWidth != 0 || absMouseHeight != 0) {
|
||||
MoonBridge.sendMousePosition(absMouseX, absMouseY, absMouseWidth, absMouseHeight);
|
||||
absMouseX = absMouseY = absMouseWidth = absMouseHeight = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InetAddress resolveServerAddress() throws IOException {
|
||||
// Try to find an address that works for this host
|
||||
InetAddress[] addrs = InetAddress.getAllByName(context.serverAddress.address);
|
||||
@@ -263,6 +231,9 @@ public class NvConnection {
|
||||
return false;
|
||||
}
|
||||
|
||||
ComputerDetails details = h.getComputerDetails(serverInfo);
|
||||
context.isNvidiaServerSoftware = details.nvidiaServer;
|
||||
|
||||
// May be missing for older servers
|
||||
context.serverGfeVersion = h.getGfeVersion(serverInfo);
|
||||
|
||||
@@ -338,14 +309,14 @@ public class NvConnection {
|
||||
if (h.getCurrentGame(serverInfo) != 0) {
|
||||
try {
|
||||
if (h.getCurrentGame(serverInfo) == app.getAppId()) {
|
||||
if (!h.resumeApp(context)) {
|
||||
if (!h.launchApp(context, "resume", app.getAppId(), context.negotiatedHdr)) {
|
||||
context.connListener.displayMessage("Failed to resume existing session");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return quitAndLaunch(h, context);
|
||||
}
|
||||
} catch (GfeHttpResponseException e) {
|
||||
} catch (HostHttpResponseException e) {
|
||||
if (e.getErrorCode() == 470) {
|
||||
// This is the error you get when you try to resume a session that's not yours.
|
||||
// Because this is fairly common, we'll display a more detailed message.
|
||||
@@ -378,7 +349,7 @@ public class NvConnection {
|
||||
context.connListener.displayMessage("Failed to quit previous session! You must quit it manually");
|
||||
return false;
|
||||
}
|
||||
} catch (GfeHttpResponseException e) {
|
||||
} catch (HostHttpResponseException e) {
|
||||
if (e.getErrorCode() == 599) {
|
||||
context.connListener.displayMessage("This session wasn't started by this device," +
|
||||
" so it cannot be quit. End streaming on the original " +
|
||||
@@ -396,7 +367,7 @@ public class NvConnection {
|
||||
private boolean launchNotRunningApp(NvHTTP h, ConnectionContext context)
|
||||
throws IOException, XmlPullParserException {
|
||||
// Launch the app since it's not running
|
||||
if (!h.launchApp(context, context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) {
|
||||
if (!h.launchApp(context, "launch", context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) {
|
||||
context.connListener.displayMessage("Failed to launch application");
|
||||
return false;
|
||||
}
|
||||
@@ -423,7 +394,7 @@ public class NvConnection {
|
||||
return;
|
||||
}
|
||||
context.connListener.stageComplete(appName);
|
||||
} catch (GfeHttpResponseException e) {
|
||||
} catch (HostHttpResponseException e) {
|
||||
e.printStackTrace();
|
||||
context.connListener.displayMessage(e.getMessage());
|
||||
context.connListener.stageFailed(appName, 0, e.getErrorCode());
|
||||
@@ -474,20 +445,6 @@ public class NvConnection {
|
||||
connectionAllowed.release();
|
||||
return;
|
||||
}
|
||||
|
||||
if (batchMouseInput) {
|
||||
// High polling rate mice can cause GeForce Experience's input queue to get backed up,
|
||||
// causing massive input latency. We counter this by limiting our mouse events to 200 Hz
|
||||
// which appears to avoid triggering the issue on all known configurations.
|
||||
mouseInputTimer = new Timer("MouseInput", true);
|
||||
mouseInputTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Flush the mouse position every 5 ms
|
||||
flushMousePosition();
|
||||
}
|
||||
}, MOUSE_BATCH_PERIOD_MS, MOUSE_BATCH_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
@@ -496,65 +453,27 @@ public class NvConnection {
|
||||
public void sendMouseMove(final short deltaX, final short deltaY)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
synchronized (mouseInputLock) {
|
||||
relMouseX += deltaX;
|
||||
relMouseY += deltaY;
|
||||
|
||||
// Reset these to ensure we don't send this as a position update
|
||||
relMouseWidth = 0;
|
||||
relMouseHeight = 0;
|
||||
}
|
||||
|
||||
if (!batchMouseInput) {
|
||||
flushMousePosition();
|
||||
}
|
||||
MoonBridge.sendMouseMove(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
synchronized (mouseInputLock) {
|
||||
absMouseX = x;
|
||||
absMouseY = y;
|
||||
absMouseWidth = referenceWidth;
|
||||
absMouseHeight = referenceHeight;
|
||||
}
|
||||
|
||||
if (!batchMouseInput) {
|
||||
flushMousePosition();
|
||||
}
|
||||
MoonBridge.sendMousePosition(x, y, referenceWidth, referenceHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
synchronized (mouseInputLock) {
|
||||
// Only accumulate the delta if the reference size is the same
|
||||
if (relMouseWidth == referenceWidth && relMouseHeight == referenceHeight) {
|
||||
relMouseX += deltaX;
|
||||
relMouseY += deltaY;
|
||||
}
|
||||
else {
|
||||
relMouseX = deltaX;
|
||||
relMouseY = deltaY;
|
||||
}
|
||||
|
||||
relMouseWidth = referenceWidth;
|
||||
relMouseHeight = referenceHeight;
|
||||
}
|
||||
|
||||
if (!batchMouseInput) {
|
||||
flushMousePosition();
|
||||
}
|
||||
MoonBridge.sendMouseMoveAsMousePosition(deltaX, deltaY, referenceWidth, referenceHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseButtonDown(final byte mouseButton)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseButton(MouseButtonPacket.PRESS_EVENT, mouseButton);
|
||||
}
|
||||
}
|
||||
@@ -562,7 +481,6 @@ public class NvConnection {
|
||||
public void sendMouseButtonUp(final byte mouseButton)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseButton(MouseButtonPacket.RELEASE_EVENT, mouseButton);
|
||||
}
|
||||
}
|
||||
@@ -590,26 +508,36 @@ public class NvConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendKeyboardInput(final short keyMap, final byte keyDirection, final byte modifier) {
|
||||
public void sendKeyboardInput(final short keyMap, final byte keyDirection, final byte modifier, final byte flags) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendKeyboardInput(keyMap, keyDirection, modifier);
|
||||
MoonBridge.sendKeyboardInput(keyMap, keyDirection, modifier, flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseScroll(final byte scrollClicks) {
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseScroll(scrollClicks);
|
||||
MoonBridge.sendMouseHighResScroll((short)(scrollClicks * 120)); // WHEEL_DELTA
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseHScroll(final byte scrollClicks) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendMouseHighResHScroll((short)(scrollClicks * 120)); // WHEEL_DELTA
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseHighResScroll(final short scrollAmount) {
|
||||
if (!isMonkey) {
|
||||
flushMousePosition();
|
||||
MoonBridge.sendMouseHighResScroll(scrollAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseHighResHScroll(final short scrollAmount) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendMouseHighResHScroll(scrollAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendUtf8Text(final String text) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendUtf8Text(text);
|
||||
|
||||
@@ -14,5 +14,5 @@ public interface NvConnectionListener {
|
||||
|
||||
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
||||
|
||||
void setHdrMode(boolean enabled);
|
||||
void setHdrMode(boolean enabled, byte[] hdrMetadata);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public class StreamConfiguration {
|
||||
private int encryptionFlags;
|
||||
private int colorRange;
|
||||
private int colorSpace;
|
||||
private boolean persistGamepadsAfterDisconnect;
|
||||
|
||||
public static class Builder {
|
||||
private StreamConfiguration config = new StreamConfiguration();
|
||||
@@ -109,6 +110,11 @@ public class StreamConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamConfiguration.Builder setPersistGamepadsAfterDisconnect(boolean value) {
|
||||
config.persistGamepadsAfterDisconnect = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamConfiguration.Builder setClientRefreshRateX100(int refreshRateX100) {
|
||||
config.clientRefreshRateX100 = refreshRateX100;
|
||||
return this;
|
||||
@@ -231,6 +237,10 @@ public class StreamConfiguration {
|
||||
return attachedGamepadMask;
|
||||
}
|
||||
|
||||
public boolean getPersistGamepadsAfterDisconnect() {
|
||||
return persistGamepadsAfterDisconnect;
|
||||
}
|
||||
|
||||
public int getClientRefreshRateX100() {
|
||||
return clientRefreshRateX100;
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ public abstract class VideoDecoderRenderer {
|
||||
|
||||
public abstract int getCapabilities();
|
||||
|
||||
public abstract void setHdrMode(boolean enabled);
|
||||
public abstract void setHdrMode(boolean enabled, byte[] hdrMetadata);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.limelight.nvstream.http;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class ComputerDetails {
|
||||
@@ -31,7 +32,7 @@ public class ComputerDetails {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode();
|
||||
return Objects.hash(address, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,6 +75,7 @@ public class ComputerDetails {
|
||||
public PairingManager.PairState pairState;
|
||||
public int runningGameId;
|
||||
public String rawAppList;
|
||||
public boolean nvidiaServer;
|
||||
|
||||
public ComputerDetails() {
|
||||
// Use defaults
|
||||
@@ -142,6 +144,7 @@ public class ComputerDetails {
|
||||
this.httpsPort = details.httpsPort;
|
||||
this.pairState = details.pairState;
|
||||
this.runningGameId = details.runningGameId;
|
||||
this.nvidiaServer = details.nvidiaServer;
|
||||
this.rawAppList = details.rawAppList;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@ package com.limelight.nvstream.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GfeHttpResponseException extends IOException {
|
||||
public class HostHttpResponseException extends IOException {
|
||||
private static final long serialVersionUID = 1543508830807804222L;
|
||||
|
||||
private int errorCode;
|
||||
private String errorMsg;
|
||||
|
||||
public GfeHttpResponseException(int errorCode, String errorMsg) {
|
||||
public HostHttpResponseException(int errorCode, String errorMsg) {
|
||||
this.errorCode = errorCode;
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
@@ -23,6 +23,6 @@ public class GfeHttpResponseException extends IOException {
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "GeForce Experience returned error: "+errorMsg+" (Error code: "+errorCode+")";
|
||||
return "Host PC returned error: "+errorMsg+" (Error code: "+errorCode+")";
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.Socket;
|
||||
@@ -209,9 +210,21 @@ public class NvHTTP {
|
||||
this.httpsPort = httpsPort;
|
||||
|
||||
try {
|
||||
// If this is an IPv4-mapped IPv6 address, OkHTTP will choke on it if it's
|
||||
// in IPv6 form, because InetAddress.getByName() will return an Inet4Address
|
||||
// for what OkHTTP thinks is an IPv6 address. Normalize it into IPv4 form
|
||||
// to avoid triggering this bug.
|
||||
String addressString = address.address;
|
||||
if (addressString.contains(":") && addressString.contains(".")) {
|
||||
InetAddress addr = InetAddress.getByName(addressString);
|
||||
if (addr instanceof Inet4Address) {
|
||||
addressString = ((Inet4Address)addr).getHostAddress();
|
||||
}
|
||||
}
|
||||
|
||||
this.baseUrlHttp = new HttpUrl.Builder()
|
||||
.scheme("http")
|
||||
.host(address.address)
|
||||
.host(addressString)
|
||||
.port(address.port)
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -266,7 +279,7 @@ public class NvHTTP {
|
||||
return getXmlString(new StringReader(str), tagname, throwIfMissing);
|
||||
}
|
||||
|
||||
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
||||
private static void verifyResponseStatus(XmlPullParser xpp) throws HostHttpResponseException {
|
||||
// We use Long.parseLong() because in rare cases GFE can send back a status code of
|
||||
// 0xFFFFFFFF, which will cause Integer.parseInt() to throw a NumberFormatException due
|
||||
// to exceeding Integer.MAX_VALUE. We'll get the desired error code of -1 by just casting
|
||||
@@ -280,7 +293,7 @@ public class NvHTTP {
|
||||
statusCode = 418;
|
||||
statusMsg = "Missing audio capture device. Reinstall GeForce Experience.";
|
||||
}
|
||||
throw new GfeHttpResponseException(statusCode, statusMsg);
|
||||
throw new HostHttpResponseException(statusCode, statusMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +319,7 @@ public class NvHTTP {
|
||||
if (e.getCause() instanceof CertificateException) {
|
||||
// Jump to the GfeHttpResponseException exception handler to retry
|
||||
// over HTTP which will allow us to pair again to update the cert
|
||||
throw new GfeHttpResponseException(401, "Server certificate mismatch");
|
||||
throw new HostHttpResponseException(401, "Server certificate mismatch");
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
@@ -317,7 +330,7 @@ public class NvHTTP {
|
||||
// We want this because it will throw us into the HTTP case if the client is unpaired.
|
||||
getServerVersion(resp);
|
||||
}
|
||||
catch (GfeHttpResponseException e) {
|
||||
catch (HostHttpResponseException e) {
|
||||
if (e.getErrorCode() == 401) {
|
||||
// Cert validation error - fall back to HTTP
|
||||
return openHttpConnectionToString(client, baseUrlHttp, "serverinfo");
|
||||
@@ -342,11 +355,10 @@ public class NvHTTP {
|
||||
|
||||
return new ComputerDetails.AddressTuple(address, port);
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException {
|
||||
|
||||
public ComputerDetails getComputerDetails(String serverInfo) throws IOException, XmlPullParserException {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
String serverInfo = getServerInfo(likelyOnline);
|
||||
|
||||
|
||||
details.name = getXmlString(serverInfo, "hostname", false);
|
||||
if (details.name == null || details.name.isEmpty()) {
|
||||
details.name = "UNKNOWN";
|
||||
@@ -368,12 +380,19 @@ public class NvHTTP {
|
||||
|
||||
details.pairState = getPairState(serverInfo);
|
||||
details.runningGameId = getCurrentGame(serverInfo);
|
||||
|
||||
|
||||
// The MJOLNIR codename was used by GFE but never by any third-party server
|
||||
details.nvidiaServer = getXmlString(serverInfo, "state", true).contains("MJOLNIR");
|
||||
|
||||
// We could reach it so it's online
|
||||
details.state = ComputerDetails.State.ONLINE;
|
||||
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException {
|
||||
return getComputerDetails(getServerInfo(likelyOnline));
|
||||
}
|
||||
|
||||
// This hack is Android-specific but we do it on all platforms
|
||||
// because it doesn't really matter
|
||||
@@ -435,7 +454,7 @@ public class NvHTTP {
|
||||
throw new FileNotFoundException(completeUrl.toString());
|
||||
}
|
||||
else {
|
||||
throw new GfeHttpResponseException(response.code(), response.message());
|
||||
throw new HostHttpResponseException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,7 +688,7 @@ public class NvHTTP {
|
||||
return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true), "applist");
|
||||
}
|
||||
|
||||
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
||||
public LinkedList<NvApp> getAppList() throws HostHttpResponseException, IOException, XmlPullParserException {
|
||||
if (verbose) {
|
||||
// Use the raw function so the app list is printed
|
||||
return getAppListByReader(new StringReader(getAppListRaw()));
|
||||
@@ -681,12 +700,12 @@ public class NvHTTP {
|
||||
}
|
||||
}
|
||||
|
||||
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
|
||||
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws HostHttpResponseException, IOException {
|
||||
return openHttpConnectionToString(enableReadTimeout ? httpClientLongConnectTimeout : httpClientLongConnectNoReadTimeout,
|
||||
baseUrlHttp, "pair", "devicename=roth&updateState=1&" + additionalArguments);
|
||||
}
|
||||
|
||||
String executePairingChallenge() throws GfeHttpResponseException, IOException {
|
||||
String executePairingChallenge() throws HostHttpResponseException, IOException {
|
||||
return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true),
|
||||
"pair", "devicename=roth&updateState=1&phrase=pairchallenge");
|
||||
}
|
||||
@@ -731,27 +750,30 @@ public class NvHTTP {
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public boolean launchApp(ConnectionContext context, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
|
||||
public boolean launchApp(ConnectionContext context, String verb, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
|
||||
// Using an FPS value over 60 causes SOPS to default to 720p60,
|
||||
// so force it to 0 to ensure the correct resolution is set. We
|
||||
// used to use 60 here but that locked the frame rate to 60 FPS
|
||||
// on GFE 3.20.3.
|
||||
int fps = context.streamConfig.getLaunchRefreshRate() > 60 ? 0 : context.streamConfig.getLaunchRefreshRate();
|
||||
int fps = context.isNvidiaServerSoftware && context.streamConfig.getLaunchRefreshRate() > 60 ?
|
||||
0 : context.streamConfig.getLaunchRefreshRate();
|
||||
|
||||
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
|
||||
// GFE to force SOPS to 720p60. This is fine for < 720p resolutions like
|
||||
// 360p or 480p, but it is not ideal for 1440p and other resolutions.
|
||||
// When we detect an unsupported resolution, disable SOPS unless it's under 720p.
|
||||
// FIXME: Detect support resolutions using the serverinfo response, not a hardcoded list
|
||||
boolean enableSops = context.streamConfig.getSops();
|
||||
if (context.negotiatedWidth * context.negotiatedHeight > 1280 * 720 &&
|
||||
context.negotiatedWidth * context.negotiatedHeight != 1920 * 1080 &&
|
||||
context.negotiatedWidth * context.negotiatedHeight != 3840 * 2160) {
|
||||
LimeLog.info("Disabling SOPS due to non-standard resolution: "+context.negotiatedWidth+"x"+context.negotiatedHeight);
|
||||
enableSops = false;
|
||||
if (context.isNvidiaServerSoftware) {
|
||||
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
|
||||
// GFE to force SOPS to 720p60. This is fine for < 720p resolutions like
|
||||
// 360p or 480p, but it is not ideal for 1440p and other resolutions.
|
||||
// When we detect an unsupported resolution, disable SOPS unless it's under 720p.
|
||||
// FIXME: Detect support resolutions using the serverinfo response, not a hardcoded list
|
||||
if (context.negotiatedWidth * context.negotiatedHeight > 1280 * 720 &&
|
||||
context.negotiatedWidth * context.negotiatedHeight != 1920 * 1080 &&
|
||||
context.negotiatedWidth * context.negotiatedHeight != 3840 * 2160) {
|
||||
LimeLog.info("Disabling SOPS due to non-standard resolution: "+context.negotiatedWidth+"x"+context.negotiatedHeight);
|
||||
enableSops = false;
|
||||
}
|
||||
}
|
||||
|
||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "launch",
|
||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), verb,
|
||||
"appid=" + appId +
|
||||
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
||||
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
||||
@@ -760,24 +782,11 @@ public class NvHTTP {
|
||||
(!enableHdr ? "" : "&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0") +
|
||||
"&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) +
|
||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() +
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""));
|
||||
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
|
||||
// sessionUrl0 will be missing for older GFE versions
|
||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
|
||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "resume",
|
||||
"rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
"&rikeyid="+context.riKeyId +
|
||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo());
|
||||
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
|
||||
"&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() +
|
||||
"&gcmap=" + context.streamConfig.getAttachedGamepadMask() +
|
||||
"&gcpersist="+(context.streamConfig.getPersistGamepadsAfterDisconnect() ? 1 : 0));
|
||||
if ((verb.equals("launch") && !getXmlString(xmlStr, "gamesession", true).equals("0") ||
|
||||
(verb.equals("resume") && !getXmlString(xmlStr, "resume", true).equals("0")))) {
|
||||
// sessionUrl0 will be missing for older GFE versions
|
||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||
return true;
|
||||
@@ -798,7 +807,7 @@ public class NvHTTP {
|
||||
if (getCurrentGame(getServerInfo(true)) != 0) {
|
||||
// Generate a synthetic GfeResponseException letting the caller know
|
||||
// that they can't kill someone else's stream.
|
||||
throw new GfeHttpResponseException(599, "");
|
||||
throw new HostHttpResponseException(599, "");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -73,6 +73,8 @@ public class MoonBridge {
|
||||
|
||||
public static final int ML_TEST_RESULT_INCONCLUSIVE = 0xFFFFFFFF;
|
||||
|
||||
public static final byte SS_KBE_FLAG_NON_NORMALIZED = 0x01;
|
||||
|
||||
private static AudioRenderer audioRenderer;
|
||||
private static VideoDecoderRenderer videoRenderer;
|
||||
private static NvConnectionListener connectionListener;
|
||||
@@ -251,9 +253,9 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bridgeClSetHdrMode(boolean enabled) {
|
||||
public static void bridgeClSetHdrMode(boolean enabled, byte[] hdrMetadata) {
|
||||
if (connectionListener != null) {
|
||||
connectionListener.setHdrMode(enabled);
|
||||
connectionListener.setHdrMode(enabled, hdrMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,12 +307,12 @@ public class MoonBridge {
|
||||
short leftStickX, short leftStickY,
|
||||
short rightStickX, short rightStickY);
|
||||
|
||||
public static native void sendKeyboardInput(short keyMap, byte keyDirection, byte modifier);
|
||||
|
||||
public static native void sendMouseScroll(byte scrollClicks);
|
||||
public static native void sendKeyboardInput(short keyMap, byte keyDirection, byte modifier, byte flags);
|
||||
|
||||
public static native void sendMouseHighResScroll(short scrollAmount);
|
||||
|
||||
public static native void sendMouseHighResHScroll(short scrollAmount);
|
||||
|
||||
public static native void sendUtf8Text(String text);
|
||||
|
||||
public static native String getStageName(int stage);
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@@ -25,13 +24,13 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
|
||||
private MdnsDiscoveryListener listener;
|
||||
private Thread discoveryThread;
|
||||
private HashMap<InetAddress, MdnsComputer> computers = new HashMap<InetAddress, MdnsComputer>();
|
||||
private HashSet<String> pendingResolution = new HashSet<String>();
|
||||
private HashSet<MdnsComputer> computers = new HashSet<>();
|
||||
private HashSet<String> pendingResolution = new HashSet<>();
|
||||
|
||||
// The resolver factory's instance member has a static lifetime which
|
||||
// means our ref count and listener must be static also.
|
||||
private static int resolverRefCount = 0;
|
||||
private static HashSet<ServiceListener> listeners = new HashSet<ServiceListener>();
|
||||
private static HashSet<ServiceListener> listeners = new HashSet<>();
|
||||
private static ServiceListener nvstreamListener = new ServiceListener() {
|
||||
@Override
|
||||
public void serviceAdded(ServiceEvent event) {
|
||||
@@ -107,7 +106,7 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static {
|
||||
// Override jmDNS's default topology discovery class with ours
|
||||
@@ -261,7 +260,7 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
for (Inet4Address v4Addr : v4Addrs) {
|
||||
synchronized (computers) {
|
||||
MdnsComputer computer = new MdnsComputer(info.getName(), v4Addr, v6GlobalAddr, info.getPort());
|
||||
if (computers.put(computer.getLocalAddress(), computer) == null) {
|
||||
if (computers.add(computer)) {
|
||||
// This was a new entry
|
||||
listener.notifyComputerAdded(computer);
|
||||
}
|
||||
@@ -274,8 +273,7 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
|
||||
if (v6LocalAddr != null || v6GlobalAddr != null) {
|
||||
MdnsComputer computer = new MdnsComputer(info.getName(), v6LocalAddr, v6GlobalAddr, info.getPort());
|
||||
if (computers.put(v6LocalAddr != null ?
|
||||
computer.getLocalAddress() : computer.getIpv6Address(), computer) == null) {
|
||||
if (computers.add(computer)) {
|
||||
// This was a new entry
|
||||
listener.notifyComputerAdded(computer);
|
||||
}
|
||||
@@ -353,7 +351,7 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
|
||||
public List<MdnsComputer> getComputerSet() {
|
||||
synchronized (computers) {
|
||||
return new ArrayList<MdnsComputer>(computers.values());
|
||||
return new ArrayList<>(computers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,28 +375,6 @@ public class MdnsDiscoveryAgent implements ServiceListener {
|
||||
@Override
|
||||
public void serviceRemoved(ServiceEvent event) {
|
||||
LimeLog.info("mDNS: Machine disappeared: "+event.getInfo().getName());
|
||||
|
||||
Inet4Address v4Addrs[] = event.getInfo().getInet4Addresses();
|
||||
for (Inet4Address addr : v4Addrs) {
|
||||
synchronized (computers) {
|
||||
MdnsComputer computer = computers.remove(addr);
|
||||
if (computer != null) {
|
||||
listener.notifyComputerRemoved(computer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inet6Address v6Addrs[] = event.getInfo().getInet6Addresses();
|
||||
for (Inet6Address addr : v6Addrs) {
|
||||
synchronized (computers) {
|
||||
MdnsComputer computer = computers.remove(addr);
|
||||
if (computer != null) {
|
||||
listener.notifyComputerRemoved(computer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,5 @@ package com.limelight.nvstream.mdns;
|
||||
|
||||
public interface MdnsDiscoveryListener {
|
||||
void notifyComputerAdded(MdnsComputer computer);
|
||||
void notifyComputerRemoved(MdnsComputer computer);
|
||||
void notifyDiscoveryFailure(Exception e);
|
||||
}
|
||||
|
||||
@@ -77,8 +77,21 @@ public class WakeOnLanSender {
|
||||
|
||||
try {
|
||||
sendPacketsForAddress(InetAddress.getByName("255.255.255.255"), address.port, sock, payload);
|
||||
sentWolPacket = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
lastException = e;
|
||||
}
|
||||
|
||||
try {
|
||||
for (InetAddress resolvedAddress : InetAddress.getAllByName(address.address)) {
|
||||
sendPacketsForAddress(resolvedAddress, address.port, sock, payload);
|
||||
try {
|
||||
sendPacketsForAddress(resolvedAddress, address.port, sock, payload);
|
||||
sentWolPacket = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// We may have addresses that don't resolve on this subnet,
|
||||
|
||||
@@ -252,19 +252,10 @@ public class StreamSettings extends Activity {
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
|
||||
// hide on-screen controls category on non touch screen devices
|
||||
if (!getActivity().getPackageManager().
|
||||
hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
|
||||
{
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_onscreen_controls");
|
||||
screen.removePreference(category);
|
||||
}
|
||||
|
||||
{
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_input_settings");
|
||||
category.removePreference(findPreference("checkbox_touchscreen_trackpad"));
|
||||
}
|
||||
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_onscreen_controls");
|
||||
screen.removePreference(category);
|
||||
}
|
||||
|
||||
// Hide remote desktop mouse mode on pre-Oreo (which doesn't have pointer capture)
|
||||
|
||||
@@ -44,4 +44,8 @@ public class HelpLauncher {
|
||||
public static void launchTroubleshooting(Context context) {
|
||||
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
||||
}
|
||||
|
||||
public static void launchGameStreamEolFaq(Context context) {
|
||||
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/NVIDIA-GameStream-End-Of-Service-Announcement-FAQ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ import android.widget.Toast;
|
||||
|
||||
import com.limelight.AppView;
|
||||
import com.limelight.Game;
|
||||
import com.limelight.PcView;
|
||||
import com.limelight.R;
|
||||
import com.limelight.ShortcutTrampoline;
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.computers.ComputerManagerService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.HostHttpResponseException;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
@@ -135,7 +134,7 @@ public class ServerHelper {
|
||||
} else {
|
||||
message = parent.getResources().getString(R.string.applist_quit_fail) + " " + app.getAppName();
|
||||
}
|
||||
} catch (GfeHttpResponseException e) {
|
||||
} catch (HostHttpResponseException e) {
|
||||
if (e.getErrorCode() == 599) {
|
||||
message = "This session wasn't started by this device," +
|
||||
" so it cannot be quit. End streaming on the original " +
|
||||
|
||||
@@ -93,7 +93,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
|
||||
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
|
||||
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z)V");
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z[B)V");
|
||||
}
|
||||
|
||||
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
@@ -331,7 +331,16 @@ void BridgeClConnectionStatusUpdate(int connectionStatus) {
|
||||
void BridgeClSetHdrMode(bool enabled) {
|
||||
JNIEnv* env = GetThreadEnv();
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled);
|
||||
jbyteArray hdrMetadataByteArray = NULL;
|
||||
SS_HDR_METADATA hdrMetadata;
|
||||
|
||||
// Check if HDR metadata was provided
|
||||
if (enabled && LiGetHdrMetadata(&hdrMetadata)) {
|
||||
hdrMetadataByteArray = (*env)->NewByteArray(env, sizeof(SS_HDR_METADATA));
|
||||
(*env)->SetByteArrayRegion(env, hdrMetadataByteArray, 0, sizeof(SS_HDR_METADATA), (jbyte*)&hdrMetadata);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled, hdrMetadataByteArray);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: 8169a31ecc...b77072d399
@@ -47,13 +47,8 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendControllerInput(JNIEnv *env, jcla
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendKeyboardInput(JNIEnv *env, jclass clazz, jshort keyCode, jbyte keyAction, jbyte modifiers) {
|
||||
LiSendKeyboardEvent(keyCode, keyAction, modifiers);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseScroll(JNIEnv *env, jclass clazz, jbyte scrollClicks) {
|
||||
LiSendScrollEvent(scrollClicks);
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendKeyboardInput(JNIEnv *env, jclass clazz, jshort keyCode, jbyte keyAction, jbyte modifiers, jbyte flags) {
|
||||
LiSendKeyboardEvent2(keyCode, keyAction, modifiers, flags);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
@@ -61,6 +56,11 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMouseHighResScroll(JNIEnv *env, j
|
||||
LiSendHighResScrollEvent(scrollAmount);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseHighResHScroll(JNIEnv *env, jclass clazz, jshort scrollAmount) {
|
||||
LiSendHighResHScrollEvent(scrollAmount);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendUtf8Text(JNIEnv *env, jclass clazz, jstring text) {
|
||||
const char* utf8Text = (*env)->GetStringUTFChars(env, text, NULL);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="pair_fail">Fallo al emparejar</string>
|
||||
<!-- WOL messages -->
|
||||
<string name="wol_pc_online">Ordenador encendido</string>
|
||||
<string name="wol_no_mac">Imposible iniciar PC porque GFE no ha enviado una dirección MAC</string>
|
||||
<string name="wol_no_mac">No se puede activar el PC porque no hay ninguna dirección MAC almacenada</string>
|
||||
<string name="wol_waking_pc">Iniciando PC…</string>
|
||||
<string name="wol_waking_msg">Puede tomar algunos segundos iniciar tu PC.
|
||||
Si no se inicia asegúrate que está configurado correctamente para Wake-On-LAN.
|
||||
@@ -47,10 +47,10 @@
|
||||
<string name="conn_terminated_title">Conexión finalizada</string>
|
||||
<string name="conn_terminated_msg">La conexión ha finalizando</string>
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint">Dirección IP del PC con GeForce</string>
|
||||
<string name="searching_pc">Buscando por PCs con GameStream ejecutándose...
|
||||
<string name="ip_hint">Dirección IP del host del ordenador</string>
|
||||
<string name="searching_pc">Buscando el hostal del PC en tu red local...
|
||||
\n
|
||||
\nVerifica que GameStream esté activado en las opciones de SHIELD dentro de GeForce Experience.</string>
|
||||
\n Asegúrate de que Sunshine se está ejecutando en el PC anfitrión o de que GameStream está activado en los ajustes de GeForce Experience SHIELD.</string>
|
||||
<string name="yes">Si</string>
|
||||
<string name="no">No</string>
|
||||
<string name="lost_connection">Conexión perdida</string>
|
||||
@@ -122,9 +122,9 @@
|
||||
<string name="pcview_menu_test_network">Probar Conexión de Red</string>
|
||||
<string name="pcview_menu_details">Ver Detalles</string>
|
||||
<string name="nettest_title_waiting">Probando Conexión de Red</string>
|
||||
<string name="nettest_text_waiting">Moonlight esta probando tu conexión de red para determinar si NVIDIA GameStream esta bloqueado.
|
||||
<string name="nettest_text_waiting">Moonlight está probando tu conexión a la red para determinar si algún puerto necesario está bloqueado.
|
||||
\n
|
||||
\nEsto puede tomar algunos segundos …</string>
|
||||
\nEsto puede tardar unos segundos…</string>
|
||||
<string name="nettest_text_inconclusive">La prueba de red no pudo ejecutarse debido a que no se pudo alcanzar ningún servidor de prueba de conexión de Moonlight. Revisa tu conexión a internet o prueba nuevamente mas tarde.</string>
|
||||
<string name="nettest_text_failure">La actual conexión de red de tu dispositivo parece estar bloqueando Moonlight. La Transmisión mediante Internet puede no funcionar mientras estes conectado a esta red.
|
||||
\n
|
||||
@@ -177,16 +177,14 @@
|
||||
<string name="audioconf_stereo">Estéreo</string>
|
||||
<string name="check_ports_msg">Revisa la configuración de tu firewall, así como, las reglas de redireccionamiento para él(los) puerto(s):</string>
|
||||
<string name="error_usb_prohibited">El acceso a USB está restringido en tu dispositivo por el administrador. Revisar tus ajustes en MDM o Knox.</string>
|
||||
<string name="message_decoding_error">Moonlight se ha cerrado debido a una incompatibilidad con el decodificador de vídeo de este dispositivo. Asegúrate de que GeForce Experience está actualizado a la última versión en tu PC. Intenta ajustar la configuración de streaming si los bloqueos continúan.</string>
|
||||
<string name="message_decoding_error">Moonlight se ha bloqueado debido a una incompatibilidad con el descodificador de vídeo de este dispositivo. Intenta ajustar la configuración de streaming si los bloqueos continúan.</string>
|
||||
<string name="message_decoding_reset">La decodificación de video de tu equipo sigue fallando con las opciones elegidas. Las opciones de la transmisión han sido revertidas a las predeterminadas.</string>
|
||||
<string name="video_decoder_init_failed">El descodificador de video no pudo ser iniciado. Tu dispositivo puede no soportar la resolución elegida o la tasa de cuadros por segundo.</string>
|
||||
<string name="no_frame_received_error">La conexión de internet no está teniendo un buen desempeño. Reduce la tasa de bits de video o prueba en una conexión de mayor velocidad.</string>
|
||||
<string name="unable_to_pin_shortcut">Tu lanzador predeterminado no permite la creación de accesos directos anclados.</string>
|
||||
<string name="early_termination_error">Algo ha salido mal en el PC anfitrión cuando se inicio la transmisión.
|
||||
<string name="early_termination_error">Algo ha fallado en tu ordenador al iniciar la transmisión.
|
||||
\n
|
||||
\nVerifica que no tengas ningún contenido protegido por DRM abierto en el equipo. También prueba reiniciando el equipo anfitrión.
|
||||
\n
|
||||
\nSi el problema persiste, intenta reinstalar los controladores de la tarjeta de video (GPU) además de GeForce Experience.</string>
|
||||
\nAsegúrate de que no tienes ningún contenido protegido por DRM abierto en tu ordenador. También puedes probar a reiniciar el ordenador.</string>
|
||||
<string name="title_details">Detalles</string>
|
||||
<string name="poor_connection_msg">Conexión pobre al PC</string>
|
||||
<string name="applist_menu_details">Ver Detalles</string>
|
||||
@@ -244,7 +242,7 @@
|
||||
<string name="title_disable_frame_drop">Nunca disminuir cuadros</string>
|
||||
<string name="summary_disable_frame_drop">Puede reducir micro-tartamudeos (Stuttering) en algunos dispositivos , pero puede incrementar latencia</string>
|
||||
<string name="title_enable_hdr">Activar HDR (Alto Rango Dinámico / Experimental)</string>
|
||||
<string name="summary_enable_hdr">Transmitir HDR (Alto Rango Dinámico) cuando el juego, el PC y la Tarjeta de Video (GPU) lo soporten. HDR requiere de una Tarjeta de Video de la serie GTX 1000 o superior.</string>
|
||||
<string name="summary_enable_hdr">Transmite en HDR cuando el juego y la GPU del PC lo admitan. HDR requiere una GPU compatible con la codificación HEVC Main 10.</string>
|
||||
<string name="summary_enable_perf_overlay">Mostrar en tiempo real la información del desempeño de la transmisión mientras está activa la misma</string>
|
||||
<string name="title_enable_post_stream_toast">Mostrar mensajes sobre latencia mientras se transmite</string>
|
||||
<string name="category_help">Ayuda</string>
|
||||
@@ -263,6 +261,8 @@
|
||||
<string name="resolution_prefix_native_landscape">(Panorama)</string>
|
||||
<string name="summary_checkbox_reduce_refresh_rate">Tasa de refresco menores pueden ahorrar energía a cambio de una mayor latencia de video</string>
|
||||
<string name="title_checkbox_reduce_refresh_rate">Permitir reducción de la tasa de refresco</string>
|
||||
<string name="title_full_range">Usar rango (0-255) de color completo</string>
|
||||
<string name="summary_full_range">Esto puede causar problemas de video o bloqueos si la pantalla de tu dispositivo no puede manejar contenido de rango completo.</string>
|
||||
<string name="title_full_range">Forzar video de rango completo (Experimental)</string>
|
||||
<string name="summary_full_range">Esto provocará la pérdida de detalles en las áreas claras y oscuras si su dispositivo no muestra correctamente el contenido de video de rango completo.</string>
|
||||
<string name="pair_pairing_help">Si el hostal del ordenador ejecuta Sunshine, navegue a la interfaz de usuario web de Sunshine para ingresar el PIN.</string>
|
||||
<string name="pcview_menu_eol">Fin de servicio de NVIDIA GameStream</string>
|
||||
</resources>
|
||||
@@ -137,7 +137,7 @@
|
||||
<string name="category_input_settings">Paramètres d\'entrée</string>
|
||||
<string name="title_checkbox_touchscreen_trackpad">Utilisez l\'écran tactile comme trackpad</string>
|
||||
<string name="summary_checkbox_touchscreen_trackpad">S\'il est activé, l\'écran tactile agit comme un trackpad. S\'il est désactivé, l\'écran tactile contrôle directement le curseur de la souris.</string>
|
||||
<string name="title_checkbox_multi_controller">Prise en charge de plusieurs contrôleurs</string>
|
||||
<string name="title_checkbox_multi_controller">Forcer la présence d\'un contrôleur</string>
|
||||
<string name="summary_checkbox_multi_controller">Lorsqu\'elle n\'est pas cochée, tous les contrôleurs sont regroupés</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Emuler support vibration de secours</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Emuler des tremblements si votre manette ne le prend pas en charge</string>
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
<string name="audioconf_stereo">Stereo</string>
|
||||
<string name="audioconf_51surround">Surround 5.1</string>
|
||||
<string name="audioconf_71surround">Surround 7.1</string>
|
||||
<string name="videoformat_hevcauto">Automatico</string>
|
||||
<string name="videoformat_hevcalways">Usa sempre HEVC (potrebbe essere instabile)</string>
|
||||
<string name="videoformat_hevcauto">Automatico (Consigliato)</string>
|
||||
<string name="videoformat_hevcalways">Preferisci HEVC</string>
|
||||
<string name="videoformat_hevcnever">Non usare mai HEVC</string>
|
||||
<string name="title_frame_pacing">Bilanciamento frame video</string>
|
||||
<string name="summary_frame_pacing">Specifica come bilanciare il ritardo video e la fluidità</string>
|
||||
@@ -263,4 +263,6 @@
|
||||
<string name="title_checkbox_enable_audiofx">Abilita il supporto per l\'equalizzatore di sistema</string>
|
||||
<string name="summary_checkbox_enable_audiofx">Consente agli effetti audio di funzionare durante lo streaming, ma può aumentare la latenza audio</string>
|
||||
<string name="title_checkbox_reduce_refresh_rate">Consenti riduzione della frequenza di aggiornamento</string>
|
||||
<string name="summary_full_range">Ciò causerà la perdita di dettagli in aree chiare e scure se il dispositivo non visualizza correttamente i contenuti video a gamma completa.</string>
|
||||
<string name="title_full_range">Forza video full range (sperimentale)</string>
|
||||
</resources>
|
||||
@@ -221,8 +221,8 @@
|
||||
<string name="unable_to_pin_shortcut">현재 런처에서는 바로가기 생성이 불가능합니다.</string>
|
||||
<string name="resolution_prefix_native_fullscreen">네이티브 (전체화면)</string>
|
||||
<!-- Array strings -->
|
||||
<string name="videoformat_hevcauto">자동</string>
|
||||
<string name="videoformat_hevcalways">항상 HEVC 사용(깨질 가능성 있음)</string>
|
||||
<string name="videoformat_hevcauto">자동 (권장)</string>
|
||||
<string name="videoformat_hevcalways">HEVC 선호</string>
|
||||
<string name="videoformat_hevcnever">HEVC 사용하지 않기</string>
|
||||
<string name="summary_seekbar_deadzone">참고: 일부 게임은 이 설정값보다 더 큰 데드존이 적용되있을 수 있습니다.</string>
|
||||
<string name="resolution_1080p">1080p</string>
|
||||
@@ -262,4 +262,6 @@
|
||||
<string name="frame_conversion_error">호스트 PC에서 치명적인 비디오 인코딩 오류를 보고했습니다.
|
||||
\n
|
||||
\nHDR 모드를 비활성화하거나 스트리밍 해상도를 변경하거나 호스트 PC의 디스플레이 해상도를 변경해 보십시오.</string>
|
||||
<string name="summary_full_range">밝기범위를 \"전체(0~255)\"로 설정합니다. 장치의 디스플레이가 \"제한(16~235)\" 범위로 출력하는 경우 밝기가 제대로 표현되지 않습니다.</string>
|
||||
<string name="title_full_range">전체 범위 비디오 활성화 (실험용)</string>
|
||||
</resources>
|
||||
@@ -23,4 +23,231 @@
|
||||
<string name="nettest_text_waiting">Moonlight está testando sua conexão com a rede para determinar se o NVIDIA GameStream está bloqueado.
|
||||
\n
|
||||
\nEste processo pode demorar alguns segundos…</string>
|
||||
<string name="pacing_balanced">Balanceado</string>
|
||||
<string name="nettest_text_success">A sua rede não parece estar a bloquear o Moonlight. Se ainda tiver problemas a se conectar, verifique as configurações de firewall do seu PC.
|
||||
\n
|
||||
\nSe estiver a tentar transmitir pela Internet, instale o Moonlight Internet Hosting Tool no seu PC e execute o Internet Streaming Tester para verificar a conexão com a Internet do seu PC.</string>
|
||||
<string name="nettest_text_failure">A conexão de rede atual do seu aparelho parece estar a bloquear o Moonlight. A transmissão pela Internet pode não funcionar enquanto estiver conectado a esta rede.
|
||||
\n
|
||||
\nAs seguintes portas de rede estão bloqueadas:
|
||||
\n</string>
|
||||
<string name="nettest_text_blocked">A conexão de rede atual do seu aparelho está a bloquear o Moonlight. A transmissão pela Internet pode não funcionar enquanto estiver conectado a esta rede.</string>
|
||||
<string name="wol_waking_msg">Pode levar alguns segundos até o seu PC acordar. Se isso não acontecer, verifique se ele está configurado corretamente para Wake-On-LAN.</string>
|
||||
<string name="wol_fail">Falha ao enviar pacotes Wake-On-LAN</string>
|
||||
<string name="unpair_success">Despareado com sucesso</string>
|
||||
<string name="unpair_fail">Falha ao desparear</string>
|
||||
<string name="title_decoding_error">Decodificador de Vídeo Crashou</string>
|
||||
<string name="title_decoding_reset">Resetar Configurações de Vídeo</string>
|
||||
<string name="message_decoding_error">Moonlight crashou devido a uma incompatibilidade com o decodificador de vídeo deste aparelho. Certifique-se de que o GeForce Experience esteja atualizado à versão mais recente no seu PC. Tente ajustar as configurações de transmissão se as falhas continuarem.</string>
|
||||
<string name="unable_to_pin_shortcut">O seu launcher atual não permite a criação de atalhos fixados.</string>
|
||||
<string name="video_decoder_init_failed">Falha ao iniciar o decodificador de vídeo. O seu aparelho pode não suportar a resolução ou taxa de quadros selecionada.</string>
|
||||
<string name="frame_conversion_error">O PC host relatou um erro fatal de codificação de vídeo.
|
||||
\n
|
||||
\nTente desativar o modo HDR, alterar a resolução de streaming ou alterar a resolução do PC host.</string>
|
||||
<string name="early_termination_error">Algo deu errado no seu PC ao iniciar a transmissão.
|
||||
\n
|
||||
\nCertifique-se de não ter nenhum conteúdo protegido por DRM aberto no seu PC. Também pode tentar reiniciar ele.
|
||||
\n
|
||||
\nSe o problema persistir, tente reinstalar os drivers da GPU e o GeForce Experience.</string>
|
||||
<string name="conn_metered">Aviso: A sua conexão de rede atual é limitada!</string>
|
||||
<string name="conn_client_latency">Latência média de decodificação de quadros:</string>
|
||||
<string name="conn_client_latency_hw">latência do decodificador de hardware:</string>
|
||||
<string name="conn_error_msg">Falha ao iniciar</string>
|
||||
<string name="conn_terminated_title">Conexão Encerrada</string>
|
||||
<string name="conn_terminated_msg">A conexão foi encerrada</string>
|
||||
<string name="ip_hint">Endereço de IP do PC GeForce</string>
|
||||
<string name="searching_pc">Procurando por PCs a executar o GameStream...
|
||||
\n
|
||||
\nCertifique-se de que o GameStream esteja ativado nas configurações do SHIELD do GeForce Experience.</string>
|
||||
<string name="perf_overlay_renderingfps">Taxa de quadros renderizando: %1$.2f FPS</string>
|
||||
<string name="perf_overlay_netdrops">Quadros dropados pela rede: %1$.2f%%</string>
|
||||
<string name="perf_overlay_netlatency">Latência média da rede: %1$d ms (variação: %2$d ms)</string>
|
||||
<string name="applist_menu_scut">Criar Atalho</string>
|
||||
<string name="applist_refresh_msg">A recarregar apps…</string>
|
||||
<string name="applist_refresh_error_msg">Falha ao obter a lista de apps</string>
|
||||
<string name="applist_quit_app">Saindo</string>
|
||||
<string name="applist_quit_success">Saiu com sucesso</string>
|
||||
<string name="applist_quit_fail">Falha ao sair</string>
|
||||
<string name="text_native_res_dialog">Os modos de resolução nativa não são oficialmente suportados pelo GeForce Experience, portanto, ele não definirá a resolução da ecrã do host. Precisará configurá-lo manualmente durante o jogo.
|
||||
\n
|
||||
\nSe optar por criar uma resolução personalizada no Painel de controle da NVIDIA para corresponder à resolução do seu aparelho, certifique-se de ter lido e entendido o aviso da NVIDIA sobre possíveis danos ao monitor, instabilidade do PC e outros problemas potenciais.
|
||||
\n
|
||||
\nNão nos responsabilizamos por quaisquer problemas resultantes da criação de uma resolução personalizada no seu PC.
|
||||
\n
|
||||
\nPor fim, o seu aparelho ou PC host pode não suportar transmissões na resolução nativa. Se não funcionar no seu aparelho, está sem sorte, infelizmente.</string>
|
||||
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
|
||||
<string name="resolution_prefix_native_landscape">(Paisagem)</string>
|
||||
<string name="resolution_prefix_native_portrait">(Retrato)</string>
|
||||
<string name="title_audio_config_list">Configurações de som Surround</string>
|
||||
<string name="title_checkbox_enable_audiofx">Ativar sistema de suporte à equalização</string>
|
||||
<string name="title_checkbox_multi_controller">Detecção automática de gamepad</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Vibra o seu aparelho para emular rumble se o seu gamepad não suportar</string>
|
||||
<string name="title_seekbar_deadzone">Ajustar zona morta do analógico</string>
|
||||
<string name="summary_checkbox_enable_audiofx">Permitir que efeitos de audio funcionem durante o streaming, pode acrescentar latência de audio</string>
|
||||
<string name="summary_checkbox_xb1_driver">Ativa um driver USB integrado para aparelhos sem suporte nativo aos controles Xbox</string>
|
||||
<string name="title_checkbox_usb_bind_all">Substituir o suporte nativo ao gamepad do Xbox</string>
|
||||
<string name="summary_checkbox_usb_bind_all">Use o driver USB do Moonlight para todos os gamepads suportados, mesmo se houver suporte nativo ao controle Xbox</string>
|
||||
<string name="title_checkbox_mouse_emulation">Emulação de rato pelo gamepad</string>
|
||||
<string name="summary_checkbox_mouse_emulation">Segure o botão Start para mudar o gamepad ao modo de rato</string>
|
||||
<string name="summary_checkbox_flip_face_buttons">Inverte os botões A/B e X/Y dos gamepads e controles de ecrã</string>
|
||||
<string name="title_checkbox_absolute_mouse_mode">Modo de rato para área de trabalho remota</string>
|
||||
<string name="summary_checkbox_absolute_mouse_mode">Isso pode alterar a aceleração do rato para se comportar mais naturalmente para uso em desktop remoto, mas é incompatível com muitos jogos.</string>
|
||||
<string name="category_on_screen_controls_settings">Configurações de Controles de Ecrã</string>
|
||||
<string name="title_checkbox_show_onscreen_controls">Mostrar controles no ecrã</string>
|
||||
<string name="summary_checkbox_show_onscreen_controls">Ativa o controle virtual no ecrã de toque</string>
|
||||
<string name="summary_only_l3r3">Esconde todos os botões virtuais exceto o L3 e R3</string>
|
||||
<string name="dialog_title_reset_osc">Resetar Layout</string>
|
||||
<string name="toast_reset_osc_success">Controles de ecrã redefinidos para o padrão</string>
|
||||
<string name="title_osc_opacity">Mudar a opacidade dos controles de ecrã</string>
|
||||
<string name="dialog_title_osc_opacity">Mudar opacidade</string>
|
||||
<string name="suffix_osc_opacity">%</string>
|
||||
<string name="category_ui_settings">Configurações de Interface</string>
|
||||
<string name="title_checkbox_enable_pip">Ativar modo Picture-in-Picture</string>
|
||||
<string name="summary_checkbox_enable_pip">Permite que a transmissão seja visualizada (mas não controlada) enquanto usa outros apps</string>
|
||||
<string name="summary_checkbox_small_icon_mode">Diminui o tamanho dos ícones permitindo que mais apps sejam visíveis no ecrã</string>
|
||||
<string name="summary_checkbox_host_audio">Reproduz o áudio do computador e deste aparelho</string>
|
||||
<string name="category_advanced_settings">Configurações Avançadas</string>
|
||||
<string name="title_unlock_fps">Liberar todas as taxas de quadros possíveis</string>
|
||||
<string name="summary_unlock_fps">Transmitir a 90 ou 120 FPS pode reduzir a latência em aparelhos de última geração, mas pode causar atraso ou instabilidade em aparelhos que não suportam</string>
|
||||
<string name="title_checkbox_reduce_refresh_rate">Permitir redução de taxa de atualização</string>
|
||||
<string name="summary_checkbox_reduce_refresh_rate">Taxas menores de atualização do display podem gravar energia ao custo de latência de video adicional</string>
|
||||
<string name="summary_checkbox_disable_warnings">Desativa as mensagens de aviso de conexão no ecrã durante a transmissão</string>
|
||||
<string name="summary_disable_frame_drop">Talvez reduza o micro-stuttering em alguns aparelhos, mas pode aumentar a latência</string>
|
||||
<string name="summary_video_format">HEVC reduz os requisitos de largura de banda de vídeo, mas requer um aparelho mais atual</string>
|
||||
<string name="title_enable_hdr">Ativar HDR (Experimental)</string>
|
||||
<string name="summary_full_range">Isso causará perda de detalhes em áreas claras e escuras se o seu aparelho não exibir corretamente todo o conteúdo de vídeo em cores.</string>
|
||||
<string name="title_disable_frame_drop">Nunca dropar quadros</string>
|
||||
<string name="summary_enable_perf_overlay">Exibe informações de performance em tempo real durante a transmissão</string>
|
||||
<string name="title_enable_post_stream_toast">Mostrar aviso de latência após terminar a transmissão</string>
|
||||
<string name="title_troubleshooting">Guia de solução de problemas</string>
|
||||
<string name="summary_troubleshooting">Veja dicas para diagnosticar e corrigir problemas comuns de streaming</string>
|
||||
<string name="resolution_360p">360p</string>
|
||||
<string name="resolution_1080p">1080p</string>
|
||||
<string name="fps_60">60 FPS</string>
|
||||
<string name="fps_90">90 FPS</string>
|
||||
<string name="fps_120">120 FPS</string>
|
||||
<string name="audioconf_stereo">Stereo</string>
|
||||
<string name="audioconf_51surround">5.1 Surround</string>
|
||||
<string name="videoformat_hevcauto">Automático (recomendado)</string>
|
||||
<string name="videoformat_hevcnever">Nunca usar HEVC</string>
|
||||
<string name="title_frame_pacing">Ritmo de quadros</string>
|
||||
<string name="pacing_smoothness">Preferir vídeo mais suave (pode aumentar significativamente a latência)</string>
|
||||
<string name="summary_seekbar_deadzone">Nota: Alguns jogos podem impor uma zona morta maior do que o Moonlight está configurado para usar.</string>
|
||||
<string name="dialog_text_reset_osc">Tem certeza de que deseja apagar o layout salvo dos controles de ecrã\?</string>
|
||||
<string name="category_host_settings">Configurações de Host</string>
|
||||
<string name="pacing_balanced_alt">Equilibrado com limite de FPS</string>
|
||||
<string name="audioconf_71surround">7.1 Surround</string>
|
||||
<string name="videoformat_hevcalways">Prefira HEVC</string>
|
||||
<string name="error_404">GFE retornou um erro HTTP 404. Verifique se o seu PC tem uma GPU compatível. O uso do software de área de trabalho remota também pode causar esse erro. Tente reiniciar o seu computador ou reinstalar o GFE.</string>
|
||||
<string name="message_decoding_reset">O decodificador de vídeo do seu aparelho continua crashando nas configurações de transmissão selecionadas. As suas configurações de transmissão foram redefinidas para o padrão.</string>
|
||||
<string name="error_usb_prohibited">O acesso USB é proibido pelo administrador do seu aparelho. Verifique as suas configurações de Knox ou MDM.</string>
|
||||
<string name="no_video_received_error">Nenhum vídeo recebido do host.</string>
|
||||
<string name="no_frame_received_error">A sua conexão de rede não está funcionando bem. Reduza a configuração da taxa de bits do vídeo ou tente uma conexão mais rápida.</string>
|
||||
<string name="check_ports_msg">Verifique o firewall e permita a(s) seguinte(s) para porta(s):</string>
|
||||
<string name="conn_hardware_latency">Latência média de decodificação de hardware:</string>
|
||||
<string name="conn_starting">Iniciando</string>
|
||||
<string name="conn_error_title">Erro de Conexão</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="no">Não</string>
|
||||
<string name="lost_connection">Conexão perdida com o PC</string>
|
||||
<string name="title_details">Detalhes</string>
|
||||
<string name="help">Ajuda</string>
|
||||
<string name="delete_pc_msg">Tem certeza de que deseja apagar este PC\?</string>
|
||||
<string name="poor_connection_msg">Conexão ruim com o PC</string>
|
||||
<string name="slow_connection_msg">Conexão lenta com o PC
|
||||
\nReduza sua taxa de bits</string>
|
||||
<string name="perf_overlay_streamdetails">Transmissão: %1$s %2$.2f FPS</string>
|
||||
<string name="perf_overlay_decoder">Decodificador: %1$s</string>
|
||||
<string name="perf_overlay_incomingfps">Taxa de quadros recebidos pela rede:: %1$.2f FPS</string>
|
||||
<string name="perf_overlay_dectime">Tempo médio de decodificação: %1$.2f ms</string>
|
||||
<string name="applist_connect_msg">Conectando ao PC…</string>
|
||||
<string name="applist_menu_resume">Retomar Sessão</string>
|
||||
<string name="applist_menu_quit">Sair da Sessão</string>
|
||||
<string name="applist_menu_quit_and_start">Sair do Jogo Atual e Iniciar</string>
|
||||
<string name="applist_menu_cancel">Cancelar</string>
|
||||
<string name="applist_menu_details">Ver Detalhes</string>
|
||||
<string name="applist_menu_tv_channel">Adicionar ao Canal</string>
|
||||
<string name="applist_menu_hide_app">Esconder app</string>
|
||||
<string name="applist_refresh_title">Lista de apps</string>
|
||||
<string name="applist_refresh_error_title">Erro</string>
|
||||
<string name="applist_quit_confirmation">Tem certeza de que deseja sair da app em execução\? Todos os dados não salvos serão perdidos.</string>
|
||||
<string name="applist_details_id">ID do App:</string>
|
||||
<string name="title_add_pc">Adicionar PC Manualmente</string>
|
||||
<string name="msg_add_pc">Conectando ao PC…</string>
|
||||
<string name="addpc_success">Computador adicionado com sucesso</string>
|
||||
<string name="addpc_unknown_host">Não foi possível encontrar o PC. Certifique-se de que não cometeu um erro de digitação no endereço.</string>
|
||||
<string name="addpc_enter_ip">Deve inserir um endereço de IP</string>
|
||||
<string name="addpc_wrong_sitelocal">Esse endereço não parece certo. Deve usar o endereço de IP público do seu roteador para transmitir pela Internet.</string>
|
||||
<string name="category_basic_settings">Configurações Básicas</string>
|
||||
<string name="title_resolution_list">Resolução</string>
|
||||
<string name="summary_resolution_list">Aumente para melhorar a clareza da imagem. Diminua para melhor desempenho em aparelhos de baixo custo e redes mais lentas.</string>
|
||||
<string name="title_native_res_dialog">Alerta de Resolução Nativa</string>
|
||||
<string name="title_fps_list">Taxa de quadros por segundo</string>
|
||||
<string name="title_seekbar_bitrate">Taxa de bits</string>
|
||||
<string name="summary_seekbar_bitrate">Aumente para uma melhor qualidade de imagem. Diminua para melhorar o desempenho em conexões mais lentas.</string>
|
||||
<string name="title_checkbox_stretch_video">Esticar vídeo ao ecrã cheio</string>
|
||||
<string name="resolution_prefix_native">Nativa</string>
|
||||
<string name="resolution_prefix_native_fullscreen">Ecrã Cheio Nativo</string>
|
||||
<string name="category_audio_settings">Configurações de Áudio</string>
|
||||
<string name="summary_audio_config_list">Ativa o som Surround 5.1 ou 7.1 para sistemas de home theater</string>
|
||||
<string name="category_input_settings">Configurações de Controles</string>
|
||||
<string name="title_checkbox_touchscreen_trackpad">Usar o ecrã como um trackpad</string>
|
||||
<string name="summary_checkbox_touchscreen_trackpad">Se ativado, o ecrã sensível ao toque funciona como um trackpad. Se desativado, o ecrã controla diretamente o cursor do rato.</string>
|
||||
<string name="summary_checkbox_multi_controller">Desmarque esta opção para forçar o gamepad a estar sempre presente</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Emular suporte rumble com vibração</string>
|
||||
<string name="title_checkbox_vibrate_osc">Ativar vibração</string>
|
||||
<string name="summary_checkbox_vibrate_osc">Vibra o seu aparelho para emular rumble nos controles de ecrã</string>
|
||||
<string name="title_only_l3r3">Mostrar somente L3 e R3</string>
|
||||
<string name="title_reset_osc">Limpar o layout salvo dos controles de ecrã</string>
|
||||
<string name="summary_reset_osc">Redefine todos os controles no ecrã para tamanho e posição padrão dele</string>
|
||||
<string name="summary_osc_opacity">Deixe os controles de ecrã mais/menos transparentes</string>
|
||||
<string name="title_language_list">Idioma</string>
|
||||
<string name="summary_language_list">Idioma para ser usado no Moonlight</string>
|
||||
<string name="title_checkbox_small_icon_mode">Mostrar ícones menores</string>
|
||||
<string name="summary_checkbox_enable_sops">Permitir que o GFE modifique as configurações do jogo para otimizar a transmissão</string>
|
||||
<string name="title_checkbox_host_audio">Reproduzir áudio no PC</string>
|
||||
<string name="title_checkbox_disable_warnings">Desativar mensagens de alerta</string>
|
||||
<string name="title_video_format">Mudar configurações do HEVC</string>
|
||||
<string name="summary_enable_hdr">Transmita HDR quando o jogo e a GPU do PC suportarem. O HDR requer uma GPU da série GTX 1000 ou posterior.</string>
|
||||
<string name="title_enable_perf_overlay">Mostrar status de performance durante a transmissão</string>
|
||||
<string name="nettest_title_done">Teste de Internet Completo</string>
|
||||
<string name="nettest_text_inconclusive">O teste de rede não pôde ser executado porque nenhum dos servidores de teste de conexão do Moonlight estava acessível. Verifique a sua conexão com a Internet ou tente novamente mais tarde.</string>
|
||||
<string name="pairing">A parear…</string>
|
||||
<string name="pair_pc_offline">Esse computador está offline</string>
|
||||
<string name="pair_pc_ingame">Esse computador está atualmente num jogo. Deve fechar o jogo antes de parear.</string>
|
||||
<string name="pair_pairing_msg">Insira o seguinte PIN no PC de destino:</string>
|
||||
<string name="pair_incorrect_pin">PIN incorreto</string>
|
||||
<string name="pair_already_in_progress">Pareamento já em andamento</string>
|
||||
<string name="wol_pc_online">Esse computador está disponível</string>
|
||||
<string name="wol_no_mac">Não foi possível acordar o PC porque o GFE não enviou um endereço MAC</string>
|
||||
<string name="wol_waking_pc">Acordando o PC…</string>
|
||||
<string name="unpairing">Despareando…</string>
|
||||
<string name="unpair_error">Aparelho não pareado</string>
|
||||
<string name="error_pc_offline">Esse computador está indisponível</string>
|
||||
<string name="error_manager_not_running">O serviço ComputerManager não está em execução. Aguarde alguns segundos ou reinicie a app..</string>
|
||||
<string name="error_unknown_host">Falha ao encontrar o host</string>
|
||||
<string name="title_setup_guide">Guia de configuração</string>
|
||||
<string name="title_full_range">Forçar vídeo de gama completa de cores (Experimental)</string>
|
||||
<string name="conn_establishing_title">Estabelecendo Conexão</string>
|
||||
<string name="conn_establishing_msg">Iniciando a conexão</string>
|
||||
<string name="addpc_fail">Não foi possível conectar ao computador especificado. Certifique-se de que as portas necessárias sejam permitidas pelo firewall.</string>
|
||||
<string name="summary_fps_list">Aumente para uma transmissão de vídeo mais suave. Diminua para melhor desempenho em aparelhos de baixo custo.</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Driver de gamepad USB Xbox 360/One</string>
|
||||
<string name="title_checkbox_enable_sops">Otimizar configurações de jogo</string>
|
||||
<string name="summary_enable_post_stream_toast">Exibe uma mensagem com informações de latência após o término da transmissão</string>
|
||||
<string name="category_help">Ajuda</string>
|
||||
<string name="summary_setup_guide">Veja instruções sobre como configurar o seu PC para streaming</string>
|
||||
<string name="resolution_1440p">1440p</string>
|
||||
<string name="resolution_4k">4K</string>
|
||||
<string name="fps_30">30 FPS</string>
|
||||
<string name="pacing_latency">Preferir baixa latência</string>
|
||||
<string name="title_checkbox_mouse_nav_buttons">Ativar os botões de voltar e avançar do rato</string>
|
||||
<string name="summary_checkbox_mouse_nav_buttons">Ativar esta opção pode bugar o clique com o botão direito em alguns aparelhos com bugs</string>
|
||||
<string name="title_checkbox_flip_face_buttons">Inverter botões</string>
|
||||
<string name="title_privacy_policy">Política de privacidade</string>
|
||||
<string name="summary_privacy_policy">Veja a política de privacidade do Moonlight</string>
|
||||
<string name="resolution_480p">480p</string>
|
||||
<string name="resolution_720p">720p</string>
|
||||
<string name="summary_frame_pacing">Especifique o equilíbrio entre latência e suavidade do vídeo</string>
|
||||
</resources>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- PC view menu entries -->
|
||||
<string name="pcview_menu_app_list">Подивитися перелік додатків</string>
|
||||
<string name="pcview_menu_app_list">Подивитися перелік застосунків</string>
|
||||
<string name="pcview_menu_pair_pc">Створити пару з пристроєм</string>
|
||||
<string name="pcview_menu_unpair_pc">Розірвати пару</string>
|
||||
<string name="pcview_menu_send_wol">Надіслати запит Wake-On-LAN</string>
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="unpair_error">Пристрій не було спарено</string>
|
||||
<!-- Errors -->
|
||||
<string name="error_pc_offline">Пристрій вимкнено чи знаходиться поза мережею</string>
|
||||
<string name="error_manager_not_running">Сервіс ComputerManager не запущено. Будь ласка, зачекайте кілька секунд або перезапустіть додаток.</string>
|
||||
<string name="error_manager_not_running">Сервіс ComputerManager не запущено. Будь ласка, зачекайте кілька секунд або перезапустіть застосунок.</string>
|
||||
<string name="error_unknown_host">Неможливо знайти хоста</string>
|
||||
<string name="error_404">GFE повернув помилку HTTP 404. Переконайтеся що ваш пристрій використовує підтримуваний GPU. Використання програм для віддаленого доступу також може викликати цю помилку. Спробуйте перезавантажити пристрій або перевстановити GFE.</string>
|
||||
<!-- Start application messages -->
|
||||
@@ -56,14 +56,14 @@
|
||||
<string name="applist_menu_quit_and_start">Вийти з поточної гри і запустити</string>
|
||||
<string name="applist_menu_cancel">Скасувати</string>
|
||||
<string name="applist_menu_tv_channel">Додати на канал</string>
|
||||
<string name="applist_refresh_title">Перелік додатків</string>
|
||||
<string name="applist_refresh_msg">Оновлення додатків…</string>
|
||||
<string name="applist_refresh_title">Перелік застосунків</string>
|
||||
<string name="applist_refresh_msg">Оновлення застосунків…</string>
|
||||
<string name="applist_refresh_error_title">Помилка</string>
|
||||
<string name="applist_refresh_error_msg">Помилка отримання переліку додатків</string>
|
||||
<string name="applist_refresh_error_msg">Помилка отримання переліку застосунків</string>
|
||||
<string name="applist_quit_app">Виходимо з</string>
|
||||
<string name="applist_quit_success">Успішний вихід з</string>
|
||||
<string name="applist_quit_fail">Помилка при виході</string>
|
||||
<string name="applist_quit_confirmation">Ви впевнені, що хочете вийти з запущеного додатку? Усі незбережені дані будуть втрачені.</string>
|
||||
<string name="applist_quit_confirmation">Ви впевнені, що хочете вийти з запущеного застосунку\? Усі незбережені дані будуть втрачені.</string>
|
||||
<!-- Add computer manually activity -->
|
||||
<string name="title_add_pc">Додати пристрій вручну</string>
|
||||
<string name="msg_add_pc">З\'єднання з пристроєм…</string>
|
||||
@@ -93,7 +93,7 @@
|
||||
<string name="title_language_list">Мова</string>
|
||||
<string name="summary_language_list">Мова, яка буде використовуватися в Moonlight</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>
|
||||
@@ -121,7 +121,7 @@
|
||||
<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="summary_checkbox_enable_pip">Дозволяє переглядати стрім (але не контролювати) під час багатозадачності</string>
|
||||
<string name="title_checkbox_usb_bind_all">Вимкнути рідну підтримку Xbox контролерів від Андроїда</string>
|
||||
<string name="summary_checkbox_usb_bind_all">Використовувати USB драйвер Moonlight для усіх підтриманих контролерів, навіть якщо існує рідна підтримка</string>
|
||||
<string name="title_checkbox_mouse_emulation">Емуляція миші через контролер</string>
|
||||
@@ -148,7 +148,7 @@
|
||||
<string name="title_details">Деталі</string>
|
||||
<string name="title_enable_perf_overlay">Увімкнути відображення статистики</string>
|
||||
<string name="title_unlock_fps">Розблокувати всі можливі частоти кадрів</string>
|
||||
<string name="applist_details_id">ID додатку:</string>
|
||||
<string name="applist_details_id">ID застосунку:</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Емуляція вібровіддачі</string>
|
||||
<string name="summary_checkbox_vibrate_osc">Вібрація пристрою для емуляції вібровіддачі при екранному управлінні</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Вібрує пристрій для емуляції вібровіддачі, якщо під\'єднаний контролер не підтримує її</string>
|
||||
@@ -169,7 +169,7 @@
|
||||
<string name="perf_overlay_dectime">Середній час розцифрування: %1$.2f мс</string>
|
||||
<string name="summary_fps_list">Збільшіть для плавнішого відео потоку. Зменшіть для кращої продуктивності на повільних пристроях.</string>
|
||||
<string name="scut_invalid_uuid">Зазначений пристрій недійсний</string>
|
||||
<string name="scut_invalid_app_id">Зазначений додаток недійсний</string>
|
||||
<string name="scut_invalid_app_id">Зазначений застосунок недійсний</string>
|
||||
<string name="title_osc_opacity">Змінити прозорість екранних елементів керування</string>
|
||||
<string name="summary_osc_opacity">Зробити екранні елементи керування більш/менш прозорими</string>
|
||||
<string name="dialog_title_osc_opacity">Зміна прозорості</string>
|
||||
@@ -178,9 +178,9 @@
|
||||
<string name="summary_enable_post_stream_toast">Показувати статистику затримки після закінчення трансляції</string>
|
||||
<string name="early_termination_error">Щось пішло не так на пристрої хоста при початку трансляції.
|
||||
\n
|
||||
\nВпевніться що у вас не відкриті додатки з DRM змістом на пристрої хоста. Ви також можете спробувати перезапустити його.
|
||||
\nПереконайтеся, що на головному ПК немає відкритого вмісту, захищеного DRM. Ви також можете спробувати перезавантажити головний ПК.
|
||||
\n
|
||||
\nЯкщо проблема не виправиться, спробуйте перевстановити драйвера для вашої відео карти та, GeForce Experience якщо встановлено.</string>
|
||||
\nЯкщо проблема не зникає, спробуйте перевстановити драйвери GPU та GeForce Experience.</string>
|
||||
<string name="nettest_text_success">Схоже що ваша мережа не блокує Moonlight. Якщо у вас й надалі проблеми з підключенням, перевірте налаштування брандмауера.
|
||||
\n
|
||||
\nЯкщо ви намагаєтеся транслювати через Інтернет, встановіть на своєму пристрої Moonlight Internet Hosting Tool, та запустіть випробування мережі щоб перевірити ваше з\'єднання до Інтернету.</string>
|
||||
@@ -195,7 +195,7 @@
|
||||
\n
|
||||
\nЗрештою, пристрої клієнта чи хоста можуть не підтримувати трансляції рідною роздільною здатністю. Якщо це не працює на вашому пристрої, вам на жаль просто не пощастило.</string>
|
||||
<string name="title_native_res_dialog">Застереження про рідну роздільну здатність</string>
|
||||
<string name="applist_menu_hide_app">Сховати додаток</string>
|
||||
<string name="applist_menu_hide_app">Сховати застосунок</string>
|
||||
<string name="perf_overlay_netlatency">Середня затримка мережі: %1$d мс (розбіжність %2$d мс)</string>
|
||||
<string name="perf_overlay_streamdetails">Відео трансл: %1$s %2$.2f FPS</string>
|
||||
<string name="check_ports_msg">Перевірте правила брандмауера та переадресації для наступних портів:</string>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="pair_already_in_progress"> 配对中,请稍候 </string>
|
||||
<!-- WOL messages -->
|
||||
<string name="wol_pc_online"> 电脑在线中 </string>
|
||||
<string name="wol_no_mac"> 无法唤醒电脑因为GFE没有返回MAC地址 </string>
|
||||
<string name="wol_no_mac">无法唤醒电脑,因为没有存储的 MAC 地址</string>
|
||||
<string name="wol_waking_pc"> 唤醒电脑中…… </string>
|
||||
<string name="wol_waking_msg">唤醒电脑需要一些时间。如果电脑没有唤醒,请确保 Wake-On-LAN 设置无误。</string>
|
||||
<string name="wol_fail"> 无法发送Wake-On-LAN数据包 </string>
|
||||
@@ -42,7 +42,7 @@
|
||||
<string name="error_unknown_host"> 无法解析主机地址 </string>
|
||||
<string name="error_404">GFE返回了HTTP 404 错误,确保你的电脑显卡支持串流。使用远程桌面软件同样会引起此错误,请尝试重启电脑或重装GFE。</string>
|
||||
<string name="title_decoding_error"> 视频解码器崩溃 </string>
|
||||
<string name="message_decoding_error"> 由于与该设备的视频解码器不兼容,Moonlight已崩溃。确保你电脑上的GFE已更新至最新版本,如果崩溃继续,请尝试调整串流设置。 </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>
|
||||
@@ -60,10 +60,10 @@
|
||||
<string name="conn_terminated_title"> 连接终结 </string>
|
||||
<string name="conn_terminated_msg"> 连接已被终结 </string>
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint"> 串流电脑的IP地址 </string>
|
||||
<string name="searching_pc">正在搜寻运行GAMESTREAM的电脑……
|
||||
<string name="ip_hint">串流电脑的IP地址</string>
|
||||
<string name="searching_pc">在你的本地网络中搜索串流主机PC...
|
||||
\n
|
||||
\n 请确保GFE SHIELD设置里的GAMESTREAM已开启。</string>
|
||||
\n 确保Sunshine在主机上运行,或在GeForce Experience SHIELD设置中启用GameStream。</string>
|
||||
<string name="yes"> 确定 </string>
|
||||
<string name="no"> 取消 </string>
|
||||
<string name="lost_connection"> 与电脑失去连接 </string>
|
||||
@@ -167,8 +167,8 @@
|
||||
<string name="summary_disable_frame_drop"> 可能会减少在一些设备上的卡顿,但会增加延迟 </string>
|
||||
<string name="title_video_format"> 更改HEVC设置 </string>
|
||||
<string name="summary_video_format">HEVC能降低视频带宽需求,但需要较新的设备才能支持</string>
|
||||
<string name="title_enable_hdr"> 启用 HDR (实验) </string>
|
||||
<string name="summary_enable_hdr"> 当游戏和显卡支持时以HDR模式串流。 HDR需要GTX 1000系列或更高规格显卡。 </string>
|
||||
<string name="title_enable_hdr">启用 HDR (实验性)</string>
|
||||
<string name="summary_enable_hdr">当游戏和显卡支持时以HDR模式串流。 HDR需要显卡支持 HEVC Main 10 编码。</string>
|
||||
<string name="title_enable_perf_overlay"> 启用性能信息 </string>
|
||||
<string name="summary_enable_perf_overlay"> 在串流中显示实时性能信息 </string>
|
||||
<string name="title_enable_post_stream_toast">串流完毕显示延迟信息</string>
|
||||
@@ -186,7 +186,7 @@
|
||||
\n
|
||||
\n如果您想通过互联网进行流媒体传输,请在您的电脑上安装Moonlight互联网主机工具,并运行附带的互联网流媒体测试器来检查您的电脑的互联网连接。</string>
|
||||
<string name="nettest_title_done">网络测试完成</string>
|
||||
<string name="nettest_text_waiting">Moonlight正在测试你的网络连接,以确定NVIDIA GameStream是否被阻止。
|
||||
<string name="nettest_text_waiting">Moonlight正在测试你的网络连接,请确保所需端口没有被屏蔽。
|
||||
\n
|
||||
\n这可能需要几秒钟……</string>
|
||||
<string name="nettest_title_waiting">测试网络连接中</string>
|
||||
@@ -208,9 +208,7 @@
|
||||
<string name="check_ports_msg">请检查您的防火墙和端口转发规则中的端口:</string>
|
||||
<string name="early_termination_error">开始串流时您的主机电脑出了点问题。
|
||||
\n
|
||||
\n请确保没有在主机电脑上开启任何受DRM保护的内容。您也可以尝试重新启动主机电脑。
|
||||
\n
|
||||
\n如果问题仍然存在,请尝试重新安装GPU驱动和GFE。</string>
|
||||
\n请确保没有在主机电脑上开启任何受DRM保护的内容。您也可以尝试重新启动主机电脑。</string>
|
||||
<string name="no_frame_received_error">您的网络连接质量不佳。请降低视频码率设置或更换更快的网络连接。</string>
|
||||
<string name="no_video_received_error">没有接收到来自主机的视频。</string>
|
||||
<string name="video_decoder_init_failed">视频解码器初始化失败。您的设备可能不支持选定的分辨率或帧数。</string>
|
||||
@@ -260,4 +258,8 @@
|
||||
<string name="frame_conversion_error">主机 PC 报告了致命的视频编码错误。
|
||||
\n
|
||||
\n尝试禁用 HDR 模式、更改流分辨率或更改主机 PC 的显示分辨率。</string>
|
||||
<string name="summary_full_range">如果你的设备无法正确显示全范围视频内容,这会导致明暗区域的细节丢失。</string>
|
||||
<string name="title_full_range">强制完全动态范围视频 (实验性)</string>
|
||||
<string name="pair_pairing_help">如果你的主机运行的是 Sunshine,那请前往Sunshine的网页界面输入PIN码。</string>
|
||||
<string name="pcview_menu_eol">NVIDIA GameStream 终止服务</string>
|
||||
</resources>
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="pair_already_in_progress">正在配對,請稍候</string>
|
||||
<!-- WOL messages -->
|
||||
<string name="wol_pc_online">電腦已上線</string>
|
||||
<string name="wol_no_mac">無法喚醒電腦因為 GFE 沒有傳送 MAC 位址</string>
|
||||
<string name="wol_no_mac">無法喚醒電腦,因為沒有已儲存的 MAC 位址</string>
|
||||
<string name="wol_waking_pc">正在喚醒電腦…</string>
|
||||
<string name="wol_waking_msg">喚醒電腦需要一些時間。如果電腦沒有喚醒,請確保 Wake-On-LAN 設定無誤。</string>
|
||||
<string name="wol_fail">無法傳送 Wake-On-LAN 資料包</string>
|
||||
@@ -35,16 +35,16 @@
|
||||
<string name="unpairing">正在取消配對…</string>
|
||||
<string name="unpair_success"> 成功取消配對 </string>
|
||||
<string name="unpair_fail"> 無法配對 </string>
|
||||
<string name="unpair_error">裝置沒有配對過</string>
|
||||
<string name="unpair_error">裝置尚未配對</string>
|
||||
<!-- Errors -->
|
||||
<string name="error_pc_offline">電腦已離線</string>
|
||||
<string name="error_manager_not_running">ComputerManager 服務未執行。請稍等幾秒或重啟應用程式。</string>
|
||||
<string name="error_manager_not_running">ComputerManager 服務未在執行。請稍等幾秒或重啟應用程式。</string>
|
||||
<string name="error_unknown_host"> 無法解析主機位址 </string>
|
||||
<string name="error_404">GFE 傳回了 HTTP 404 錯誤。確保你的電腦顯示卡支援串流。使用遠端桌面軟體同樣會引起此錯誤,請嘗試重啟電腦或重新安裝 GFE。</string>
|
||||
<string name="title_decoding_error">視訊解碼器崩潰</string>
|
||||
<string name="message_decoding_error">由於與該裝置的視訊解碼器不相容,Moonlight 已崩潰。確保你電腦上的 GFE 已更新至最新版本,如果崩潰繼續,請嘗試調整串流設定。</string>
|
||||
<string name="error_404">GFE 傳回了 HTTP 404 錯誤,請確保你的電腦 GPU 支援串流。使用遠端桌面軟體同樣會引起此錯誤,請嘗試重啟電腦或重新安裝 GFE。</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="message_decoding_reset">由於裝置的視訊解碼器在你選取的串流設定上持續當機,已重設你的串流設定。</string>
|
||||
<string name="error_usb_prohibited">裝置管理員已禁止 USB 訪問。請檢查您的 Knox 或 MDM 設定。</string>
|
||||
<string name="unable_to_pin_shortcut">您目前的啟動器不允許創建釘選的捷徑。</string>
|
||||
<!-- Start application messages -->
|
||||
@@ -60,10 +60,10 @@
|
||||
<string name="conn_terminated_title">連線已終止</string>
|
||||
<string name="conn_terminated_msg">連線已終止</string>
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint">串流電腦的 IP 位址</string>
|
||||
<string name="searching_pc">正在搜尋執行 GAMESTREAM 的電腦…
|
||||
<string name="ip_hint">主機電腦的 IP 位址</string>
|
||||
<string name="searching_pc">正在您的區域網路中搜尋主機電腦…
|
||||
\n
|
||||
\n請確保 GFE SHIELD 設定裡的 GAMESTREAM 已啟用。</string>
|
||||
\n 請確保 Sunshine 正在您的主機電腦中執行,或在 GFE SHIELD 設定中啟用 GAMESTREAM。</string>
|
||||
<string name="yes"> 確定 </string>
|
||||
<string name="no"> 取消 </string>
|
||||
<string name="lost_connection">與電腦失去連線</string>
|
||||
@@ -74,8 +74,8 @@
|
||||
\n 請降低位元速率</string>
|
||||
<string name="poor_connection_msg">與電腦連線不良</string>
|
||||
<string name="perf_overlay_decoder">解碼器:%1$s</string>
|
||||
<string name="perf_overlay_incomingfps">網路接收影格數:%1$.2f FPS</string>
|
||||
<string name="perf_overlay_renderingfps">渲染影格數:%1$.2f FPS</string>
|
||||
<string name="perf_overlay_incomingfps">網路接收影格速率:%1$.2f FPS</string>
|
||||
<string name="perf_overlay_renderingfps">轉譯影格速率:%1$.2f FPS</string>
|
||||
<string name="perf_overlay_netdrops">網路丟失影格:%1$.2f%%</string>
|
||||
<string name="perf_overlay_dectime">平均解碼時間:%1$.2f ms</string>
|
||||
<!-- AppList activity -->
|
||||
@@ -92,7 +92,7 @@
|
||||
<string name="applist_refresh_error_title"> 錯誤 </string>
|
||||
<string name="applist_refresh_error_msg">無法取得遊戲清單</string>
|
||||
<string name="applist_quit_app">正在結束</string>
|
||||
<string name="applist_quit_success">結束成功</string>
|
||||
<string name="applist_quit_success">成功結束</string>
|
||||
<string name="applist_quit_fail">無法結束</string>
|
||||
<string name="applist_quit_confirmation">您確定要結束執行中的遊戲?所有未儲存的資料將丟失。</string>
|
||||
<string name="applist_details_id">App ID:</string>
|
||||
@@ -113,7 +113,7 @@
|
||||
<string name="title_seekbar_bitrate">視訊位元速率</string>
|
||||
<string name="summary_seekbar_bitrate">提高位元速率以提升圖像品質,降低可在較慢的網路中取得更好的串流體驗。</string>
|
||||
<string name="title_unlock_fps">解鎖所有可用影格速率</string>
|
||||
<string name="summary_unlock_fps">以 90 或 120 畫面更新率串流可能會減少在高效能裝置上的網路延時,但會在不支援的裝置上造成卡頓或崩潰</string>
|
||||
<string name="summary_unlock_fps">以 90 或 120 畫面更新率串流可能會減少在高效能裝置上的網路延時,但在不支援的裝置上會卡頓或不穩定的狀況</string>
|
||||
<string name="title_checkbox_stretch_video">將畫面延展至全螢幕</string>
|
||||
<string name="title_checkbox_disable_warnings">停用錯誤訊息</string>
|
||||
<string name="summary_checkbox_disable_warnings">停用串流中連線錯誤訊息</string>
|
||||
@@ -140,7 +140,7 @@
|
||||
<string name="title_checkbox_mouse_nav_buttons"> 啟用前進後退滑鼠鍵 </string>
|
||||
<string name="summary_checkbox_mouse_nav_buttons">在一些支援不佳的裝置上啟用此項可能會使其右鍵失效</string>
|
||||
<string name="title_checkbox_flip_face_buttons">反轉技能鍵</string>
|
||||
<string name="summary_checkbox_flip_face_buttons">為手把和虛擬控制器交換 A/B 和 X/Y 技能按鍵</string>
|
||||
<string name="summary_checkbox_flip_face_buttons">為手把和螢幕控制按鈕交換 A/B 和 X/Y 技能按鍵</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>
|
||||
@@ -152,7 +152,7 @@
|
||||
<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="toast_reset_osc_success">螢幕控制按鈕已重設為預設值</string>
|
||||
<string name="category_ui_settings">使用者介面設定</string>
|
||||
<string name="title_language_list"> 語言 </string>
|
||||
<string name="summary_language_list">選擇 Moonlight 顯示的語言</string>
|
||||
@@ -169,15 +169,15 @@
|
||||
<string name="title_video_format">變更 HEVC 設定</string>
|
||||
<string name="summary_video_format">HEVC 能降低視訊頻寬需求,但需要較新的裝置才能支援</string>
|
||||
<string name="title_enable_hdr">啟用 HDR (實驗性)</string>
|
||||
<string name="summary_enable_hdr">當遊戲和顯示卡支援時以 HDR 模式串流。 HDR 需要 GTX 1000 系列或更高規格顯示卡。</string>
|
||||
<string name="summary_enable_hdr">在遊戲和 GPU 支援時以 HDR 模式串流,HDR 模式需要支援 HEVC Main 10 編碼的 GPU。</string>
|
||||
<string name="title_enable_perf_overlay">串流時顯示效能資訊</string>
|
||||
<string name="summary_enable_perf_overlay">在串流中顯示即時效能資訊</string>
|
||||
<string name="title_enable_post_stream_toast">串流後顯示延時資訊</string>
|
||||
<string name="summary_enable_post_stream_toast">串流結束後顯示延時資訊</string>
|
||||
<string name="title_osc_opacity">變更螢幕按鈕透明度</string>
|
||||
<string name="title_osc_opacity">變更螢幕控制按鈕透明度</string>
|
||||
<string name="dialog_title_osc_opacity">變更不透明度</string>
|
||||
<string name="suffix_osc_opacity">%</string>
|
||||
<string name="summary_osc_opacity">令螢幕按钮變得更透明/更不透明</string>
|
||||
<string name="summary_osc_opacity">使螢幕控制按钮變得更透明/更不透明</string>
|
||||
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
|
||||
<string name="resolution_prefix_native">本機</string>
|
||||
<string name="text_native_res_dialog">本機解析度模式不受 GFE 的官方支援,因此不會自動設定主機的顯示解析度。您需要在遊戲中手動進行設定。
|
||||
@@ -190,11 +190,9 @@
|
||||
<string name="title_native_res_dialog">本機解析度警告</string>
|
||||
<string name="applist_menu_hide_app">隱藏遊戲</string>
|
||||
<string name="check_ports_msg">檢查您的防火牆和通訊埠轉送規則中的通訊埠:</string>
|
||||
<string name="early_termination_error">開始串流時您的主機電腦出了點問題。
|
||||
<string name="early_termination_error">您的主機電腦在開始串流時出了點問題。
|
||||
\n
|
||||
\n請確保沒有在主機電腦上開啟任何受 DRM 保護的內容。您也可以嘗試重新開機主機電腦。
|
||||
\n
|
||||
\n如果問題仍然存在,請嘗試重新安裝 GPU 驅動程式和 GFE。</string>
|
||||
\n請確保沒有在主機電腦上開啟任何受 DRM 保護的內容。您也可以嘗試重新開機主機電腦。</string>
|
||||
<string name="no_frame_received_error">您的網路連線品質不佳。降低視訊位元速率設定或更換更快的連線。</string>
|
||||
<string name="no_video_received_error">沒有接收到來自主機的視訊。</string>
|
||||
<string name="video_decoder_init_failed">無法初始化視訊解碼器,您的裝置可能不支援選取的解析度或影格速率。</string>
|
||||
@@ -208,7 +206,7 @@
|
||||
\n
|
||||
\n如果您是嘗試透過網際網路串流,請在您的電腦上安裝 Moonlight Internet Hosting Tool,然後執行裡面的 Internet Streaming Tester 來檢查電腦的網際網路連線。</string>
|
||||
<string name="nettest_title_done">網路檢測完畢</string>
|
||||
<string name="nettest_text_waiting">Moonlight 正在測試您的網路連線以確認 NVIDIA 遊戲串流服務是否被封鎖。
|
||||
<string name="nettest_text_waiting">Moonlight 正在測試您的網路連線以確認必要連接埠是否被封鎖。
|
||||
\n
|
||||
\n可能需要等待一些時間…</string>
|
||||
<string name="nettest_title_waiting">正在測試網路連線</string>
|
||||
@@ -263,4 +261,6 @@
|
||||
\n嘗試停用 HDR 模式,變更串流解析度,或變更您的主機電腦的顯示器解析度。</string>
|
||||
<string name="title_full_range">強制全範圍視訊 (實驗性)</string>
|
||||
<string name="summary_full_range">若您的裝置無法正確地顯示全範圍視訊內容,將會導致淺色區域和深色區域的細節遺失。</string>
|
||||
<string name="pair_pairing_help">若您的主機電腦正在執行 Sunshine,請導覽至 Sunshine 網頁 UI 並輸入 PIN 碼。</string>
|
||||
<string name="pcview_menu_eol">NVIDIA GameStream 終止服務</string>
|
||||
</resources>
|
||||
@@ -22,10 +22,11 @@
|
||||
<string name="pcview_menu_delete_pc">Delete PC</string>
|
||||
<string name="pcview_menu_test_network">Test Network Connection</string>
|
||||
<string name="pcview_menu_details">View Details</string>
|
||||
<string name="pcview_menu_eol">NVIDIA GameStream End-of-Service</string>
|
||||
|
||||
<!-- Network test strings -->
|
||||
<string name="nettest_title_waiting">Testing Network Connection</string>
|
||||
<string name="nettest_text_waiting">Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.\n\nThis may take a few seconds…</string>
|
||||
<string name="nettest_text_waiting">Moonlight is testing your network connection to determine if any required ports are blocked.\n\nThis may take a few seconds…</string>
|
||||
<string name="nettest_title_done">Network Test Complete</string>
|
||||
<string name="nettest_text_success">Your network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC\'s firewall settings.\n\nIf you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your PC and run the included Internet Streaming Tester to check your PC\'s Internet connection.</string>
|
||||
<string name="nettest_text_inconclusive">The network test could not be performed because none of Moonlight\'s connection testing servers were reachable. Check your Internet connection or try again later.</string>
|
||||
@@ -38,13 +39,14 @@
|
||||
<string name="pair_pc_ingame">Computer is currently in a game. You must close the game before pairing.</string>
|
||||
<string name="pair_pairing_title">Pairing</string>
|
||||
<string name="pair_pairing_msg">Please enter the following PIN on the target PC:</string>
|
||||
<string name="pair_pairing_help">If your host PC is running Sunshine, navigate to the Sunshine web UI to enter the PIN.</string>
|
||||
<string name="pair_incorrect_pin">Incorrect PIN</string>
|
||||
<string name="pair_fail">Pairing failed</string>
|
||||
<string name="pair_already_in_progress">Pairing already in progress</string>
|
||||
|
||||
<!-- WOL messages -->
|
||||
<string name="wol_pc_online">Computer is online</string>
|
||||
<string name="wol_no_mac">Unable to wake PC because GFE didn\'t send a MAC address</string>
|
||||
<string name="wol_no_mac">Unable to wake PC because there is no stored MAC address</string>
|
||||
<string name="wol_waking_pc">Waking PC…</string>
|
||||
<string name="wol_waking_msg">It may take a few seconds for your PC to wake up.
|
||||
If it doesn\'t, make sure it\'s configured properly for Wake-On-LAN.
|
||||
@@ -65,7 +67,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 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="message_decoding_error">Moonlight has crashed due to an incompatibility with this device\'s video decoder. Try adjusting the streaming settings if the crashes continue.</string>
|
||||
<string name="title_decoding_reset">Video Settings Reset</string>
|
||||
<string name="message_decoding_reset">Your device\'s video decoder continues to crash at your selected streaming settings. Your streaming settings have been reset to default.</string>
|
||||
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
|
||||
@@ -73,7 +75,7 @@
|
||||
<string name="video_decoder_init_failed">Video decoder failed to initialize. Your device may not support the selected resolution or frame rate.</string>
|
||||
<string name="no_video_received_error">No video received from host.</string>
|
||||
<string name="no_frame_received_error">Your network connection isn\'t performing well. Reduce your video bitrate setting or try a faster connection.</string>
|
||||
<string name="early_termination_error">Something went wrong on your host PC when starting the stream.\n\nMake sure you don\'t have any DRM-protected content open on your host PC. You can also try restarting your host PC.\n\nIf the issue persists, try reinstalling your GPU drivers and GeForce Experience.</string>
|
||||
<string name="early_termination_error">Something went wrong on your host PC when starting the stream.\n\nMake sure you don\'t have any DRM-protected content open on your host PC. You can also try restarting your host PC.</string>
|
||||
<string name="frame_conversion_error">The host PC reported a fatal video encoding error.\n\nTry disabling HDR mode, changing the streaming resolution, or changing your host PC\'s display resolution.</string>
|
||||
<string name="check_ports_msg">Check your firewall and port forwarding rules for port(s):</string>
|
||||
|
||||
@@ -91,9 +93,9 @@
|
||||
<string name="conn_terminated_msg">The connection was terminated</string>
|
||||
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint">IP address of GeForce PC</string>
|
||||
<string name="searching_pc">Searching for PCs with GameStream running…\n\n
|
||||
Ensure GameStream is enabled in the GeForce Experience SHIELD settings.</string>
|
||||
<string name="ip_hint">IP address of host PC</string>
|
||||
<string name="searching_pc">Searching for host PCs on your local network…\n\n
|
||||
Ensure Sunshine is running on your host PC or GameStream is enabled in the GeForce Experience SHIELD settings.</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="lost_connection">Lost connection to PC</string>
|
||||
@@ -228,7 +230,7 @@
|
||||
<string name="title_video_format">Change HEVC settings</string>
|
||||
<string name="summary_video_format">HEVC lowers video bandwidth requirements but requires a newer device</string>
|
||||
<string name="title_enable_hdr">Enable HDR (Experimental)</string>
|
||||
<string name="summary_enable_hdr">Stream HDR when the game and PC GPU support it. HDR requires a GTX 1000 series GPU or later.</string>
|
||||
<string name="summary_enable_hdr">Stream HDR when the game and PC GPU support it. HDR requires a GPU with HEVC Main 10 encoding support.</string>
|
||||
<string name="title_full_range">Force full range video (Experimental)</string>
|
||||
<string name="summary_full_range">This will cause loss of detail in light and dark areas if your device doesn\'t properly display full range video content.</string>
|
||||
<string name="title_enable_perf_overlay">Show performance stats while streaming</string>
|
||||
|
||||
@@ -38,7 +38,8 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
||||
public void run() {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
byte deltaScroll = 0;
|
||||
byte deltaVScroll = 0;
|
||||
byte deltaHScroll = 0;
|
||||
|
||||
// Bind a local listening socket for evdevreader to connect to
|
||||
try {
|
||||
@@ -115,9 +116,13 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
||||
listener.mouseMove(deltaX, deltaY);
|
||||
deltaX = deltaY = 0;
|
||||
}
|
||||
if (deltaScroll != 0) {
|
||||
listener.mouseScroll(deltaScroll);
|
||||
deltaScroll = 0;
|
||||
if (deltaVScroll != 0) {
|
||||
listener.mouseVScroll(deltaVScroll);
|
||||
deltaVScroll = 0;
|
||||
}
|
||||
if (deltaHScroll != 0) {
|
||||
listener.mouseHScroll(deltaHScroll);
|
||||
deltaHScroll = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -129,8 +134,11 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
||||
case EvdevEvent.REL_Y:
|
||||
deltaY = event.value;
|
||||
break;
|
||||
case EvdevEvent.REL_HWHEEL:
|
||||
deltaHScroll = (byte) event.value;
|
||||
break;
|
||||
case EvdevEvent.REL_WHEEL:
|
||||
deltaScroll = (byte) event.value;
|
||||
deltaVScroll = (byte) event.value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -13,6 +13,7 @@ public class EvdevEvent {
|
||||
/* Relative axes */
|
||||
public static final short REL_X = 0x00;
|
||||
public static final short REL_Y = 0x01;
|
||||
public static final short REL_HWHEEL = 0x06;
|
||||
public static final short REL_WHEEL = 0x08;
|
||||
|
||||
/* Buttons */
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- Added support for horizontal scrolling with Sunshine hosts
|
||||
- Enhanced HDR tonemapping when streaming from Sunshine hosts
|
||||
- Reduced input latency via increased input polling rate
|
||||
- Added support for typing non-ASCII characters with the software keyboard
|
||||
- Fixed issues detecting Sunshine and GFE running on the same PC
|
||||
- Fixed relative mouse mode not working on some Chrome OS devices
|
||||
- Fixed error message appearing after Wake-on-LAN even when it was successful
|
||||
- Fixed a bug handling PCs with addresses that contain underscores
|
||||
- Fixed a bug that could cause Chromecasts to enter 24 Hz mode when streaming
|
||||
- Fixed touchscreen trackpad mode toggle not appearing on certain devices
|
||||
@@ -1,6 +1,6 @@
|
||||
This app streams games, programs, or your full desktop from an NVIDIA GameStream-compatible PC or <a href="https://github.com/SunshineStream/Sunshine">Sunshine Stream</a> on your local network or the Internet. Mouse, keyboard, and controller input is sent from your Android device to the PC.
|
||||
This app streams games, programs, or your full desktop from your PC or <a href="https://github.com/LizardByte/Sunshine">Sunshine</a> on your local network or the Internet. Mouse, keyboard, and controller input is sent from your Android device to the PC.
|
||||
|
||||
Streaming performance may vary based on your client device and network setup. HDR requires an HDR10-capable device, GTX 1000-series GPU, and HDR10-enabled game.
|
||||
Streaming performance may vary based on your client device and network setup. HDR requires an HDR10-capable device, a GPU that can encode HEVC Main 10, and an HDR10-enabled game.
|
||||
|
||||
'''Features'''
|
||||
* Open-source and completely free (no ads, IAPs, or "Pro")
|
||||
@@ -9,25 +9,22 @@ Streaming performance may vary based on your client device and network setup. HD
|
||||
* Up to 4K 120 FPS HDR streaming with 7.1 surround sound
|
||||
* Keyboard and mouse support (best with Android 8.0 or later)
|
||||
* Stylus/S-Pen support
|
||||
* Supports PS3, PS4, Xbox 360, Xbox One, and Android gamepads
|
||||
* Supports PS3/4/5, Xbox 360/One/Series, and Android gamepads
|
||||
* Force feedback support
|
||||
* Local co-op with up to 4 connected controllers
|
||||
* Mouse control via gamepad by long-pressing Start
|
||||
|
||||
'''PC Requirements for NVIDIA'''
|
||||
* NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU
|
||||
* Windows 7 or later
|
||||
* NVIDIA GeForce Experience or NVIDIA Quadro Experience installed
|
||||
|
||||
'''PC Requirements for SunshineStream'''
|
||||
* Linux, MacOS, Windows
|
||||
* AMD, Nvidia GPUs or Software encoding
|
||||
|
||||
'''Quick Setup Instructions'''
|
||||
# Make sure GeForce/Quadro Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
|
||||
'''Quick Setup Host Instructions for GeForce Experience (NVIDIA-only)'''
|
||||
# Make sure GeForce Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
|
||||
# Tap on the PC in Moonlight and type the PIN on your PC
|
||||
# Start streaming!
|
||||
|
||||
'''Quick Setup Host Instructions for Sunshine (all GPUs)'''
|
||||
# Install Sunshine on your PC from https://github.com/LizardByte/Sunshine/releases
|
||||
# Navigate to the Sunshine Web UI on your PC for first-time setup
|
||||
# Tap on the PC in Moonlight and type the PIN in the Sunshine Web UI on your PC
|
||||
# Start streaming!
|
||||
|
||||
To have a good experience, you need a mid to high-end wireless router with a good wireless connection to your Android device (5 GHz highly recommended) and a good connection from your PC to your router (Ethernet highly recommended).
|
||||
|
||||
'''Detailed Setup Instructions'''
|
||||
|
||||
@@ -1 +1 @@
|
||||
Play games from your PC on Android (NVIDIA or SunshineStream)
|
||||
Play games from your PC on Android
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Reference in New Issue
Block a user