Compare commits

..

44 Commits

Author SHA1 Message Date
Cameron Gutman 4bdc2e0aba Add F-Droid metadata for 274 2022-05-22 18:02:21 -05:00
Cameron Gutman e69061082b Version 10.1.1 2022-05-22 17:16:37 -05:00
Cameron Gutman 1da2ec3cb1 Merge remote-tracking branch 'origin/weblate' 2022-05-22 17:15:44 -05:00
Cameron Gutman 8ffc3b80b2 Rework use of URLs in NvHTTP
- Fixes parsing inconsistencies between URI and HttpUrl
- Fixes a couple of serverinfo requests sent without uniqueid and UUID
- Avoids PairingManager having to look into NvHTTP internals
2022-05-22 16:47:45 -05:00
Cameron Gutman 08f8b6cb8e Keep the SpinnerDialog visible while the connectivity test runs 2022-05-22 15:36:38 -05:00
Cameron Gutman fb09c9692c Fix handling of InterruptedExceptions 2022-05-22 15:31:06 -05:00
Cameron Gutman 4901b0c78f Stop parallel polling threads when we find a working address 2022-05-22 14:56:28 -05:00
Cameron Gutman 0a2117241f Wrap Choreographer calls to releaseOutputBuffer() in try/catch 2022-05-22 14:32:03 -05:00
Wen-haur Chiu f352cfd15b Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (218 of 218 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-05-21 14:16:28 +02:00
Cameron Gutman ac7c5c1064 Improve handling of required XML tags 2022-05-20 17:15:26 -05:00
Cameron Gutman 077cb2103d Version 10.1 2022-05-18 22:44:46 -05:00
Cameron Gutman cdeda011a4 Temporarily disable in-app links until they are translated 2022-05-18 22:40:47 -05:00
Cameron Gutman 894c146988 Fix JAVA_HOME path on VS2022 CI image 2022-05-18 00:57:05 -05:00
Cameron Gutman 61cc9e151f Use newer AppVeyor machine image 2022-05-18 00:38:47 -05:00
Cameron Gutman cfe4c9ff21 Target Android 12L 2022-05-17 17:16:28 -05:00
Cameron Gutman d4bd29b320 Properly deal with battery saver mode in capped FPS mode 2022-05-17 00:14:55 -05:00
Cameron Gutman 7f2f2056c3 Add in-app privacy policy link to comply with Google Play policies
Also added Setup Guide and Troubleshooting Guide links too.
2022-05-15 15:56:19 -05:00
Cameron Gutman 4dd3b2cfb7 Tweak capped FPS option text 2022-05-14 23:33:43 -05:00
Cameron Gutman 2e62ad0f00 Merge remote-tracking branch 'origin/weblate' 2022-05-14 23:31:51 -05:00
Cameron Gutman 41ef292b82 Fix frame rate cap not taking effect with the unlock FPS option enabled 2022-05-14 21:19:51 -05:00
Cameron Gutman aa60671c88 Return the selected refresh rate now that the capped FPS mode is not default 2022-05-14 20:53:42 -05:00
Cameron Gutman f1ccba39e8 Don't raise refresh rate above stream FPS except in min latency mode 2022-05-14 20:53:07 -05:00
Cameron Gutman 226e580a30 Prevent microstutter in balanced mode when streaming at 60 FPS on a 120 Hz display 2022-05-14 20:08:41 -05:00
Cameron Gutman 6f8e719200 Update AGP 2022-05-14 18:25:48 -05:00
Cameron Gutman c127af1e05 Rewrite polling logic to avoid needing to poll using a separate socket first 2022-05-14 18:14:37 -05:00
Wen-haur Chiu 648904cc69 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (211 of 211 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-05-11 19:13:59 +02:00
Cameron Gutman dc85ddb3f9 Reintroduce option of using old frame pacing algorithm using capped FPS 2022-05-08 15:20:08 -05:00
Cameron Gutman 23a7d8555f Avoid activity restarts in StreamSettings and AddComputerManually
We would ideally save and restore state, but this is fine for these specific
transient user activities.

Fixes #1052
Fixes #1055
2022-05-08 14:55:47 -05:00
Cameron Gutman bc9e250d34 Merge remote-tracking branch 'origin/weblate' 2022-05-08 14:40:07 -05:00
Cameron Gutman 2203186527 Remove extra ViewGroup between OSC and StreamView
This allows touch events to be properly split
2022-05-08 14:39:32 -05:00
DankXylese 53d3d9ecb8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/uk/
2022-04-28 21:13:07 +02:00
Cameron Gutman de549f67a1 Update README 2022-04-05 19:51:44 -05:00
Jorys Paulin 755c41481a Translated using Weblate (French)
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/fr/
2022-04-04 10:12:11 +02:00
Dominik Chrástecký aebc2126bc Added translation using Weblate (Czech) 2022-04-03 19:50:26 +02:00
Wen-haur Chiu f43547fb31 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-03-25 18:11:43 +01:00
CorteX 398e4df7cf Translated using Weblate (Chinese (Simplified))
Currently translated at 98.0% (206 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hans/
2022-03-23 06:58:35 +01:00
reloxx13 ff68efc3f5 Translated using Weblate (German)
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/de/
2022-03-21 09:59:13 +01:00
Caio Gabriel 8ba2f51bda Translated using Weblate (Portuguese)
Currently translated at 9.0% (19 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt/
2022-03-19 22:58:22 +01:00
Caio Gabriel 87b79b278b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt_BR/
2022-03-19 22:58:21 +01:00
Caio Gabriel 121e3ea9be Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt_BR/
2022-03-18 16:45:57 +01:00
Caio Gabriel ec6ed79ee1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt_BR/
2022-03-18 03:47:44 +01:00
Caio Gabriel ca125826a7 Translated using Weblate (Portuguese)
Currently translated at 8.5% (18 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt/
2022-03-17 22:58:19 +01:00
Cameron Gutman dd0aecf108 Update BouncyCastle 2022-03-15 22:16:41 -05:00
Wen-haur Chiu ef5cb2f0cd Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-03-09 15:58:49 +01:00
36 changed files with 931 additions and 378 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/232a8tadrrn8jv0k/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
[![Translation Status](https://hosted.weblate.org/widgets/moonlight/-/moonlight-android/svg-badge.svg)](https://hosted.weblate.org/projects/moonlight/moonlight-android/)
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
[Moonlight for Android](https://moonlight-stream.org) is an open source client for NVIDIA GameStream, as used by the NVIDIA Shield.
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
whether in your own home or over the internet.
+6 -6
View File
@@ -3,14 +3,14 @@ apply plugin: 'com.android.application'
android {
ndkVersion "23.1.7779620"
compileSdkVersion 31
compileSdkVersion 32
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
targetSdkVersion 32
versionName "10.0"
versionCode = 272
versionName "10.1.1"
versionCode = 274
}
flavorDimensions "root"
@@ -118,8 +118,8 @@ android {
}
dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'org.jcodec:jcodec:0.2.3'
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
implementation 'com.squareup.okio:okio:1.17.5'
+2
View File
@@ -99,6 +99,7 @@
<activity
android:name=".preferences.StreamSettings"
android:resizeableActivity="true"
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
android:label="Streaming Settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -108,6 +109,7 @@
android:name=".preferences.AddComputerManually"
android:resizeableActivity="true"
android:windowSoftInputMode="stateVisible"
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
android:label="Add Computer Manually">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
+55 -8
View File
@@ -389,6 +389,28 @@ public class Game extends Activity implements SurfaceHolder.Callback,
float displayRefreshRate = prepareDisplayForRendering();
LimeLog.info("Display refresh rate: "+displayRefreshRate);
// If the user requested frame pacing using a capped FPS, we will need to change our
// desired FPS setting here in accordance with the active display refresh rate.
int roundedRefreshRate = Math.round(displayRefreshRate);
int chosenFrameRate = prefConfig.fps;
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
if (prefConfig.fps >= roundedRefreshRate) {
if (prefConfig.fps > roundedRefreshRate + 3) {
// Use frame drops when rendering above the screen frame rate
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
LimeLog.info("Using drop mode for FPS > Hz");
} else if (roundedRefreshRate <= 49) {
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
LimeLog.info("Bogus refresh rate: " + roundedRefreshRate);
}
else {
chosenFrameRate = roundedRefreshRate - 1;
LimeLog.info("Adjusting FPS target for screen to " + chosenFrameRate);
}
}
}
boolean vpnActive = NetHelper.isActiveNetworkVpn(this);
if (vpnActive) {
LimeLog.info("Detected active network is a VPN");
@@ -397,7 +419,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
StreamConfiguration config = new StreamConfiguration.Builder()
.setResolution(prefConfig.width, prefConfig.height)
.setLaunchRefreshRate(prefConfig.fps)
.setRefreshRate(prefConfig.fps)
.setRefreshRate(chosenFrameRate)
.setApp(new NvApp(appName != null ? appName : "app", appId, appSupportsHdr))
.setBitrate(prefConfig.bitrate)
.setEnableSops(prefConfig.enableSops)
@@ -573,6 +595,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
inputCaptureProvider.onWindowFocusChanged(hasFocus);
}
private boolean isRefreshRateEqualMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
refreshRate <= prefConfig.fps + 3;
}
private boolean isRefreshRateGoodMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
Math.round(refreshRate) % prefConfig.fps <= 3;
@@ -608,6 +635,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Display.Mode bestMode = display.getMode();
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate());
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
@@ -639,12 +667,30 @@ public class Game extends Activity implements SurfaceHolder.Callback,
continue;
}
if (refreshRateIsGood) {
// We have a good matching refresh rate, so we're looking for equal or greater
// that is also a good matching refresh rate for our stream frame rate.
if (refreshRateReduced || !isRefreshRateGoodMatch(candidate.getRefreshRate())) {
if (prefConfig.framePacing != PreferenceConfiguration.FRAME_PACING_MIN_LATENCY &&
refreshRateIsEqual && !isRefreshRateEqualMatch(candidate.getRefreshRate())) {
// If we had an equal refresh rate and this one is not, skip it. In min latency
// mode, we want to always prefer the highest frame rate even though it may cause
// microstuttering.
continue;
}
else if (refreshRateIsGood) {
// We've already got a good match, so if this one isn't also good, it's not
// worth considering at all.
if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
continue;
}
// We don't want ever reduce our refresh rate unless we found an exact
// match and we're not in min latency mode.
if (refreshRateReduced) {
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_MIN_LATENCY) {
continue;
}
else if (!isRefreshRateEqualMatch(candidate.getRefreshRate())) {
continue;
}
}
}
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
// We didn't have a good match and this match isn't good either, so just don't
@@ -661,6 +707,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
bestMode = candidate;
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
refreshRateIsEqual = isRefreshRateEqualMatch(candidate.getRefreshRate());
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
@@ -741,9 +788,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return displayRefreshRate;
}
else {
// Use the actual refresh rate of the display, since the preferred refresh rate or mode
// may not actually be applied (ex: Pixel 4 with Smooth Display disabled).
return getWindowManager().getDefaultDisplay().getRefreshRate();
// Use the lower of the current refresh rate and the selected refresh rate.
// The preferred refresh rate may not actually be applied (ex: Battery Saver mode).
return Math.min(getWindowManager().getDefaultDisplay().getRefreshRate(), displayRefreshRate);
}
}
@@ -1482,7 +1482,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// UI thread.
try {
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
switch (keyCode) {
@@ -1591,7 +1598,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
try {
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
@@ -1609,7 +1623,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
try {
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
@@ -37,7 +37,9 @@ public abstract class AbstractXboxController extends AbstractController {
// around when we call notifyDeviceAdded(), we won't be able to claim
// the controller number used by the original InputDevice.
Thread.sleep(1000);
} catch (InterruptedException e) {}
} catch (InterruptedException e) {
return;
}
// Report that we're added _before_ reporting input
notifyDeviceAdded();
@@ -116,7 +116,14 @@ public class AbsoluteTouchContext implements TouchContext {
try {
// FIXME: Sleeping on the main thread sucks
Thread.sleep(50);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
@@ -139,7 +139,14 @@ public class RelativeTouchContext implements TouchContext {
// do input detection by polling
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
// Raise the mouse button
conn.sendMouseButtonUp(buttonIndex);
@@ -9,12 +9,11 @@ import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.R;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.nvstream.NvConnection;
import java.util.ArrayList;
import java.util.List;
@@ -40,11 +39,10 @@ public class VirtualController {
private static final boolean _PRINT_DEBUG_INFORMATION = false;
private ControllerHandler controllerHandler;
private Context context = null;
private final ControllerHandler controllerHandler;
private final Context context;
private FrameLayout frame_layout = null;
private RelativeLayout relative_layout = null;
private Timer retransmitTimer;
@@ -60,10 +58,6 @@ public class VirtualController {
this.frame_layout = layout;
this.context = context;
relative_layout = new RelativeLayout(context);
frame_layout.addView(relative_layout);
buttonConfigure = new Button(context);
buttonConfigure.setAlpha(0.25f);
buttonConfigure.setFocusable(false);
@@ -87,7 +81,7 @@ public class VirtualController {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
relative_layout.invalidate();
buttonConfigure.invalidate();
for (VirtualControllerElement element : elements) {
element.invalidate();
@@ -99,11 +93,20 @@ public class VirtualController {
public void hide() {
retransmitTimer.cancel();
relative_layout.setVisibility(View.INVISIBLE);
for (VirtualControllerElement element : elements) {
element.setVisibility(View.INVISIBLE);
}
buttonConfigure.setVisibility(View.INVISIBLE);
}
public void show() {
relative_layout.setVisibility(View.VISIBLE);
for (VirtualControllerElement element : elements) {
element.setVisibility(View.VISIBLE);
}
buttonConfigure.setVisibility(View.VISIBLE);
// HACK: GFE sometimes discards gamepad packets when they are received
// very shortly after another. This can be critical if an axis zeroing packet
@@ -120,9 +123,11 @@ public class VirtualController {
public void removeElements() {
for (VirtualControllerElement element : elements) {
relative_layout.removeView(element);
frame_layout.removeView(element);
}
elements.clear();
frame_layout.removeView(buttonConfigure);
}
public void setOpacity(int opacity) {
@@ -134,10 +139,10 @@ public class VirtualController {
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
elements.add(element);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width, height);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
layoutParams.setMargins(x, y, 0, 0);
relative_layout.addView(element, layoutParams);
frame_layout.addView(element, layoutParams);
}
public List<VirtualControllerElement> getElements() {
@@ -146,23 +151,20 @@ public class VirtualController {
private static final void _DBG(String text) {
if (_PRINT_DEBUG_INFORMATION) {
System.out.println("VirtualController: " + text);
LimeLog.info("VirtualController: " + text);
}
}
public void refreshLayout() {
relative_layout.removeAllViews();
removeElements();
DisplayMetrics screen = context.getResources().getDisplayMetrics();
int buttonSize = (int)(screen.heightPixels*0.06f);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonSize, buttonSize);
params.leftMargin = 15;
params.topMargin = 15;
relative_layout.addView(buttonConfigure, params);
frame_layout.addView(buttonConfigure, params);
// Start with the default layout
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
@@ -12,7 +12,7 @@ import android.graphics.Paint;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.FrameLayout;
import org.json.JSONException;
import org.json.JSONObject;
@@ -72,7 +72,7 @@ public abstract class VirtualControllerElement extends View {
int newPos_x = (int) getX() + x - pressed_x;
int newPos_y = (int) getY() + y - pressed_y;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
@@ -83,7 +83,7 @@ public abstract class VirtualControllerElement extends View {
}
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
int newHeight = height + (startSize_y - pressed_y);
int newWidth = width + (startSize_x - pressed_x);
@@ -316,7 +316,7 @@ public abstract class VirtualControllerElement extends View {
public JSONObject getConfiguration() throws JSONException {
JSONObject configuration = new JSONObject();
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
configuration.put("LEFT", layoutParams.leftMargin);
configuration.put("TOP", layoutParams.topMargin);
@@ -327,7 +327,7 @@ public abstract class VirtualControllerElement extends View {
}
public void loadConfiguration(JSONObject configuration) throws JSONException {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = configuration.getInt("LEFT");
layoutParams.topMargin = configuration.getInt("TOP");
@@ -86,6 +86,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
private long lastRenderedFrameTimeNanos;
private int numSpsIn;
private int numPpsIn;
@@ -419,21 +420,33 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
return;
}
// Render up to one frame when in frame pacing mode.
//
// NB: Since the queue limit is 2, we won't starve the decoder of output buffers
// by holding onto them for too long. This also ensures we will have that 1 extra
// frame of buffer to smooth over network/rendering jitter.
Integer nextOutputBuffer = outputBufferQueue.poll();
if (nextOutputBuffer != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, frameTimeNanos);
}
else {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
}
// Don't render unless a new frame is due. This prevents microstutter when streaming
// at a frame rate that doesn't match the display (such as 60 FPS on 120 Hz).
long actualFrameTimeDeltaNs = frameTimeNanos - lastRenderedFrameTimeNanos;
long expectedFrameTimeDeltaNs = 800000000 / refreshRate; // within 80% of the next frame
if (actualFrameTimeDeltaNs >= expectedFrameTimeDeltaNs) {
// Render up to one frame when in frame pacing mode.
//
// NB: Since the queue limit is 2, we won't starve the decoder of output buffers
// by holding onto them for too long. This also ensures we will have that 1 extra
// frame of buffer to smooth over network/rendering jitter.
Integer nextOutputBuffer = outputBufferQueue.poll();
if (nextOutputBuffer != null) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, frameTimeNanos);
}
else {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
}
activeWindowVideoStats.totalFramesRendered++;
lastRenderedFrameTimeNanos = frameTimeNanos;
activeWindowVideoStats.totalFramesRendered++;
} catch (Exception e) {
// This will leak nextOutputBuffer, but there's really nothing else we can do
handleDecoderException(e, null, 0, false);
}
}
}
// Request another callback for next frame
@@ -468,8 +481,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
presentationTimeUs = info.presentationTimeUs;
}
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS) {
// In max smoothness mode, we want to never drop frames
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS ||
prefs.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
// In max smoothness or cap FPS mode, we want to never drop frames
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Use a PTS that will cause this frame to never be dropped
videoDecoder.releaseOutputBuffer(lastIndex, 0);
@@ -625,7 +639,14 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
// Wait for the renderer thread to shut down
try {
rendererThread.join();
} catch (InterruptedException ignored) { }
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
@Override
@@ -5,8 +5,6 @@ import java.io.OutputStream;
import java.io.StringReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.LinkedList;
@@ -49,8 +47,7 @@ public class ComputerManagerService extends Service {
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
private static final int MDNS_QUERY_PERIOD_MS = 1000;
private static final int FAST_POLL_TIMEOUT = 1000;
private static final int OFFLINE_POLL_TRIES = 5;
private static final int OFFLINE_POLL_TRIES = 3;
private static final int INITIAL_POLL_TRIES = 2;
private static final int EMPTY_LIST_THRESHOLD = 3;
private static final int POLL_DATA_TTL_MS = 30000;
@@ -232,7 +229,13 @@ public class ComputerManagerService extends Service {
// Wait for the bind notification
discoveryServiceConnection.wait(1000);
}
} catch (InterruptedException ignored) {
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
@@ -241,11 +244,18 @@ public class ComputerManagerService extends Service {
while (activePolls.get() != 0) {
try {
Thread.sleep(250);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
return ComputerManagerService.this.addComputerBlocking(fakeDetails);
}
@@ -399,9 +409,18 @@ public class ComputerManagerService extends Service {
details.ipv6Address = computer.getIpv6Address().getHostAddress();
}
// Kick off a serverinfo poll on this machine
if (!addComputerBlocking(details)) {
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
try {
// Kick off a blocking serverinfo poll on this machine
if (!addComputerBlocking(details)) {
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
@@ -449,28 +468,25 @@ public class ComputerManagerService extends Service {
}
}
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
// Block while we try to fill the details
try {
// We cannot use runPoll() here because it will attempt to persist the state of the machine
// in the database, which would be bad because we don't have our pinned cert loaded yet.
if (pollComputer(fakeDetails)) {
// See if we have record of this PC to pull its pinned cert
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
fakeDetails.serverCert = tuple.computer.serverCert;
break;
}
// We cannot use runPoll() here because it will attempt to persist the state of the machine
// in the database, which would be bad because we don't have our pinned cert loaded yet.
if (pollComputer(fakeDetails)) {
// See if we have record of this PC to pull its pinned cert
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
fakeDetails.serverCert = tuple.computer.serverCert;
break;
}
}
// Poll again, possibly with the pinned cert, to get accurate pairing information.
// This will insert the host into the database too.
runPoll(fakeDetails, true, 0);
}
} catch (InterruptedException e) {
return false;
// Poll again, possibly with the pinned cert, to get accurate pairing information.
// This will insert the host into the database too.
runPoll(fakeDetails, true, 0);
}
// If the machine is reachable, it was successful
@@ -528,11 +544,6 @@ public class ComputerManagerService extends Service {
}
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
// Fast poll this address first to determine if we can connect at the TCP layer
if (!fastPollIp(address)) {
return null;
}
try {
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
@@ -551,146 +562,140 @@ public class ComputerManagerService extends Service {
return null;
}
// Set the new active address
newDetails.activeAddress = address;
return newDetails;
} catch (XmlPullParserException | IOException e) {
} catch (XmlPullParserException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
return null;
}
}
// Just try to establish a TCP connection to speculatively detect a running
// GFE server
private boolean fastPollIp(String address) {
if (address == null) {
// Don't bother if our address is null
return false;
private static class ParallelPollTuple {
public String address;
public ComputerDetails existingDetails;
public boolean complete;
public Thread pollingThread;
public ComputerDetails returnedDetails;
public ParallelPollTuple(String address, ComputerDetails existingDetails) {
this.address = address;
this.existingDetails = existingDetails;
}
Socket s = new Socket();
try {
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
s.close();
return true;
} catch (IOException e) {
return false;
public void interrupt() {
if (pollingThread != null) {
pollingThread.interrupt();
}
}
}
private void startFastPollThread(final String address, final boolean[] info) {
Thread t = new Thread() {
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<String> uniqueAddresses) {
// Don't bother starting a polling thread for an address that doesn't exist
// or if the address has already been polled with an earlier tuple
if (tuple.address == null || !uniqueAddresses.add(tuple.address)) {
tuple.complete = true;
tuple.returnedDetails = null;
return;
}
tuple.pollingThread = new Thread() {
@Override
public void run() {
boolean pollRes = fastPollIp(address);
ComputerDetails details = tryPollIp(tuple.existingDetails, tuple.address);
synchronized (info) {
info[0] = true; // Done
info[1] = pollRes; // Polling result
synchronized (tuple) {
tuple.complete = true; // Done
tuple.returnedDetails = details; // Polling result
info.notify();
tuple.notify();
}
}
};
t.setName("Fast Poll - "+address);
t.start();
tuple.pollingThread.setName("Parallel Poll - "+tuple.address+" - "+tuple.existingDetails.name);
tuple.pollingThread.start();
}
private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress, final String ipv6Address) throws InterruptedException {
final boolean[] remoteInfo = new boolean[2];
final boolean[] localInfo = new boolean[2];
final boolean[] manualInfo = new boolean[2];
final boolean[] ipv6Info = new boolean[2];
private ComputerDetails parallelPollPc(ComputerDetails details) throws InterruptedException {
ParallelPollTuple localInfo = new ParallelPollTuple(details.localAddress, details);
ParallelPollTuple manualInfo = new ParallelPollTuple(details.manualAddress, details);
ParallelPollTuple remoteInfo = new ParallelPollTuple(details.remoteAddress, details);
ParallelPollTuple ipv6Info = new ParallelPollTuple(details.ipv6Address, details);
startFastPollThread(localAddress, localInfo);
startFastPollThread(remoteAddress, remoteInfo);
startFastPollThread(manualAddress, manualInfo);
startFastPollThread(ipv6Address, ipv6Info);
// These must be started in order of precedence for the deduplication algorithm
// to result in the correct behavior.
HashSet<String> uniqueAddresses = new HashSet<>();
startParallelPollThread(localInfo, uniqueAddresses);
startParallelPollThread(manualInfo, uniqueAddresses);
startParallelPollThread(remoteInfo, uniqueAddresses);
startParallelPollThread(ipv6Info, uniqueAddresses);
// Check local first
synchronized (localInfo) {
while (!localInfo[0]) {
localInfo.wait(500);
try {
// Check local first
synchronized (localInfo) {
while (!localInfo.complete) {
localInfo.wait();
}
if (localInfo.returnedDetails != null) {
localInfo.returnedDetails.activeAddress = localInfo.address;
return localInfo.returnedDetails;
}
}
if (localInfo[1]) {
return localAddress;
}
}
// Now manual
synchronized (manualInfo) {
while (!manualInfo.complete) {
manualInfo.wait();
}
// Now manual
synchronized (manualInfo) {
while (!manualInfo[0]) {
manualInfo.wait(500);
if (manualInfo.returnedDetails != null) {
manualInfo.returnedDetails.activeAddress = manualInfo.address;
return manualInfo.returnedDetails;
}
}
if (manualInfo[1]) {
return manualAddress;
}
}
// Now remote IPv4
synchronized (remoteInfo) {
while (!remoteInfo.complete) {
remoteInfo.wait();
}
// Now remote IPv4
synchronized (remoteInfo) {
while (!remoteInfo[0]) {
remoteInfo.wait(500);
if (remoteInfo.returnedDetails != null) {
remoteInfo.returnedDetails.activeAddress = remoteInfo.address;
return remoteInfo.returnedDetails;
}
}
if (remoteInfo[1]) {
return remoteAddress;
}
}
// Now global IPv6
synchronized (ipv6Info) {
while (!ipv6Info.complete) {
ipv6Info.wait();
}
// Now global IPv6
synchronized (ipv6Info) {
while (!ipv6Info[0]) {
ipv6Info.wait(500);
}
if (ipv6Info[1]) {
return ipv6Address;
if (ipv6Info.returnedDetails != null) {
ipv6Info.returnedDetails.activeAddress = ipv6Info.address;
return ipv6Info.returnedDetails;
}
}
} finally {
// Stop any further polling if we've found a working address or we've been
// interrupted by an attempt to stop polling.
localInfo.interrupt();
manualInfo.interrupt();
remoteInfo.interrupt();
ipv6Info.interrupt();
}
return null;
}
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails;
// Do a TCP-level connection to the HTTP server to see if it's listening.
// Do not write this address to details.activeAddress because:
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress, details.ipv6Address);
LimeLog.info("Fast poll for "+details.name+" returned candidate address: "+candidateAddress);
// If no connection could be established to either IP address, there's nothing we can do
if (candidateAddress == null) {
return false;
}
// Try using the active address from fast-poll
polledDetails = tryPollIp(details, candidateAddress);
if (polledDetails == null) {
// If that failed, try all unique addresses except what we've
// already tried
HashSet<String> uniqueAddresses = new HashSet<>();
uniqueAddresses.add(details.localAddress);
uniqueAddresses.add(details.manualAddress);
uniqueAddresses.add(details.remoteAddress);
uniqueAddresses.add(details.ipv6Address);
for (String addr : uniqueAddresses) {
if (addr == null || addr.equals(candidateAddress)) {
continue;
}
polledDetails = tryPollIp(details, addr);
if (polledDetails != null) {
break;
}
}
}
// Poll all addresses in parallel to speed up the process
LimeLog.info("Starting parallel poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
ComputerDetails polledDetails = parallelPollPc(details);
LimeLog.info("Parallel poll for "+details.name+" returned address: "+details.activeAddress);
if (polledDetails != null) {
details.update(polledDetails);
@@ -128,6 +128,13 @@ public class CachedAppAssetLoader {
try {
Thread.sleep((int) (1000 + (Math.random() * 500)));
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
return null;
}
}
@@ -9,10 +9,7 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -53,6 +50,7 @@ import com.limelight.nvstream.ConnectionContext;
import com.limelight.nvstream.http.PairingManager.PairState;
import okhttp3.ConnectionPool;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@@ -71,8 +69,8 @@ public class NvHTTP {
// Print URL and content to logcat on debug builds
private static boolean verbose = BuildConfig.DEBUG;
public String baseUrlHttps;
public String baseUrlHttp;
private HttpUrl baseUrlHttps;
private HttpUrl baseUrlHttp;
private OkHttpClient httpClient;
private OkHttpClient httpClientWithReadTimeout;
@@ -190,22 +188,26 @@ public class NvHTTP {
initializeHttpState(cryptoProvider);
try {
// The URI constructor takes care of escaping IPv6 literals
this.baseUrlHttps = new URI("https", null, address, HTTPS_PORT, null, null, null).toString();
this.baseUrlHttp = new URI("http", null, address, HTTP_PORT, null, null, null).toString();
} catch (URISyntaxException e) {
// Encapsulate URISyntaxException into IOException for callers to handle more easily
this.baseUrlHttp = new HttpUrl.Builder()
.scheme("http")
.host(address)
.port(HTTP_PORT)
.build();
this.baseUrlHttps = new HttpUrl.Builder()
.scheme("https")
.host(address)
.port(HTTPS_PORT)
.build();
} catch (IllegalArgumentException e) {
// Encapsulate IllegalArgumentException into IOException for callers to handle more easily
throw new IOException(e);
}
this.pm = new PairingManager(this, cryptoProvider);
}
String buildUniqueIdUuidString() {
return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID();
}
static String getXmlString(Reader r, String tagname) throws XmlPullParserException, IOException {
static String getXmlString(Reader r, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
@@ -234,11 +236,19 @@ public class NvHTTP {
eventType = xpp.next();
}
if (throwIfMissing) {
// We throw an XmlPullParserException here for ease of handling in all the various callers.
// We could also throw an IOException, but some callers expect those in cases where the
// host may not be reachable. We want to distinguish unreachable hosts vs. hosts that
// are returning garbage XML to us, so we use XmlPullParserException instead.
throw new XmlPullParserException("Missing mandatory field in host response: "+tagname);
}
return null;
}
static String getXmlString(String str, String tagname) throws XmlPullParserException, IOException {
return getXmlString(new StringReader(str), tagname);
static String getXmlString(String str, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
return getXmlString(new StringReader(str), tagname, throwIfMissing);
}
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
@@ -272,7 +282,7 @@ public class NvHTTP {
if (serverCert != null) {
try {
try {
resp = openHttpConnectionToString(baseUrlHttps + "/serverinfo?"+buildUniqueIdUuidString(), true);
resp = openHttpConnectionToString(baseUrlHttps, "serverinfo", true);
} catch (SSLHandshakeException e) {
// Detect if we failed due to a server cert mismatch
if (e.getCause() instanceof CertificateException) {
@@ -292,7 +302,7 @@ public class NvHTTP {
catch (GfeHttpResponseException e) {
if (e.getErrorCode() == 401) {
// Cert validation error - fall back to HTTP
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
return openHttpConnectionToString(baseUrlHttp, "serverinfo", true);
}
// If it's not a cert validation error, throw it
@@ -303,7 +313,7 @@ public class NvHTTP {
}
else {
// No pinned cert, so use HTTP
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
return openHttpConnectionToString(baseUrlHttp , "serverinfo", true);
}
}
@@ -311,21 +321,21 @@ public class NvHTTP {
ComputerDetails details = new ComputerDetails();
String serverInfo = getServerInfo();
details.name = getXmlString(serverInfo, "hostname");
details.name = getXmlString(serverInfo, "hostname", false);
if (details.name == null || details.name.isEmpty()) {
details.name = "UNKNOWN";
}
details.uuid = getXmlString(serverInfo, "uniqueid");
details.macAddress = getXmlString(serverInfo, "mac");
details.localAddress = getXmlString(serverInfo, "LocalIP");
// UUID is mandatory to determine which machine is responding
details.uuid = getXmlString(serverInfo, "uniqueid", true);
// This may be null, but that's okay
details.remoteAddress = getXmlString(serverInfo, "ExternalIP");
details.macAddress = getXmlString(serverInfo, "mac", false);
details.localAddress = getXmlString(serverInfo, "LocalIP", false);
// This is missing on on recent GFE versions
details.remoteAddress = getXmlString(serverInfo, "ExternalIP", false);
// This has some extra logic to always report unpaired if the pinned cert isn't there
details.pairState = getPairState(serverInfo);
details.runningGameId = getCurrentGame(serverInfo);
// We could reach it so it's online
@@ -357,12 +367,26 @@ public class NvHTTP {
}
}
private HttpUrl getCompleteUrl(HttpUrl baseUrl, String path, String query) {
return baseUrl.newBuilder()
.addPathSegment(path)
.query(query)
.addQueryParameter("uniqueid", uniqueId)
.addQueryParameter("uuid", UUID.randomUUID().toString())
.build();
}
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
return openHttpConnection(baseUrl, path, null, enableReadTimeout);
}
// Read timeout should be enabled for any HTTP query that requires no outside action
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
// queries do not.
private ResponseBody openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
Request request = new Request.Builder().url(url).get().build();
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query);
Request request = new Request.Builder().url(completeUrl).get().build();
Response response;
if (enableReadTimeout) {
@@ -384,25 +408,29 @@ public class NvHTTP {
}
if (response.code() == 404) {
throw new FileNotFoundException(url);
throw new FileNotFoundException(completeUrl.toString());
}
else {
throw new GfeHttpResponseException(response.code(), response.message());
}
}
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws IOException {
private String openHttpConnectionToString(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
return openHttpConnectionToString(baseUrl, path, null, enableReadTimeout);
}
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
try {
if (verbose) {
LimeLog.info("Requesting URL: "+url);
LimeLog.info("Requesting URL: "+getCompleteUrl(baseUrl, path, query));
}
ResponseBody resp = openHttpConnection(url, enableReadTimeout);
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
String respString = resp.string();
resp.close();
if (verbose) {
LimeLog.info(url+" -> "+respString);
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
}
return respString;
@@ -416,7 +444,8 @@ public class NvHTTP {
}
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "appversion");
// appversion is present in all supported GFE versions
return getXmlString(serverInfo, "appversion", true);
}
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
@@ -424,15 +453,14 @@ public class NvHTTP {
}
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
return PairState.NOT_PAIRED;
}
return PairState.PAIRED;
// appversion is present in all supported GFE versions
return NvHTTP.getXmlString(serverInfo, "PairStatus", true).equals("1") ?
PairState.PAIRED : PairState.NOT_PAIRED;
}
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
// MaxLumaPixelsH264 wasn't present on old GFE versions
String str = getXmlString(serverInfo, "MaxLumaPixelsH264", false);
if (str != null) {
return Long.parseLong(str);
} else {
@@ -441,7 +469,8 @@ public class NvHTTP {
}
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
// MaxLumaPixelsHEVC wasn't present on old GFE versions
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC", false);
if (str != null) {
return Long.parseLong(str);
} else {
@@ -458,7 +487,8 @@ public class NvHTTP {
// Bit 10: HEVC Main10 4:4:4
// Bit 11: ???
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
// ServerCodecModeSupport wasn't present on old GFE versions
String str = getXmlString(serverInfo, "ServerCodecModeSupport", false);
if (str != null) {
return Long.parseLong(str);
} else {
@@ -467,16 +497,18 @@ public class NvHTTP {
}
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "gputype");
// ServerCodecModeSupport wasn't present on old GFE versions
return getXmlString(serverInfo, "gputype", false);
}
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "GfeVersion");
// ServerCodecModeSupport wasn't present on old GFE versions
return getXmlString(serverInfo, "GfeVersion", false);
}
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
// Only allow 4K on GFE 3.x
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion");
// Only allow 4K on GFE 3.x. GfeVersion wasn't present on very old versions of GFE.
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion", false);
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
return false;
}
@@ -488,10 +520,8 @@ public class NvHTTP {
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
// has the semantics that its name would indicate. To contain the effects of this change as much
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
String serverState = getXmlString(serverInfo, "state");
if (serverState != null && serverState.endsWith("_SERVER_BUSY")) {
String game = getXmlString(serverInfo, "currentgame");
return Integer.parseInt(game);
if (getXmlString(serverInfo, "state", true).endsWith("_SERVER_BUSY")) {
return Integer.parseInt(getXmlString(serverInfo, "currentgame", true));
}
else {
return 0;
@@ -588,8 +618,8 @@ public class NvHTTP {
return appList;
}
public String getAppListRaw() throws MalformedURLException, IOException {
return openHttpConnectionToString(baseUrlHttps + "/applist?"+buildUniqueIdUuidString(), true);
public String getAppListRaw() throws IOException {
return openHttpConnectionToString(baseUrlHttps, "applist", true);
}
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
@@ -598,20 +628,31 @@ public class NvHTTP {
return getAppListByReader(new StringReader(getAppListRaw()));
}
else {
ResponseBody resp = openHttpConnection(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
ResponseBody resp = openHttpConnection(baseUrlHttps, "applist", true);
LinkedList<NvApp> appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
resp.close();
return appList;
}
}
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
return openHttpConnectionToString(baseUrlHttp, "pair",
"devicename=roth&updateState=1&" + additionalArguments,
enableReadTimeout);
}
String executePairingChallenge() throws GfeHttpResponseException, IOException {
return openHttpConnectionToString(baseUrlHttps, "pair",
"devicename=roth&updateState=1&phrase=pairchallenge",
true);
}
public void unpair() throws IOException {
openHttpConnectionToString(baseUrlHttp + "/unpair?"+buildUniqueIdUuidString(), true);
openHttpConnectionToString(baseUrlHttp, "unpair", true);
}
public InputStream getBoxArt(NvApp app) throws IOException {
ResponseBody resp = openHttpConnection(baseUrlHttps + "/appasset?"+ buildUniqueIdUuidString() +
"&appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
ResponseBody resp = openHttpConnection(baseUrlHttps, "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
return resp.byteStream();
}
@@ -666,9 +707,8 @@ public class NvHTTP {
enableSops = false;
}
String xmlStr = openHttpConnectionToString(baseUrlHttps +
"/launch?" + buildUniqueIdUuidString() +
"&appid=" + appId +
String xmlStr = openHttpConnectionToString(baseUrlHttps, "launch",
"appid=" + appId +
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
@@ -679,9 +719,9 @@ public class NvHTTP {
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""),
false);
String gameSession = getXmlString(xmlStr, "gamesession");
if (gameSession != null && !gameSession.equals("0")) {
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0");
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
// sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
return true;
}
else {
@@ -690,14 +730,14 @@ public class NvHTTP {
}
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/resume?" + buildUniqueIdUuidString() +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
String xmlStr = openHttpConnectionToString(baseUrlHttps, "resume",
"rikey="+bytesToHex(context.riKey.getEncoded()) +
"&rikeyid="+context.riKeyId +
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
false);
String resume = getXmlString(xmlStr, "resume");
if (Integer.parseInt(resume) != 0) {
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0");
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
// sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
return true;
}
else {
@@ -706,9 +746,8 @@ public class NvHTTP {
}
public boolean quitApp() throws IOException, XmlPullParserException {
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
String cancel = getXmlString(xmlStr, "cancel");
if (Integer.parseInt(cancel) == 0) {
String xmlStr = openHttpConnectionToString(baseUrlHttps, "cancel", false);
if (getXmlString(xmlStr, "cancel", true).equals("0")) {
return false;
}
@@ -68,7 +68,8 @@ public class PairingManager {
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
{
String certText = NvHTTP.getXmlString(text, "plaincert");
// Plaincert may be null if another client is already trying to pair
String certText = NvHTTP.getXmlString(text, "plaincert", false);
if (certText != null) {
byte[] certBytes = hexToBytes(certText);
@@ -204,11 +205,10 @@ public class PairingManager {
// Send the salt and get the server cert. This doesn't have a read timeout
// because the user must enter the PIN before the server responds
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
String getCert = http.executePairingCommand("phrase=getservercert&salt="+
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
false);
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
if (!NvHTTP.getXmlString(getCert, "paired", true).equals("1")) {
return PairState.FAILED;
}
@@ -217,7 +217,7 @@ public class PairingManager {
if (serverCert == null) {
// Attempting to pair while another device is pairing will cause GFE
// to give an empty cert in the response.
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
http.unpair();
return PairState.ALREADY_IN_PROGRESS;
}
@@ -229,16 +229,14 @@ public class PairingManager {
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
// Send the encrypted challenge to the server
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
true);
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
String challengeResp = http.executePairingCommand("clientchallenge="+bytesToHex(encryptedChallenge), true);
if (!NvHTTP.getXmlString(challengeResp, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
// Decode the server's response and subsequent challenge
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse", true));
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
@@ -248,23 +246,21 @@ public class PairingManager {
byte[] clientSecret = generateRandomBytes(16);
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
true);
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
String secretResp = http.executePairingCommand("serverchallengeresp="+bytesToHex(challengeRespEncrypted), true);
if (!NvHTTP.getXmlString(secretResp, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
// Get the server's signed secret
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret", true));
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
// Ensure the authenticity of the data
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
// Cancel the pairing process
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
http.unpair();
// Looks like a MITM
return PairState.FAILED;
@@ -274,7 +270,7 @@ public class PairingManager {
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
// Cancel the pairing process
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
http.unpair();
// Probably got the wrong PIN
return PairState.PIN_WRONG;
@@ -282,19 +278,16 @@ public class PairingManager {
// Send the server our signed secret
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
true);
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
String clientSecretResp = http.executePairingCommand("clientpairingsecret="+bytesToHex(clientPairingSecret), true);
if (!NvHTTP.getXmlString(clientSecretResp, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
// Do the initial challenge (seems neccessary for us to show as paired)
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
// Do the initial challenge (seems necessary for us to show as paired)
String pairChallenge = http.executePairingChallenge();
if (!NvHTTP.getXmlString(pairChallenge, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
@@ -96,7 +96,7 @@ public class AddComputerManually extends Activity {
}
}
private void doAddPc(String host) {
private void doAddPc(String host) throws InterruptedException {
boolean wrongSiteLocal = false;
boolean success;
int portTestResult;
@@ -108,12 +108,18 @@ public class AddComputerManually extends Activity {
ComputerDetails details = new ComputerDetails();
details.manualAddress = host;
success = managerBinder.addComputerBlocking(details);
} catch (InterruptedException e) {
// Propagate the InterruptedException to the caller for proper handling
dialog.dismiss();
throw e;
} catch (IllegalArgumentException e) {
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
e.printStackTrace();
success = false;
}
// Keep the SpinnerDialog open while testing connectivity
if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
}
@@ -162,15 +168,12 @@ public class AddComputerManually extends Activity {
@Override
public void run() {
while (!isInterrupted()) {
String computer;
try {
computer = computersToAdd.take();
String computer = computersToAdd.take();
doAddPc(computer);
} catch (InterruptedException e) {
return;
}
doAddPc(computer);
}
}
};
@@ -184,7 +187,14 @@ public class AddComputerManually extends Activity {
try {
addThread.join();
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
addThread = null;
}
@@ -80,7 +80,8 @@ public class PreferenceConfiguration {
public static final int FRAME_PACING_MIN_LATENCY = 0;
public static final int FRAME_PACING_BALANCED = 1;
public static final int FRAME_PACING_MAX_SMOOTHNESS = 2;
public static final int FRAME_PACING_CAP_FPS = 2;
public static final int FRAME_PACING_MAX_SMOOTHNESS = 3;
public static final String RES_360P = "640x360";
public static final String RES_480P = "854x480";
@@ -288,6 +289,9 @@ public class PreferenceConfiguration {
else if (str.equals("balanced")) {
return FRAME_PACING_BALANCED;
}
else if (str.equals("cap-fps")) {
return FRAME_PACING_CAP_FPS;
}
else if (str.equals("smoothness")) {
return FRAME_PACING_MAX_SMOOTHNESS;
}
@@ -232,6 +232,13 @@ public class StreamSettings extends Activity {
category.removePreference(findPreference("checkbox_enable_pip"));
}
// Fire TV apps are not allowed to use WebViews or browsers, so hide the Help category
/*if (getActivity().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
PreferenceCategory category =
(PreferenceCategory) findPreference("category_help");
screen.removePreference(category);
}*/
// Remove the vibration options if the device can't vibrate
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
PreferenceCategory category =
@@ -0,0 +1,45 @@
package com.limelight.preferences;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.preference.Preference;
import android.util.AttributeSet;
import com.limelight.utils.HelpLauncher;
public class WebLauncherPreference extends Preference {
private String url;
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(attrs);
}
public WebLauncherPreference(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(attrs);
}
private void initialize(AttributeSet attrs) {
if (attrs == null) {
throw new IllegalStateException("WebLauncherPreference must have attributes!");
}
url = attrs.getAttributeValue(null, "url");
if (url == null) {
throw new IllegalStateException("WebLauncherPreference must have 'url' attribute!");
}
}
@Override
public void onClick() {
HelpLauncher.launchUrl(getContext(), url);
}
}
@@ -8,7 +8,7 @@ import android.net.Uri;
import com.limelight.HelpActivity;
public class HelpLauncher {
private static void launchUrl(Context context, String url) {
public static void launchUrl(Context context, String url) {
// Try to launch the default browser
try {
Intent i = new Intent(Intent.ACTION_VIEW);
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+24 -5
View File
@@ -97,7 +97,6 @@
<string name="perf_overlay_renderingfps">Wiedergabe-Bildwiederholungsrate: %1$.2f FPS</string>
<string name="perf_overlay_netdrops">Wegen Netzwerkübertragung ausgelassene Frames: %1$.2f%%</string>
<string name="perf_overlay_dectime">Durchschnittliche Dekodierungszeit: %1$.2f ms</string>
<!-- AppList activity -->
<string name="applist_connect_msg">Verbinde mit Host…</string>
<string name="applist_menu_resume">Sitzung wiederherstellen</string>
@@ -133,7 +132,7 @@
<string name="summary_fps_list">Erhöhen für einen gleichmäßigeren Video-Stream. Verringern um auf langsameren Geräten eine bessere Performance zu erzielen.</string>
<string name="title_seekbar_bitrate">Video Bitrate</string>
<string name="summary_seekbar_bitrate">Erhöhen für einen schärferen Video-Stream. Verringern um auf langsameren Geräten eine bessere Performance zu erzielen.</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="suffix_seekbar_bitrate_mbps">Mbit/s</string>
<string name="title_checkbox_stretch_video">Video auf Vollbildschirm strecken</string>
<string name="category_audio_settings">Audio Einstellungen</string>
<string name="title_audio_config_list">Surround Sound Konfiguration </string>
@@ -192,8 +191,8 @@
<string name="title_checkbox_disable_warnings">Warnhinweise deaktivieren</string>
<string name="summary_checkbox_disable_warnings">On-Screen Warnmeldungen während des Streaming deaktivieren</string>
<string name="summary_disable_frame_drop">Kann das Mikro-Ruckeln auf einigen Geräten reduzieren, allerdings erhöht dies gleichzeitig die Latenz</string>
<string name="title_video_format">H265 Einstellungen ändern</string>
<string name="summary_video_format">H265 verringert die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
<string name="title_video_format">HEVC Einstellungen ändern</string>
<string name="summary_video_format">HEVC verringert die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
<string name="title_enable_hdr">HDR aktivieren (experimentell)</string>
<string name="summary_enable_hdr">HDR-Streaming sofern dies von der Host-GPU unterstützt wird. HDR erfordert eine GPU der GTX 1000 Serie oder neuer.</string>
<string name="title_enable_perf_overlay">Performance Overlay aktivieren</string>
@@ -218,9 +217,29 @@
<string name="pcview_menu_header_unknown">Aktualisiere</string>
<string name="pcview_menu_header_offline">Offline</string>
<string name="pcview_menu_header_online">Online</string>
<!-- Array strings -->
<string name="videoformat_hevcauto">Verwende HEVC so fern stabile Unterstützung vorhanden ist</string>
<string name="videoformat_hevcalways">Immer HEVC verwenden (könnte Crashes verursachen)</string>
<string name="videoformat_hevcnever">Nie HEVC verwenden</string>
<string name="title_frame_pacing">Video Frame-Pacing</string>
<string name="summary_frame_pacing">Lege fest, wie die Videolatenz und die flüssige Wiedergabe ausgeglichen werden sollen</string>
<string name="resolution_prefix_native_fullscreen">Natives Vollbild</string>
<string name="pacing_latency">Bevorzuge niedrigste Latenz</string>
<string name="pacing_balanced">Ausgeglichen</string>
<string name="pacing_smoothness">Bevorzuge flüssige Bildwiedergabe (kann die Latenzzeit deutlich erhöhen)</string>
<string name="perf_overlay_streamdetails">Video Stream: %1$s %2$.2f FPS</string>
<string name="resolution_360p">360p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="audioconf_stereo">Stereo</string>
<string name="audioconf_51surround">5.1 Surround Sound</string>
<string name="audioconf_71surround">7.1 Surround Sound</string>
<string name="perf_overlay_netlatency">Durchschnittliche Netzwerklatenz: %1$d ms (Abweichung: %2$d ms)</string>
<string name="fps_30">30 FPS</string>
<string name="resolution_480p">480p</string>
<string name="fps_120">120 FPS</string>
<string name="resolution_720p">720p</string>
<string name="fps_60">60 FPS</string>
<string name="fps_90">90 FPS</string>
</resources>
+11 -4
View File
@@ -220,19 +220,26 @@
<string name="resolution_prefix_native_fullscreen">Plein-écran natif</string>
<string name="perf_overlay_netlatency">Latence réseau moyenne : %1$d ms (variance : %2$d ms)</string>
<string name="perf_overlay_streamdetails">Stream vidéo : %1$s %2$.2f FPS</string>
<!-- Array strings -->
<string name="fps_30">30 IPS</string>
<string name="fps_60">60 IPS</string>
<string name="fps_90">90 IPS</string>
<string name="fps_120">120 IPS</string>
<string name="audioconf_stereo">Stéréo</string>
<string name="audioconf_51surround">Son surround 5.1</string>
<string name="audioconf_71surround">Son surround 7.1</string>
<string name="videoformat_hevcauto">Utiliser HEVC uniquement s\'il est stable</string>
<string name="videoformat_hevcalways">Utilisez toujours HEVC (mais il peut planter)</string>
<string name="videoformat_hevcnever">N\'utilisez jamais HEVC</string>
<string name="title_frame_pacing">Frame-pacing vidéo</string>
<string name="summary_frame_pacing">Spécifiez comment équilibrer latence et fluidité de la vidéo</string>
<string name="pacing_latency">Préférer une latence plus faible</string>
<string name="pacing_balanced">Équilibré</string>
<string name="resolution_720p">720p</string>
<string name="pacing_smoothness">Préférer la qualité vidéo (risque d\'augmenter la latence)</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_4k">4K</string>
</resources>
+230 -1
View File
@@ -1,2 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="title_frame_pacing">Ritmo de quadros</string>
<string name="pacing_balanced">Balanceado</string>
<string name="pacing_latency">Preferir baixa latência</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="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="summary_unlock_fps">Transmitir a 90 ou 120 FPS pode reduzir a latência em dispositivos de última geração, mas pode causar atraso ou instabilidade em dispositivos que não suportam</string>
<string name="summary_checkbox_vibrate_osc">Vibra seu dispositivo para emular rumble nos controles de tela</string>
<string name="pcview_menu_header_offline">Indisponível</string>
<string name="pcview_menu_header_unknown">Atualizando</string>
<string name="pacing_smoothness">Preferir vídeo mais suave (pode aumentar significativamente a latência)</string>
<string name="summary_frame_pacing">Especifique o equilíbrio entre latência e suavidade do vídeo</string>
<string name="title_checkbox_disable_warnings">Desabilitar mensagens de alerta</string>
<string name="title_checkbox_enable_pip">Habilitar modo Picture-in-Picture</string>
<string name="summary_checkbox_small_icon_mode">Diminui o tamanho dos ícones permitindo que mais aplicativos sejam visíveis na tela</string>
<string name="dialog_text_reset_osc">Tem certeza de que deseja excluir o layout salvo dos controles de tela\?</string>
<string name="summary_checkbox_touchscreen_trackpad">Se habilitado, a tela sensível ao toque funciona como um trackpad. Se desativado, a tela controla diretamente o cursor do mouse.</string>
<string name="summary_checkbox_xb1_driver">Habilita um driver USB integrado para dispositivos sem suporte nativo aos controles Xbox</string>
<string name="title_checkbox_mouse_nav_buttons">Habilitar os botões de voltar e avançar do mouse</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="conn_error_msg">Falha ao iniciar</string>
<string name="ip_hint">Endereço de IP do PC GeForce</string>
<string name="searching_pc">Procurando por PCs rodando o GameStream...
\n
\nCertifique-se de que o GameStream esteja ativado nas configurações do SHIELD do GeForce Experience.</string>
<string name="nettest_text_success">Sua rede não parece estar bloqueando o Moonlight. Se você ainda tiver problemas para se conectar, verifique as configurações de firewall do seu PC.
\n
\nSe você estiver tentando transmitir pela Internet, instale o Moonlight Internet Hosting Tool em seu PC e execute o Internet Streaming Tester para verificar a conexão com a Internet do seu PC.</string>
<string name="check_ports_msg">Verifique o firewall e permita a(s) seguinte(s) para porta(s):</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_msg">Pode levar alguns segundos para o seu PC acordar. Se isso não acontecer, verifique se ele está configurado corretamente para Wake-On-LAN.</string>
<string name="error_404">GFE retornou um erro HTTP 404. Verifique se o seu PC está executando uma GPU compatível. O uso do software de área de trabalho remota também pode causar esse erro. Tente reiniciar sua máquina ou reinstalar o GFE.</string>
<string name="message_decoding_error">Moonlight crashou devido a uma incompatibilidade com o decodificador de vídeo deste dispositivo. Certifique-se de que o GeForce Experience esteja atualizado para a versão mais recente em seu PC. Tente ajustar as configurações de transmissão se as falhas continuarem.</string>
<string name="message_decoding_reset">O decodificador de vídeo do seu dispositivo continua crashando nas configurações de transmissão selecionadas. 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 dispositivo. Verifique suas configurações de Knox ou MDM.</string>
<string name="unable_to_pin_shortcut">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. Seu dispositivo pode não suportar a resolução ou taxa de quadros selecionada.</string>
<string name="no_video_received_error">Nenhum vídeo recebido do host.</string>
<string name="no_frame_received_error">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="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 em seu PC. Você 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_establishing_msg">Iniciando a conexão</string>
<string name="conn_establishing_title">Estabelecendo Conexão</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_720p">720p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 FPS</string>
<string name="fps_60">60 FPS</string>
<string name="videoformat_hevcalways">Sempre usar HEVC (talvez crashe)</string>
<string name="videoformat_hevcauto">Usar HEVC apenas se for estável</string>
<string name="fps_120">120 FPS</string>
<string name="audioconf_stereo">Stereo</string>
<string name="audioconf_51surround">5.1 Surround</string>
<string name="audioconf_71surround">7.1 Surround</string>
<string name="fps_90">90 FPS</string>
<string name="videoformat_hevcnever">Nunca usar HEVC</string>
<string name="scut_deleted_pc">PC deletado</string>
<string name="scut_not_paired">PC não pareado</string>
<string name="help_loading_title">Ver Ajuda</string>
<string name="help_loading_msg">Carregando página de ajuda…</string>
<string name="scut_pc_not_found">PC não encontrado</string>
<string name="scut_invalid_uuid">O PC informado não é válido</string>
<string name="scut_invalid_app_id">O aplicativo informado não é válido</string>
<string name="pcview_menu_header_online">Disponível</string>
<string name="pcview_menu_app_list">Ver Todos os Jogos</string>
<string name="pcview_menu_unpair_pc">Desparear</string>
<string name="pcview_menu_delete_pc">Deletar PC</string>
<string name="pcview_menu_test_network">Testar Conexão com a Internet</string>
<string name="pcview_menu_details">Ver Detalhes</string>
<string name="nettest_title_waiting">Testando a Conexão com a Internet</string>
<string name="nettest_title_done">Teste de Internet Completo</string>
<string name="pcview_menu_pair_pc">Parear com o PC</string>
<string name="pcview_menu_send_wol">Enviar Wake-On-LAN</string>
<string name="nettest_text_waiting">O Moonlight está testando a sua internet para determinar se o NVIDIA GameStream está bloqueado.
\n
\nIsso pode levar alguns segundos…</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 sua conexão com a Internet ou tente novamente mais tarde.</string>
<string name="nettest_text_failure">A conexão de rede atual do seu dispositivo parece estar bloqueando 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 dispositivo está bloqueando o Moonlight. A transmissão pela Internet pode não funcionar enquanto estiver conectado a esta rede.</string>
<string name="pairing">Pareando…</string>
<string name="pair_pc_offline">Esse computador está indisponível</string>
<string name="pair_pc_ingame">Esse computador está atualmente em um jogo. Você deve fechar o jogo antes de parear.</string>
<string name="pair_pairing_title">Pareando</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_fail">Falha no pareamento</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_waking_pc">Acordando o PC…</string>
<string name="wol_fail">Falha ao enviar pacotes Wake-On-LAN</string>
<string name="unpairing">Despareando…</string>
<string name="unpair_success">Despareado com sucesso</string>
<string name="unpair_fail">Falha ao desparear</string>
<string name="unpair_error">Dispositivo 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 o aplicativo.</string>
<string name="error_unknown_host">Falha ao encontrar o host</string>
<string name="conn_terminated_msg">A conexão foi encerrada</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 excluir este PC\?</string>
<string name="slow_connection_msg">Conexão lenta com o PC
\nReduza sua taxa de bits</string>
<string name="poor_connection_msg">Conexão ruim com o PC</string>
<string name="conn_terminated_title">Conexão Encerrada</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="conn_metered">Aviso: 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_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="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_renderingfps">Taxa de quadros renderizando: %1$.2f FPS</string>
<string name="perf_overlay_streamdetails">Transmissão: %1$s %2$.2f FPS</string>
<string name="perf_overlay_netlatency">Latência média da rede: %1$d ms (variação: %2$d ms)</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">Retornar ao Jogo</string>
<string name="applist_menu_quit">Sair do Jogo</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_scut">Criar Atalho</string>
<string name="applist_menu_tv_channel">Adicionar ao Canal</string>
<string name="applist_menu_hide_app">Esconder Jogo</string>
<string name="applist_refresh_title">Lista de Aplicativos</string>
<string name="perf_overlay_netdrops">Quadros dropados pela rede: %1$.2f%%</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="applist_refresh_msg">Recarregando aplicativos…</string>
<string name="applist_refresh_error_title">Erro</string>
<string name="applist_refresh_error_msg">Falha ao obter a lista de aplicativos</string>
<string name="applist_quit_confirmation">Tem certeza de que deseja sair do aplicativo 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">Você deve inserir um endereço de IP</string>
<string name="category_input_settings">Configurações de Controles</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 dispositivos de baixo custo e redes mais lentas.</string>
<string name="title_native_res_dialog">Alerta de Resolução Nativa</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 tela do host. Você precisará configurá-lo manualmente durante o jogo.
\n
\nSe você optar por criar uma resolução personalizada no Painel de controle da NVIDIA para corresponder à resolução do seu dispositivo, 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 em seu PC.
\n
\nPor fim, seu dispositivo ou PC host pode não suportar transmissões na resolução nativa. Se não funcionar no seu dispositivo, você está sem sorte, infelizmente.</string>
<string name="title_fps_list">Taxa de quadros por segundo</string>
<string name="summary_fps_list">Aumente para uma transmissão de vídeo mais suave. Diminua para melhor desempenho em dispositivos de baixo custo.</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 para tela cheia</string>
<string name="resolution_prefix_native">Nativa</string>
<string name="resolution_prefix_native_fullscreen">Nativa Tela Cheia</string>
<string name="category_audio_settings">Configurações de Áudio</string>
<string name="title_audio_config_list">Configurações de som Surround</string>
<string name="summary_audio_config_list">Habilita o som Surround 5.1 ou 7.1 para sistemas de home theater</string>
<string name="addpc_wrong_sitelocal">Esse endereço não parece certo. Você deve usar o endereço de IP público do seu roteador para transmitir pela Internet.</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="title_checkbox_show_onscreen_controls">Mostrar controles na tela</string>
<string name="summary_checkbox_show_onscreen_controls">Habilita o controle virtual na tela de toque</string>
<string name="title_checkbox_vibrate_osc">Habilitar vibração</string>
<string name="title_only_l3r3">Mostrar somente L3 e R3</string>
<string name="summary_only_l3r3">Esconde todos os botões virtuais exceto o L3 e R3</string>
<string name="title_checkbox_touchscreen_trackpad">Usar a tela como um trackpad</string>
<string name="title_checkbox_multi_controller">Detecção automática de gamepad</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="summary_checkbox_vibrate_fallback">Vibra seu dispositivo para emular rumble se seu gamepad não suportar</string>
<string name="title_seekbar_deadzone">Ajustar zona morta do analógico</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_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 mouse via gamepad</string>
<string name="summary_checkbox_mouse_emulation">Segure o botão Start para mudar o gamepad para o modo de mouse</string>
<string name="summary_checkbox_mouse_nav_buttons">Ativar esta opção pode bugar o clique com o botão direito em alguns dispositivos com bugs</string>
<string name="summary_checkbox_flip_face_buttons">Inverte os botões A/B e X/Y dos gamepads e controles de tela</string>
<string name="category_on_screen_controls_settings">Configurações de Controles de Tela</string>
<string name="title_checkbox_flip_face_buttons">Inverter botões</string>
<string name="title_unlock_fps">Liberar todas as taxas de quadros possíveis</string>
<string name="category_advanced_settings">Configurações Avançadas</string>
<string name="title_checkbox_host_audio">Reproduzir áudio no PC</string>
<string name="summary_checkbox_host_audio">Reproduz o áudio do computador e deste dispositivo</string>
<string name="title_checkbox_enable_sops">Otimizar configurações de jogo</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_language_list">Idioma</string>
<string name="summary_language_list">Idioma para ser usado no Moonlight</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="category_ui_settings">Configurações de Interface</string>
<string name="suffix_osc_opacity">%</string>
<string name="category_host_settings">Configurações de Host</string>
<string name="dialog_title_osc_opacity">Mudar opacidade</string>
<string name="title_checkbox_small_icon_mode">Mostrar ícones menores</string>
<string name="summary_reset_osc">Redefine todos os controles na tela para seu tamanho e posição padrão</string>
<string name="dialog_title_reset_osc">Resetar Layout</string>
<string name="toast_reset_osc_success">Controles de tela redefinidos para o padrão</string>
<string name="title_osc_opacity">Mudar a opacidade dos controles de tela</string>
<string name="summary_osc_opacity">Deixe os controles de tela mais/menos transparentes</string>
<string name="title_reset_osc">Limpar o layout salvo dos controles de tela</string>
<string name="title_video_format">Mudar configurações do HEVC</string>
<string name="summary_video_format">HEVC reduz os requisitos de largura de banda de vídeo, mas requer um dispositivo mais atual</string>
<string name="summary_checkbox_disable_warnings">Desativa as mensagens de aviso de conexão na tela durante a transmissão</string>
<string name="title_disable_frame_drop">Nunca dropar quadros</string>
<string name="summary_disable_frame_drop">Talvez reduza o micro-stuttering em alguns dispositivos, mas pode aumentar a latência</string>
<string name="title_enable_hdr">Habilitar HDR (Experimental)</string>
<string name="title_enable_post_stream_toast">Mostrar aviso de latência após terminar a transmissão</string>
<string name="summary_enable_perf_overlay">Exibe informações de performance em tempo real durante a transmissão</string>
<string name="title_enable_perf_overlay">Mostrar status de performance durante a transmissão</string>
</resources>
+4
View File
@@ -13,4 +13,8 @@
<string name="pcview_menu_header_offline">Offline</string>
<string name="pcview_menu_app_list">Veja todos aplicativos</string>
<string name="pcview_menu_pair_pc">Sincronize com o PC</string>
<string name="pcview_menu_details">Ver Detalhes</string>
<string name="pair_pairing_title">Pareando</string>
<string name="pair_fail">Falha no pareamento</string>
<string name="nettest_title_waiting">Testar Conexão com a Internet</string>
</resources>
+18 -1
View File
@@ -217,9 +217,26 @@
<string name="pcview_menu_header_unknown">Оновлення</string>
<string name="pcview_menu_header_offline">Поза мережею</string>
<string name="pcview_menu_header_online">В мережі</string>
<!-- Array strings -->
<string name="videoformat_hevcauto">Використовувати HEVC тільки якщо безпечно</string>
<string name="videoformat_hevcalways">Завжди використовувати HEVC якщо доступно</string>
<string name="videoformat_hevcnever">Ніколи не використовувати HEVC</string>
<string name="title_frame_pacing">Швидкість кадрів відео</string>
<string name="summary_frame_pacing">Укажіть баланс відео затримки та плавності</string>
<string name="pacing_latency">Перевага найменшій затримці</string>
<string name="pacing_smoothness">Перевага плавному відео (може значно збільшити затримку)</string>
<string name="pacing_balanced">Збалансовано</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_720p">720p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 кадрів/сек</string>
<string name="fps_60">60 кадрів/сек</string>
<string name="fps_90">90 кадрів/сек</string>
<string name="fps_120">120 кадрів/сек</string>
<string name="audioconf_stereo">Стерео</string>
<string name="audioconf_51surround">5.1 Об\'ємний Звук</string>
<string name="audioconf_71surround">7.1 Об\'ємний Звук</string>
</resources>
+11 -2
View File
@@ -218,13 +218,22 @@
<string name="perf_overlay_netlatency">平均网络延迟: %1$d ms (抖动: %2$d ms)</string>
<string name="perf_overlay_streamdetails">视频流: %1$s %2$.2f FPS</string>
<string name="resolution_prefix_native_fullscreen">本地全屏</string>
<!-- Array strings -->
<string name="audioconf_stereo">立体声</string>
<string name="audioconf_51surround">5.1环绕声</string>
<string name="audioconf_71surround">7.1环绕声</string>
<string name="videoformat_hevcauto">如果稳定才使用HEVC</string>
<string name="videoformat_hevcalways">强制使用HEVC(不稳定)</string>
<string name="videoformat_hevcnever">不使用HEVC</string>
<string name="title_frame_pacing">视频帧速调节</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 FPS</string>
<string name="fps_60">60 FPS</string>
<string name="fps_90">90 FPS</string>
<string name="fps_120">120 FPS</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_720p">720p</string>
</resources>
+62 -49
View File
@@ -8,7 +8,7 @@
<string name="scut_invalid_app_id">提供的應用程式無效</string>
<!-- Help strings -->
<string name="help_loading_title">說明檢視器</string>
<string name="help_loading_msg">正在載入說明頁面…</string>
<string name="help_loading_msg">正在載入說明頁面…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">檢視遊戲清單</string>
<string name="pcview_menu_pair_pc"> 和電腦配對 </string>
@@ -17,30 +17,30 @@
<string name="pcview_menu_delete_pc"> 刪除電腦 </string>
<string name="pcview_menu_details">檢視詳細資料</string>
<!-- Pair messages -->
<string name="pairing"> 配對中…… </string>
<string name="pair_pc_offline"> 電腦離線</string>
<string name="pair_pc_ingame">電腦正在遊戲中,在配對之前你必須先退出遊戲。</string>
<string name="pair_pairing_title"> 配對中 </string>
<string name="pairing">正在配對…</string>
<string name="pair_pc_offline">電腦離線</string>
<string name="pair_pc_ingame">電腦目前正在遊戲中,在配對之前你必須先退出遊戲。</string>
<string name="pair_pairing_title">正在配對</string>
<string name="pair_pairing_msg">請在目標電腦上輸入以下 PIN 碼:</string>
<string name="pair_incorrect_pin">PIN 碼錯誤</string>
<string name="pair_fail"> 配對失敗 </string>
<string name="pair_already_in_progress"> 配對,請稍候 </string>
<string name="pair_already_in_progress">正在配對,請稍候</string>
<!-- WOL messages -->
<string name="wol_pc_online"> 電腦線上中 </string>
<string name="wol_pc_online">電腦已上</string>
<string name="wol_no_mac">無法喚醒電腦因為 GFE 沒有傳送 MAC 位址</string>
<string name="wol_waking_pc"> 喚醒電腦中…… </string>
<string name="wol_waking_pc">正在喚醒電腦</string>
<string name="wol_waking_msg">喚醒電腦需要一些時間。如果電腦沒有喚醒,請確保 Wake-On-LAN 設定無誤。</string>
<string name="wol_fail">無法傳送 Wake-On-LAN 資料包</string>
<!-- Unpair messages -->
<string name="unpairing"> 取消配對中…… </string>
<string name="unpairing">正在取消配對</string>
<string name="unpair_success"> 成功取消配對 </string>
<string name="unpair_fail"> 無法配對 </string>
<string name="unpair_error">裝置沒有配對過</string>
<!-- Errors -->
<string name="error_pc_offline"> 電腦離線</string>
<string name="error_pc_offline">電腦離線</string>
<string name="error_manager_not_running">ComputerManager 服務未執行。請稍等幾秒或重啟應用程式。</string>
<string name="error_unknown_host"> 無法解析主機位址 </string>
<string name="error_404">GFE 返回了 HTTP 404 錯誤。確保你的電腦顯卡支援串流。使用遠端桌面軟體同樣會引起此錯誤,請嘗試重啟電腦或重新安裝 GFE。</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="title_decoding_reset">重設視訊設定</string>
@@ -48,20 +48,20 @@
<string name="error_usb_prohibited">裝置管理員已禁止 USB 訪問。請檢查您的 Knox 或 MDM 設定。</string>
<string name="unable_to_pin_shortcut">您目前的啟動器不允許創建釘選的捷徑。</string>
<!-- Start application messages -->
<string name="conn_establishing_title">建立連線</string>
<string name="conn_establishing_msg">啟動連線</string>
<string name="conn_establishing_title">正在建立連線</string>
<string name="conn_establishing_msg">正在啟動連線</string>
<string name="conn_metered">警告:你正在使用行動網路連線!</string>
<string name="conn_client_latency">平均每個影格解碼延時:</string>
<string name="conn_client_latency_hw">硬體解碼器延時:</string>
<string name="conn_hardware_latency">硬體解碼器平均延時:</string>
<string name="conn_starting">啟動</string>
<string name="conn_starting">正在啟動</string>
<string name="conn_error_title">連線錯誤</string>
<string name="conn_error_msg"> 啟動失敗 </string>
<string name="conn_terminated_title">連線</string>
<string name="conn_terminated_msg">連線已被中</string>
<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="searching_pc">正在搜尋執行 GAMESTREAM 的電腦…
\n
\n請確保 GFE SHIELD 設定裡的 GAMESTREAM 已啟用。</string>
<string name="yes"> 確定 </string>
@@ -79,27 +79,27 @@
<string name="perf_overlay_netdrops">網路丟失影格:%1$.2f%%</string>
<string name="perf_overlay_dectime">平均解碼時間:%1$.2f ms</string>
<!-- AppList activity -->
<string name="applist_connect_msg">正在連線電腦…</string>
<string name="applist_menu_resume">恢復對話</string>
<string name="applist_menu_quit">退出對話</string>
<string name="applist_menu_quit_and_start">退出目前遊戲並開始這個遊戲</string>
<string name="applist_connect_msg">正在連線電腦…</string>
<string name="applist_menu_resume">恢復工作階段</string>
<string name="applist_menu_quit">結束工作階段</string>
<string name="applist_menu_quit_and_start">結束目前遊戲並開始這個遊戲</string>
<string name="applist_menu_cancel"> 取消 </string>
<string name="applist_menu_details">檢視詳細資料</string>
<string name="applist_menu_scut">創建捷徑</string>
<string name="applist_menu_tv_channel">新增至頻道</string>
<string name="applist_refresh_title">遊戲清單</string>
<string name="applist_refresh_msg">重新整理中…</string>
<string name="applist_refresh_msg">正在重新整理…</string>
<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_fail">退出失敗</string>
<string name="applist_quit_confirmation">您確定要退出執行中的遊戲?所有未儲存的資料將丟失。</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_details_id">App ID</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">手動新增電腦</string>
<string name="msg_add_pc">正在連線電腦…</string>
<string name="addpc_fail">無法連線至指定電腦。請確保所需埠沒有被防火牆阻止。</string>
<string name="msg_add_pc">正在連線電腦…</string>
<string name="addpc_fail">無法連線至指定電腦。請確保所需通訊埠沒有被防火牆阻止。</string>
<string name="addpc_success">成功新增電腦</string>
<string name="addpc_unknown_host">無法解析電腦的 IP 位址,請確保 IP 位址輸入無誤。</string>
<string name="addpc_enter_ip">請輸入一個 IP 位址</string>
@@ -126,14 +126,14 @@
<string name="title_checkbox_touchscreen_trackpad">將觸控式螢幕作為觸控板使用</string>
<string name="summary_checkbox_touchscreen_trackpad">如果啟用,則將觸控式螢幕作為觸控板使用。 如果停用,則觸控式螢幕直接控制滑鼠游標。</string>
<string name="title_checkbox_multi_controller">自動偵測手把</string>
<string name="summary_checkbox_multi_controller">禁用此項所有手把將視為一個手把</string>
<string name="summary_checkbox_multi_controller">取消此項所有手把將視為一個手把</string>
<string name="title_checkbox_vibrate_fallback">用震動仿真遊戲低頻音</string>
<string name="summary_checkbox_vibrate_fallback">如果你的手把不支援震動,則震動裝置以仿真遊戲低頻音</string>
<string name="title_seekbar_deadzone">調整類比搖杆死區</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One USB 手把驅動程式</string>
<string name="summary_checkbox_xb1_driver">為缺少原生 Xbox 控制器支援的裝置啟用內建 USB 驅動程式</string>
<string name="title_checkbox_usb_bind_all">原生 Xbox 手把支援</string>
<string name="title_checkbox_usb_bind_all">原生 Xbox 手把支援</string>
<string name="summary_checkbox_usb_bind_all">強制 Moonlight 的 USB 驅動程式接管所有受支援的原生 Xbox 控制器</string>
<string name="title_checkbox_mouse_emulation">透過手把仿真滑鼠</string>
<string name="summary_checkbox_mouse_emulation">長按開始鍵將手把切換為滑鼠模式</string>
@@ -148,12 +148,12 @@
<string name="summary_checkbox_vibrate_osc">使用螢幕控制按鈕時震動裝置以仿真遊戲低頻音</string>
<string name="title_only_l3r3">只顯示 L3 和 R3</string>
<string name="summary_only_l3r3">隱藏除 L3 和 R3 外的所有虛擬按鈕</string>
<string name="title_reset_osc">重設已儲存的螢幕控制按鈕佈局</string>
<string name="title_reset_osc">重設已儲存的螢幕控制按鈕版面配置</string>
<string name="summary_reset_osc">重設所有螢幕控制按鈕為預設大小和位置</string>
<string name="dialog_title_reset_osc">重設按鈕佈局</string>
<string name="dialog_text_reset_osc">你確定要刪除已儲存的螢幕按鈕佈局嗎?</string>
<string name="dialog_title_reset_osc">重設按鈕版面配置</string>
<string name="dialog_text_reset_osc">你確定要刪除已儲存的螢幕按鈕版面配置嗎?</string>
<string name="toast_reset_osc_success">螢幕按鈕佈局已經重設</string>
<string name="category_ui_settings">介面設定</string>
<string name="category_ui_settings">使用者介面設定</string>
<string name="title_language_list"> 語言 </string>
<string name="summary_language_list">選擇 Moonlight 顯示的語言</string>
<string name="title_checkbox_small_icon_mode"> 使用小圖示 </string>
@@ -166,30 +166,30 @@
<string name="category_advanced_settings">進階設定</string>
<string name="title_disable_frame_drop">永不丟失影格</string>
<string name="summary_disable_frame_drop">可能會減少在一些裝置上的卡頓,但會增加延時</string>
<string name="title_video_format"> HEVC 設定</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 需要 GTX 1000 系列或更高規格顯卡。</string>
<string name="title_enable_perf_overlay">顯示效能資訊</string>
<string name="summary_enable_perf_overlay">在串流中顯示即時效能資訊</string>
<string name="title_enable_post_stream_toast">串流完畢顯示延時資訊</string>
<string name="summary_enable_post_stream_toast">串流結束後顯示延時資訊</string>
<string name="title_osc_opacity">螢幕按鈕透明度</string>
<string name="dialog_title_osc_opacity">透明度</string>
<string name="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="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="resolution_prefix_native">本機</string>
<string name="text_native_res_dialog">本機解析度模式不受 GFE 的官方支援,因此不會自動設定主機的顯示解析度。您需要在遊戲中手動進行設定。
\n
\n如果您選擇在 NVIDIA 控制台中創建自訂解析度以匹配裝置解析度,請確保您已閱讀並理解 NVIDIA 關於可能導致顯示器損毀和電腦不穩定以及其他潛在問題的警告。
\n如果您選擇在 NVIDIA 控制面板中建立自訂解析度以符合裝置解析度,請確保您已閱讀並理解 NVIDIA 關於可能導致監視器損毀和電腦不穩定以及其他潛在問題的警告。
\n
\n對於您在您的電腦上建自訂解析度而導致的任何問題,我們概不負責。
\n對於您在您的電腦上建自訂解析度而導致的任何問題,我們概不負責。
\n
\n最後,您的裝置或主機電腦可能不支援以本機解析度串流。如果此模式在您的裝置上無法正常執行,只能說您運氣欠佳了。</string>
<string name="title_native_res_dialog">本機解析度警告</string>
<string name="applist_menu_hide_app">隱藏遊戲</string>
<string name="check_ports_msg">檢查您的防火牆和埠轉規則中的埠:</string>
<string name="check_ports_msg">檢查您的防火牆和通訊埠轉規則中的通訊埠:</string>
<string name="early_termination_error">開始串流時您的主機電腦出了點問題。
\n
\n請確保沒有在主機電腦上開啟任何受 DRM 保護的內容。您也可以嘗試重新開機主機電腦。
@@ -198,29 +198,29 @@
<string name="no_frame_received_error">您的網路連線品質不佳。降低視訊位元速率設定或更換更快的連線。</string>
<string name="no_video_received_error">沒有接收到來自主機的視訊。</string>
<string name="video_decoder_init_failed">視訊解碼器初始化失敗。您的裝置可能不支援選定的解析度或影格速率。</string>
<string name="nettest_text_blocked">您裝置目前的網路連線攔截了 Moonlight。連線到該網路時可能無法透過網際網路串流。</string>
<string name="nettest_text_failure">您裝置目前的網路連線似乎攔截了 Moonlight。連線到該網路時可能無法透過網際網路串流。
<string name="nettest_text_blocked">您裝置目前的網路連線封鎖了 Moonlight。連線到該網路時可能無法透過網際網路串流。</string>
<string name="nettest_text_failure">您裝置目前的網路連線似乎封鎖了 Moonlight。連線到該網路時可能無法透過網際網路串流。
\n
\n以下網路埠被攔截
\n以下網路通訊埠被封鎖
\n</string>
<string name="nettest_text_inconclusive">由於沒有 Moonlight 連線測試伺服器可供訪問,因此無法執行網路測試。請檢查您的 Internet 連線或稍後再試。</string>
<string name="nettest_text_success">您的網路似乎沒有攔截 Moonlight。如果仍然無法連線,請檢查您電腦的防火牆設定。
<string name="nettest_text_success">您的網路似乎沒有封鎖 Moonlight。如果仍然無法連線,請檢查您電腦的防火牆設定。
\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 正在測您的網路連線以確認 NVIDIA 遊戲串流服務是否被封鎖
\n
\n可能需要等待一些時間…</string>
<string name="nettest_title_waiting">正在測試網路連線</string>
<string name="pcview_menu_test_network">測試網路連線</string>
<string name="pcview_menu_header_unknown">重新整理</string>
<string name="pcview_menu_header_unknown">正在重新整理</string>
<string name="pcview_menu_header_offline">離線</string>
<string name="pcview_menu_header_online">線上</string>
<string name="perf_overlay_netlatency">平均網路延時:%1$d ms (抖動:%2$d ms)</string>
<string name="perf_overlay_streamdetails">視訊串流:%1$s %2$.2f FPS</string>
<string name="resolution_prefix_native_fullscreen">本機全螢幕</string>
<!-- Array strings -->
<string name="audioconf_stereo">身歷</string>
<string name="audioconf_stereo">立體</string>
<string name="audioconf_51surround">5.1 環場音效</string>
<string name="audioconf_71surround">7.1 環場音效</string>
<string name="videoformat_hevcauto">僅在穩定時使用 HEVC</string>
@@ -236,4 +236,17 @@
<string name="resolution_480p">480P</string>
<string name="resolution_1080p">1080P</string>
<string name="resolution_1440p">1440P</string>
<string name="title_frame_pacing">視訊影格調步</string>
<string name="summary_frame_pacing">指定如何平衡視訊延遲和平滑度</string>
<string name="pacing_balanced">平衡</string>
<string name="pacing_smoothness">偏好平滑視訊 (可能顯著提高延遲)</string>
<string name="pacing_latency">偏好低延遲</string>
<string name="pacing_balanced_alt">FPS 上限平衡</string>
<string name="category_help">說明</string>
<string name="title_troubleshooting">疑難排解指南</string>
<string name="summary_setup_guide">檢視關於如何設定你的遊戲電腦以進行串流的指示</string>
<string name="title_privacy_policy">隱私權政策</string>
<string name="title_setup_guide">設定指南</string>
<string name="summary_troubleshooting">檢視診斷和修正常見串流問題的提示</string>
<string name="summary_privacy_policy">檢視 Moonlight 的隱私權政策</string>
</resources>
+2
View File
@@ -98,11 +98,13 @@
<string-array name="video_frame_pacing_names">
<item>@string/pacing_latency</item>
<item>@string/pacing_balanced</item>
<item>@string/pacing_balanced_alt</item>
<item>@string/pacing_smoothness</item>
</string-array>
<string-array name="video_frame_pacing_values" translatable="false">
<item>latency</item>
<item>balanced</item>
<item>cap-fps</item>
<item>smoothness</item>
</string-array>
</resources>
+9
View File
@@ -227,6 +227,14 @@
<string name="title_enable_post_stream_toast">Show latency message after streaming</string>
<string name="summary_enable_post_stream_toast">Display a latency information message after the stream ends</string>
<string name="category_help">Help</string>
<string name="title_setup_guide">Setup guide</string>
<string name="summary_setup_guide">View instructions on how to set up your gaming PC for streaming</string>
<string name="title_troubleshooting">Troubleshooting guide</string>
<string name="summary_troubleshooting">View tips for diagnosing and fixing common streaming issues</string>
<string name="title_privacy_policy">Privacy policy</string>
<string name="summary_privacy_policy">View Moonlight\'s privacy policy</string>
<!-- Array strings -->
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
@@ -252,5 +260,6 @@
<string name="summary_frame_pacing">Specify how to balance video latency and smoothness</string>
<string name="pacing_latency">Prefer lowest latency</string>
<string name="pacing_balanced">Balanced</string>
<string name="pacing_balanced_alt">Balanced with FPS limit</string>
<string name="pacing_smoothness">Prefer smoothest video (may significantly increase latency)</string>
</resources>
+15
View File
@@ -206,4 +206,19 @@
android:summary="@string/summary_enable_post_stream_toast"
android:defaultValue="false"/>
</PreferenceCategory>
<!--PreferenceCategory android:title="@string/category_help"
android:key="category_help">
<com.limelight.preferences.WebLauncherPreference
android:title="@string/title_setup_guide"
android:summary="@string/summary_setup_guide"
url="https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide"/>
<com.limelight.preferences.WebLauncherPreference
android:title="@string/title_troubleshooting"
android:summary="@string/summary_troubleshooting"
url="https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"/>
<com.limelight.preferences.WebLauncherPreference
android:title="@string/title_privacy_policy"
android:summary="@string/summary_privacy_policy"
url="https://moonlight-stream.org/privacy.html"/>
</PreferenceCategory-->
</PreferenceScreen>
+2 -2
View File
@@ -2,13 +2,13 @@ version: 0.0.0.{build}
clone_depth: 1
image: Visual Studio 2019
image: Visual Studio 2022
before_build:
- 'git submodule update --init --recursive'
- 'mklink /D C:\android-sdk "C:\Program Files (x86)\Android\android-sdk"'
- 'set ANDROID_HOME=C:\android-sdk'
- 'set JAVA_HOME=C:\Program Files\Java\jdk11'
- 'set JAVA_HOME=C:\Program Files\Java\jdk17'
build_script:
- gradlew.bat build connectedCheck
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.android.tools.build:gradle:7.2.0'
}
}
@@ -0,0 +1,6 @@
- Improved frame pacing when streaming 60 FPS on 120 Hz devices
- Reduced power usage when streaming below maximum display refresh rate
- Reintroduced previous frame pacing behavior as "Balanced with FPS limit" option
- Rewrote PC address detection logic to better handle some network configurations
- Fixed simultaneous mouse and on-screen controller input
- Updated community contributed translations from Weblate
@@ -0,0 +1,2 @@
- Fixed a few rare crashes and ANR bugs
- Updated community contributed translations from Weblate
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip