Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bdc2e0aba | |||
| e69061082b | |||
| 1da2ec3cb1 | |||
| 8ffc3b80b2 | |||
| 08f8b6cb8e | |||
| fb09c9692c | |||
| 4901b0c78f | |||
| 0a2117241f | |||
| f352cfd15b | |||
| ac7c5c1064 | |||
| 077cb2103d | |||
| cdeda011a4 | |||
| 894c146988 | |||
| 61cc9e151f | |||
| cfe4c9ff21 | |||
| d4bd29b320 | |||
| 7f2f2056c3 | |||
| 4dd3b2cfb7 | |||
| 2e62ad0f00 | |||
| 41ef292b82 | |||
| aa60671c88 | |||
| f1ccba39e8 | |||
| 226e580a30 | |||
| 6f8e719200 | |||
| c127af1e05 | |||
| 648904cc69 | |||
| dc85ddb3f9 | |||
| 23a7d8555f | |||
| bc9e250d34 | |||
| 2203186527 | |||
| 53d3d9ecb8 | |||
| de549f67a1 | |||
| 755c41481a | |||
| aebc2126bc | |||
| f43547fb31 | |||
| 398e4df7cf | |||
| ff68efc3f5 | |||
| 8ba2f51bda | |||
| 87b79b278b | |||
| 121e3ea9be | |||
| ec6ed79ee1 | |||
| ca125826a7 | |||
| dd0aecf108 | |||
| ef5cb2f0cd |
@@ -3,7 +3,7 @@
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
|
||||
[](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
@@ -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'
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
+23
-21
@@ -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);
|
||||
|
||||
+5
-5
@@ -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);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user