Compare commits

...

67 Commits

Author SHA1 Message Date
Cameron Gutman
ac1cb6d56b Version 9.7.6 2020-10-25 12:44:26 -05:00
Cameron Gutman
dfbffea0fc Disable mouse acceleration on Nvidia Shield TV devices 2020-10-25 12:18:27 -05:00
Cameron Gutman
7ae9c993f1 Version 9.7.5 2020-10-19 23:10:22 -05:00
Cameron Gutman
91d739f8d6 Use the Nvidia button on Shield controllers as a Guide button 2020-10-18 21:14:53 -05:00
Cameron Gutman
f0c625d85c Only emulate buttons that aren't physically present 2020-10-18 21:07:43 -05:00
Cameron Gutman
b5f5e73076 Revert "Remove button emulation"
This reverts commit 092830ed07.
2020-10-18 20:45:11 -05:00
Cameron Gutman
1fb5eff7f1 Update dependencies 2020-10-18 20:38:13 -05:00
Cameron Gutman
5116cfd141 Fix inverted assert condition 2020-10-18 20:08:55 -05:00
Cameron Gutman
e53a1f90b0 Correct some callers of time functions that expect monotonic clocks 2020-10-18 20:05:09 -05:00
Cameron Gutman
766c9628b0 Update moonlight-common-c with MTU test 2020-10-17 21:55:38 -05:00
Cameron Gutman
6a4abdd74c Update to AGP 4.1.0 2020-10-17 21:54:31 -05:00
Cameron Gutman
fc8bc5ba1e Version 9.7.4 2020-10-09 20:06:39 -05:00
Cameron Gutman
0fde5d44c0 Enable HEVC for all Amlogic decoders on API 28+ 2020-10-06 21:40:18 -05:00
Cameron Gutman
dc6b5a3d49 Update AGP to 4.0.2 2020-10-06 21:35:31 -05:00
Cameron Gutman
396522f249 Version 9.7.3 2020-09-07 11:50:03 -07:00
Cameron Gutman
86ab39e4ca Update moonlight-common-c for increased connection reliability 2020-09-06 18:18:41 -07:00
Cameron Gutman
a4c9cb0e55 Version 9.7.2 2020-09-05 10:53:04 -07:00
Cameron Gutman
e6c6feac10 Remove suffix_seekbar_bitrate string 2020-09-04 15:23:32 -07:00
Cameron Gutman
ca0aee58ab Bump bitrate max to 150 Mbps 2020-09-04 15:15:44 -07:00
Cameron Gutman
6391f2c43d Use a 1 Mbps key increment for bitrate 2020-09-04 15:14:30 -07:00
Cameron Gutman
32171bb70c Display bitrate in Mbps 2020-09-04 15:11:24 -07:00
Cameron Gutman
fd6675a3a3 Populate the external IP address when a PC is added manually using an RFC 1918 IPv4 address 2020-08-30 18:39:25 -07:00
bubuleur
9d883978a8 Mise à jour langue française (#865)
* Mise à jour langue française

* Update strings.xml

* Update strings.xml

* Update French

* Update \'
2020-08-30 13:32:05 -07:00
Cameron Gutman
1aae65575c Add warning if no key frames can be received in 10 seconds 2020-08-29 21:27:44 -07:00
Udalov Nikita
c5d58e1aab Update Russian translations (#872)
* Update Russian translations
2020-08-29 19:17:45 -07:00
Cameron Gutman
56394471fa Don't hide games immediately 2020-08-11 18:47:01 -07:00
Cameron Gutman
4cae6959df Update inconclusive test result text 2020-08-09 17:16:51 -07:00
Cameron Gutman
f02d7b4516 Version 9.7.1 2020-08-09 17:13:44 -07:00
Cameron Gutman
f5c83112df Update gitignore 2020-08-09 16:47:16 -07:00
Cameron Gutman
a413dc81c1 Avoid doing client connectivity tests on the main thread 2020-08-09 16:22:50 -07:00
Cameron Gutman
c9eddab191 Remove UDP 7 and add UDP 47009 for WoL 2020-08-09 14:40:44 -07:00
Cameron Gutman
ec1268bd71 Version 9.7 2020-08-09 12:08:50 -07:00
Cameron Gutman
22eb2b5823 Always show the network test option 2020-08-06 22:11:22 -07:00
Cameron Gutman
9669da026f Test network when the connection terminates due to lack of video traffic 2020-08-06 22:01:45 -07:00
Cameron Gutman
7b14e54eab Test network connectivity when adding a PC fails 2020-08-06 20:43:17 -07:00
Cameron Gutman
6b30ee4593 Change connection test domain name 2020-08-06 20:31:15 -07:00
Cameron Gutman
17c47a15da Improve display mode selection algorithm
- Allow the refresh rate to drop if it results in a better match for the stream frame rate
- Allow the resolution to drop for > 60 FPS streams to allow matching a higher refresh rate
2020-08-06 20:14:56 -07:00
Cameron Gutman
8f55517236 Prevent assert when control stream connection fails 2020-08-06 19:13:50 -07:00
Cameron Gutman
41ad086dfa Upgrade to AGP 4.0.1 2020-08-06 19:07:32 -07:00
Cameron Gutman
e19ef7dcae Remove redundant Cancel option in app grid menu 2020-08-04 02:09:33 -07:00
Cameron Gutman
f361265d70 Add automatic network test for failed connection stages 2020-08-01 22:56:32 -07:00
Cameron Gutman
ef72e3ef77 Only show the option to hide the app if it's not running or already hidden 2020-08-01 22:48:22 -07:00
Cameron Gutman
770f1a1ca0 Add network connection test 2020-08-01 22:19:40 -07:00
Cameron Gutman
e8fc91191f Add the option to hide games in the app list
Fixes #640
2020-08-01 18:20:39 -07:00
Cameron Gutman
105ad3317d Pass parent view into grid adapters 2020-08-01 17:52:55 -07:00
Cameron Gutman
22bf4775cd Enable poll() in ENet 2020-07-27 00:12:26 -07:00
Cameron Gutman
5c6be7969a Disable max operating rate trick on all Snapdragon 765G devices
Fixes #783
2020-07-26 22:39:10 -07:00
Cameron Gutman
c6e23f4be2 Update common-c with client connectivity test and select() replacement 2020-07-26 22:06:46 -07:00
Cameron Gutman
05547c22ec Use SecureRandom for PINs 2020-07-12 12:16:11 -07:00
Cameron Gutman
cc7ac79fa6 Version 9.6.4 2020-07-10 18:31:41 -07:00
Cameron Gutman
4c5c27dfc1 Re-enable the max operating rate trick on Android 10 except on the Mi 10 Lite 5G
It still provides nice performance gains on Pixel 2 running Android 10
2020-07-10 18:29:29 -07:00
Cameron Gutman
4aabfbd52e Add missing jlong cast to fix Lint warning 2020-07-07 01:10:10 -05:00
Cameron Gutman
6eab842361 Fix Lint error due to extra translated strings 2020-07-07 01:05:10 -05:00
a6969
b729dfd702 Added Ukrainian language (#857)
* Added Ukrainian language strings.xml

Translated the application into Ukrainian language.
2020-07-07 00:59:42 -05:00
Cameron Gutman
6366840781 Update common-c to remove FEC validation assert that fails on GFE 3.20.4 2020-07-07 00:58:44 -05:00
Cameron Gutman
704a2ee90b Propagate exceptions caused by GFE response parsing errors 2020-07-07 00:57:37 -05:00
Cameron Gutman
484be9bfe6 Wrap and propagate unexpected exceptions 2020-07-07 00:52:11 -05:00
Cameron Gutman
a99e070c26 Fix missing return causing invalid parameters to be passed to LiStartConnection() 2020-07-07 00:47:12 -05:00
Cameron Gutman
bf803f88af Refactor TLS initialization code 2020-07-06 02:32:06 -05:00
Cameron Gutman
9af6febca5 Fix pairing issue due to picking up a final local variable instead of a class member 2020-07-06 02:30:49 -05:00
Cameron Gutman
0101d0a1bd Fix TLS error when connecting to GFE 3.20.4 on Android 4.x 2020-07-06 01:44:35 -05:00
Cameron Gutman
266874609d Fix hostname validation for CA-issued certificates 2020-07-04 20:09:06 -05:00
Cameron Gutman
2ba7feedfc Fix several Lint warnings 2020-07-04 15:41:41 -05:00
Cameron Gutman
43c67b4939 Avoid using max operating rate on Android Q and non-Qualcomm devices 2020-07-01 11:26:40 -05:00
Cameron Gutman
2d9915e43a Enable GWP-ASan on Android 11 2020-07-01 11:07:53 -05:00
Cameron Gutman
2329b41bce Rethrow the original validation error if the cert isn't pinned or self-signed 2020-06-29 11:29:33 -07:00
Cameron Gutman
536496184e Use the default X509TrustManager to validate non-pinned certificates
This allows the certificate to be rotated without re-adding the PC.
2020-06-29 11:20:14 -07:00
57 changed files with 1079 additions and 266 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
*.ap_
*.aab
output.json
output-metadata.json
out/
# files for the dex VM

View File

@@ -7,8 +7,8 @@ android {
minSdkVersion 16
targetSdkVersion 30
versionName "9.6.1"
versionCode = 233
versionName "9.7.6"
versionCode = 246
}
flavorDimensions "root"
@@ -114,10 +114,10 @@ android {
}
dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
implementation 'org.bouncycastle:bcprov-jdk15on:1.66'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.66'
implementation 'org.jcodec:jcodec:0.2.3'
implementation 'com.squareup.okhttp3:okhttp:3.12.10'
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.squareup.okio:okio:1.17.5'
implementation 'org.jmdns:jmdns:3.5.5'
}

View File

@@ -42,6 +42,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:installLocation="auto"
android:gwpAsanMode="always"
android:theme="@style/AppTheme">
<provider
android:name=".PosterContentProvider"
@@ -125,6 +126,15 @@
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.AppView" />
<!-- Special metadata for NVIDIA Shield devices to prevent input buffering
and most importantly, opt out of mouse acceleration while streaming -->
<meta-data
android:name="com.nvidia.immediateInput"
android:value="true" />
<meta-data
android:name="com.nvidia.rawCursorInput"
android:value="true" />
</activity>
<service

View File

@@ -2,6 +2,7 @@ package com.limelight;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashSet;
import java.util.List;
import com.limelight.computers.ComputerManagerListener;
@@ -26,6 +27,7 @@ import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -59,17 +61,22 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private int lastRunningAppId;
private boolean suspendGridUpdates;
private boolean inForeground;
private boolean showHiddenApps;
private HashSet<Integer> hiddenAppIds = new HashSet<>();
private final static int START_OR_RESUME_ID = 1;
private final static int QUIT_ID = 2;
private final static int CANCEL_ID = 3;
private final static int START_WITH_QUIT = 4;
private final static int VIEW_DETAILS_ID = 5;
private final static int CREATE_SHORTCUT_ID = 6;
private final static int HIDE_APP_ID = 7;
public final static String HIDDEN_APPS_PREF_FILENAME = "HiddenApps";
public final static String NAME_EXTRA = "Name";
public final static String UUID_EXTRA = "UUID";
public final static String NEW_PAIR_EXTRA = "NewPair";
public final static String SHOW_HIDDEN_APPS_EXTRA = "ShowHiddenApps";
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@@ -98,13 +105,16 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
try {
appGridAdapter = new AppGridAdapter(AppView.this,
PreferenceConfiguration.readPreferences(AppView.this),
computer, localBinder.getUniqueId());
computer, localBinder.getUniqueId(),
showHiddenApps);
} catch (Exception e) {
e.printStackTrace();
finish();
return;
}
appGridAdapter.updateHiddenApps(hiddenAppIds, true);
// Now make the binder visible. We must do this after appGridAdapter
// is set to prevent us from reaching updateUiWithServerinfo() and
// touching the appGridAdapter prior to initialization.
@@ -285,8 +295,14 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
UiHelper.notifyNewRootView(this);
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
uuidString = getIntent().getStringExtra(UUID_EXTRA);
SharedPreferences hiddenAppsPrefs = getSharedPreferences(HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE);
for (String hiddenAppIdStr : hiddenAppsPrefs.getStringSet(uuidString, new HashSet<String>())) {
hiddenAppIds.add(Integer.parseInt(hiddenAppIdStr));
}
String computerName = getIntent().getStringExtra(NAME_EXTRA);
TextView label = findViewById(R.id.appListText);
@@ -298,6 +314,21 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
Service.BIND_AUTO_CREATE);
}
private void updateHiddenApps(boolean hideImmediately) {
HashSet<String> hiddenAppIdStringSet = new HashSet<>();
for (Integer hiddenAppId : hiddenAppIds) {
hiddenAppIdStringSet.add(hiddenAppId.toString());
}
getSharedPreferences(HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE)
.edit()
.putStringSet(uuidString, hiddenAppIdStringSet)
.apply();
appGridAdapter.updateHiddenApps(hiddenAppIds, hideImmediately);
}
private void populateAppGridWithCache() {
try {
// Try to load from cache
@@ -365,10 +396,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}
else {
menu.add(Menu.NONE, START_WITH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
}
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
// Only show the hide checkbox if this is not the currently running app or it's already hidden
if (lastRunningAppId != selectedApp.app.getAppId() || selectedApp.isHidden) {
MenuItem hideAppItem = menu.add(Menu.NONE, HIDE_APP_ID, 3, getResources().getString(R.string.applist_menu_hide_app));
hideAppItem.setCheckable(true);
hideAppItem.setChecked(selectedApp.isHidden);
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 4, getResources().getString(R.string.applist_menu_details));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Only add an option to create shortcut if box art is loaded
@@ -379,7 +417,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
BitmapDrawable drawable = (BitmapDrawable)appImageView.getDrawable();
if (drawable != null && drawable.getBitmap() != null) {
// We have a bitmap loaded too
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 5, getResources().getString(R.string.applist_menu_scut));
}
}
}
@@ -430,13 +468,22 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case CANCEL_ID:
return true;
case VIEW_DETAILS_ID:
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details), app.app.toString(), false);
return true;
case HIDE_APP_ID:
if (item.isChecked()) {
// Transitioning hidden to shown
hiddenAppIds.remove(app.app.getAppId());
}
else {
// Transitioning shown to hidden
hiddenAppIds.add(app.app.getAppId());
}
updateHiddenApps(false);
return true;
case CREATE_SHORTCUT_ID:
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
Bitmap appBits = ((BitmapDrawable)appImageView.getDrawable()).getBitmap();
@@ -590,9 +637,10 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
listView.requestFocus();
}
public class AppObject {
public static class AppObject {
public final NvApp app;
public boolean isRunning;
public boolean isHidden;
public AppObject(NvApp app) {
if (app == null) {

View File

@@ -30,6 +30,7 @@ import com.limelight.ui.GameGestures;
import com.limelight.ui.StreamView;
import com.limelight.utils.Dialog;
import com.limelight.utils.NetHelper;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
@@ -616,6 +617,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
private boolean isRefreshRateGoodMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
Math.round(refreshRate) % prefConfig.fps <= 3;
}
private float prepareDisplayForRendering() {
Display display = getWindowManager().getDefaultDisplay();
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
@@ -624,41 +630,59 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display.Mode bestMode = display.getMode();
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateOk = candidate.getRefreshRate() >= bestMode.getRefreshRate();
boolean resolutionOk = candidate.getPhysicalWidth() >= bestMode.getPhysicalWidth() &&
candidate.getPhysicalHeight() >= bestMode.getPhysicalHeight() &&
candidate.getPhysicalWidth() <= 4096;
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
candidate.getPhysicalHeight() < bestMode.getPhysicalHeight();
boolean resolutionFitsStream = candidate.getPhysicalWidth() >= prefConfig.width &&
candidate.getPhysicalHeight() >= prefConfig.height;
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
// On non-4K streams, we force the resolution to never change
if (prefConfig.width < 3840) {
if (candidate.getPhysicalWidth() > 4096) {
// Avoid resolutions options above 4K to be safe
continue;
}
// On non-4K streams, we force the resolution to never change unless it's above
// 60 FPS, which may require a resolution reduction due to HDMI bandwidth limitations.
if (prefConfig.width < 3840 && prefConfig.fps <= 60) {
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
continue;
}
}
// Ensure the frame rate stays around 60 Hz for <= 60 FPS streams
if (prefConfig.fps <= 60) {
if (candidate.getRefreshRate() >= 63) {
// Make sure the resolution doesn't regress unless if it's over 60 FPS
// where we may need to reduce resolution to achieve the desired refresh rate.
if (resolutionReduced && !(prefConfig.fps > 60 && resolutionFitsStream)) {
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())) {
continue;
}
}
// Make sure the refresh rate doesn't regress
if (!refreshRateOk) {
continue;
}
// Make sure the resolution doesn't regress
if (!resolutionOk) {
continue;
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
// We didn't have a good match and this match isn't good either, so just don't
// reduce the refresh rate.
if (refreshRateReduced) {
continue;
}
} else {
// We didn't have a good match and this match is good. Prefer this refresh rate
// even if it reduces the refresh rate. Lowering the refresh rate can be beneficial
// when streaming a 60 FPS stream on a 90 Hz device. We want to select 60 Hz to
// match the frame rate even if the active display mode is 90 Hz.
}
bestMode = candidate;
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
@@ -669,9 +693,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float bestRefreshRate = display.getRefreshRate();
for (float candidate : display.getSupportedRefreshRates()) {
if (candidate > bestRefreshRate) {
LimeLog.info("Examining refresh rate: "+candidate);
LimeLog.info("Examining refresh rate: "+candidate);
if (candidate > bestRefreshRate) {
// Ensure the frame rate stays around 60 Hz for <= 60 FPS streams
if (prefConfig.fps <= 60) {
if (candidate >= 63) {
@@ -1502,7 +1526,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
@Override
public void stageFailed(final String stage, final int errorCode) {
public void stageFailed(final String stage, final int portFlags, final int errorCode) {
// Perform a connection test if the failure could be due to a blocked port
// This does network I/O, so don't do it on the main thread.
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443, portFlags);
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1520,8 +1548,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Toast.makeText(Game.this, getResources().getText(R.string.video_decoder_init_failed), Toast.LENGTH_LONG).show();
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.conn_error_msg) + " " + stage +" (error "+errorCode+")", true);
String dialogText = getResources().getString(R.string.conn_error_msg) + " " + stage +" (error "+errorCode+")";
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
dialogText += "\n\n" + getResources().getString(R.string.nettest_text_blocked);
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title), dialogText, true);
}
}
});
@@ -1529,6 +1562,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void connectionTerminated(final int errorCode) {
// Perform a connection test if the failure could be due to a blocked port
// This does network I/O, so don't do it on the main thread.
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER,
443, MoonBridge.getPortFlagsFromTerminationErrorCode(errorCode));
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1548,14 +1586,24 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (errorCode != MoonBridge.ML_ERROR_GRACEFUL_TERMINATION) {
String message;
switch (errorCode) {
case MoonBridge.ML_ERROR_NO_VIDEO_TRAFFIC:
message = getResources().getString(R.string.no_video_received_error);
break;
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
// If we got a blocked result, that supersedes any other error message
message = getResources().getString(R.string.nettest_text_blocked);
}
else {
switch (errorCode) {
case MoonBridge.ML_ERROR_NO_VIDEO_TRAFFIC:
message = getResources().getString(R.string.no_video_received_error);
break;
default:
message = getResources().getString(R.string.conn_terminated_msg);
break;
case MoonBridge.ML_ERROR_NO_VIDEO_FRAME:
message = getResources().getString(R.string.no_frame_received_error);
break;
default:
message = getResources().getString(R.string.conn_terminated_msg);
break;
}
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),

View File

@@ -117,6 +117,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
private final static int RESUME_ID = 6;
private final static int QUIT_ID = 7;
private final static int VIEW_DETAILS_ID = 8;
private final static int FULL_APP_LIST_ID = 9;
private final static int TEST_NETWORK_ID = 10;
private void initializeViews() {
setContentView(R.layout.activity_pc_view);
@@ -320,11 +322,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (computer.details.state == ComputerDetails.State.OFFLINE ||
computer.details.state == ComputerDetails.State.UNKNOWN) {
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
else if (computer.details.pairState != PairState.PAIRED) {
menu.add(Menu.NONE, PAIR_ID, 1, getResources().getString(R.string.pcview_menu_pair_pc));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
else {
if (computer.details.runningGameId != 0) {
@@ -333,12 +333,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}
menu.add(Menu.NONE, APP_LIST_ID, 3, getResources().getString(R.string.pcview_menu_app_list));
// FIXME: We used to be able to unpair here but it's been broken since GFE 2.1.x, so I've replaced
// it with delete which actually work
menu.add(Menu.NONE, DELETE_ID, 4, getResources().getString(R.string.pcview_menu_delete_pc));
menu.add(Menu.NONE, FULL_APP_LIST_ID, 4, getResources().getString(R.string.pcview_menu_full_app_list));
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 5, getResources().getString(R.string.pcview_menu_details));
menu.add(Menu.NONE, TEST_NETWORK_ID, 5, getResources().getString(R.string.pcview_menu_test_network));
menu.add(Menu.NONE, DELETE_ID, 6, getResources().getString(R.string.pcview_menu_delete_pc));
menu.add(Menu.NONE, VIEW_DETAILS_ID, 7, getResources().getString(R.string.pcview_menu_details));
}
@Override
@@ -442,7 +442,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (toastSuccess) {
// Open the app list after a successful pairing attempt
doAppList(computer, true);
doAppList(computer, true, false);
}
else {
// Start polling again if we're still in the foreground
@@ -541,7 +541,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}).start();
}
private void doAppList(ComputerDetails computer, boolean newlyPaired) {
private void doAppList(ComputerDetails computer, boolean newlyPaired, boolean showHiddenGames) {
if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return;
@@ -555,6 +555,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid);
i.putExtra(AppView.NEW_PAIR_EXTRA, newlyPaired);
i.putExtra(AppView.SHOW_HIDDEN_APPS_EXTRA, showHiddenGames);
startActivity(i);
}
@@ -592,8 +593,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case FULL_APP_LIST_ID:
case APP_LIST_ID:
doAppList(computer.details, false);
doAppList(computer.details, false, item.getItemId() == FULL_APP_LIST_ID);
return true;
case RESUME_ID:
@@ -625,6 +627,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Dialog.displayDialog(PcView.this, getResources().getString(R.string.title_details), computer.details.toString(), false);
return true;
case TEST_NETWORK_ID:
ServerHelper.doNetworkTest(PcView.this);
return true;
default:
return super.onContextItemSelected(item);
}
@@ -635,6 +641,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
new DiskAssetLoader(this).deleteAssetsForComputer(details.uuid);
// Delete hidden games preference value
getSharedPreferences(AppView.HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE)
.edit()
.remove(details.uuid)
.apply();
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
@@ -711,7 +723,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Pair an unpaired machine by default
doPair(computer.details);
} else {
doAppList(computer.details, false);
doAppList(computer.details, false, false);
}
}
});
@@ -719,7 +731,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
registerForContextMenu(listView);
}
public class ComputerObject {
public static class ComputerObject {
public ComputerDetails details;
public ComputerObject(ComputerDetails details) {

View File

@@ -102,9 +102,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
LimeLog.warning("Corrupted certificate");
return false;
} catch (NoSuchAlgorithmException e) {
// Should never happen
e.printStackTrace();
return false;
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
// May happen if the key is corrupt
LimeLog.warning("Corrupted key");
@@ -124,10 +122,8 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", bcProvider);
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e1) {
// Should never happen
e1.printStackTrace();
return false;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
Date now = new Date();
@@ -152,8 +148,6 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
cert = new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) {
// Nothing should go wrong here
e.printStackTrace();
throw new RuntimeException(e);
}

View File

@@ -40,6 +40,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
private static final int EMULATING_SPECIAL = 0x1;
private static final int EMULATING_SELECT = 0x2;
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
private final Vector2d inputVector = new Vector2d();
private final SparseArray<InputDeviceContext> inputDeviceContexts = new SparseArray<>();
@@ -461,6 +467,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.vibrator = dev.getVibrator();
}
// Detect if the gamepad has Mode and Select buttons according to the Android key layouts.
// We do this first because other codepaths below may override these defaults.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
boolean[] buttons = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_MODE, KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BACK, 0);
context.hasMode = buttons[0];
context.hasSelect = buttons[1] || buttons[2];
}
context.leftStickXAxis = MotionEvent.AXIS_X;
context.leftStickYAxis = MotionEvent.AXIS_Y;
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
@@ -519,6 +533,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// The old DS4 driver uses RX and RY for triggers
context.leftTriggerAxis = MotionEvent.AXIS_RX;
context.rightTriggerAxis = MotionEvent.AXIS_RY;
// DS4 has Select and Mode buttons (possibly mapped non-standard)
context.hasSelect = true;
context.hasMode = true;
}
else {
// If it's not a non-standard DS4 controller, it's probably an Xbox controller or
@@ -600,6 +618,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.backIsStart = true;
context.modeIsSelect = true;
context.triggerDeadzone = 0.30f;
context.hasSelect = true;
context.hasMode = false;
}
}
@@ -617,6 +637,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (!hasStartKey[0] && !hasStartKey[1]) {
context.backIsStart = true;
context.modeIsSelect = true;
context.hasSelect = true;
context.hasMode = false;
}
}
@@ -625,14 +647,26 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.triggerDeadzone = 0.30f;
}
// SHIELD controllers will use small stick deadzones
else if (devName.contains("SHIELD")) {
else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
context.leftStickDeadzoneRadius = 0.07f;
context.rightStickDeadzoneRadius = 0.07f;
// The big Nvidia button on the Shield controllers acts like a Search button. It
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
// nothing, so we can hijack it to act like a mode button.
if (devName.contains("NVIDIA Controller v01.03") || devName.contains("NVIDIA Controller v01.04")) {
context.searchIsMode = true;
context.hasMode = true;
}
}
// The Serval has a couple of unknown buttons that are start and select. It also has
// a back button which we want to ignore since there's already a select button.
else if (devName.contains("Razer Serval")) {
context.isServal = true;
// Serval has Select and Mode buttons (possibly mapped non-standard)
context.hasMode = true;
context.hasSelect = true;
}
// The Xbox One S Bluetooth controller has some mappings that need fixing up.
// However, Microsoft released a firmware update with no change to VID/PID
@@ -643,6 +677,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
else if (devName.equals("Xbox Wireless Controller")) {
if (gasRange == null) {
context.isNonStandardXboxBtController = true;
// Xbox One S has Select and Mode buttons (possibly mapped non-standard)
context.hasMode = true;
context.hasSelect = true;
}
}
}
@@ -1018,6 +1056,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Emulate the select button with mode
return KeyEvent.KEYCODE_BUTTON_SELECT;
}
else if (context.searchIsMode && keyCode == KeyEvent.KEYCODE_SEARCH) {
// Emulate the mode button with search
return KeyEvent.KEYCODE_BUTTON_MODE;
}
return keyCode;
}
@@ -1416,6 +1458,41 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return false;
}
// Check if we're emulating the select button
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
{
// If either start or LB is up, select comes up too
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
(context.inputMap & ControllerPacket.LB_FLAG) == 0)
{
context.inputMap &= ~ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
try {
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
}
}
// Check if we're emulating the special button
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
{
// If either start or select and RB is up, the special button comes up too
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
((context.inputMap & ControllerPacket.BACK_FLAG) == 0 &&
(context.inputMap & ControllerPacket.RB_FLAG) == 0))
{
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
try {
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
}
}
sendControllerInputPacket(context);
if (context.pendingExit && context.inputMap == 0) {
@@ -1444,6 +1521,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE:
context.hasMode = true;
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_START:
@@ -1455,6 +1533,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
context.hasSelect = true;
context.inputMap |= ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -1535,6 +1614,43 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.pendingExit = true;
}
// Start+LB acts like select for controllers with one button
if (!context.hasSelect) {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
context.inputMap |= ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
}
}
// If there is a physical select button, we'll use Start+Select as the special button combo
// otherwise we'll use Start+RB.
if (!context.hasMode) {
if (context.hasSelect) {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG)) {
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
}
else {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
}
}
// We don't need to send repeat key down events, but the platform
// sends us events that claim to be repeats but they're from different
// devices, so we just send them all and deal with some duplicates.
@@ -1616,7 +1732,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
usbDeviceContexts.put(controller.getControllerId(), context);
}
class GenericControllerContext {
static class GenericControllerContext {
public int id;
public boolean external;
@@ -1676,10 +1792,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public boolean isServal;
public boolean backIsStart;
public boolean modeIsSelect;
public boolean searchIsMode;
public boolean ignoreBack;
public boolean hasJoystickAxes;
public boolean pendingExit;
public int emulatingButtonFlags = 0;
public boolean hasSelect;
public boolean hasMode;
// Used for OUYA bumper state tracking since they force all buttons
// up when the OUYA button goes down. We watch the last time we get
// a bumper up and compare that to our maximum delay when we receive

View File

@@ -8,6 +8,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.view.MotionEvent;
import java.util.ArrayList;
@@ -270,7 +271,7 @@ public class AnalogStick extends VirtualControllerElement {
// We also release the deadzone if the user keeps the stick pressed for a bit to allow
// them to make precise movements.
stick_state = (stick_state == STICK_STATE.MOVED_ACTIVE ||
System.currentTimeMillis() - timeLastClick > timeoutDeadzone ||
SystemClock.uptimeMillis() - timeLastClick > timeoutDeadzone ||
movement_radius > radius_dead_zone) ?
STICK_STATE.MOVED_ACTIVE : STICK_STATE.MOVED_IN_DEAD_ZONE;
@@ -311,7 +312,7 @@ public class AnalogStick extends VirtualControllerElement {
stick_state = STICK_STATE.MOVED_IN_DEAD_ZONE;
// check for double click
if (lastClickState == CLICK_STATE.SINGLE &&
timeLastClick + timeoutDoubleClick > System.currentTimeMillis()) {
timeLastClick + timeoutDoubleClick > SystemClock.uptimeMillis()) {
click_state = CLICK_STATE.DOUBLE;
notifyOnDoubleClick();
} else {
@@ -319,7 +320,7 @@ public class AnalogStick extends VirtualControllerElement {
notifyOnClick();
}
// reset last click timestamp
timeLastClick = System.currentTimeMillis();
timeLastClick = SystemClock.uptimeMillis();
// set item pressed and update
setPressed(true);
break;

View File

@@ -22,7 +22,7 @@ import java.util.Timer;
import java.util.TimerTask;
public class VirtualController {
public class ControllerInputContext {
public static class ControllerInputContext {
public short inputMap = 0x0000;
public byte leftTrigger = 0x00;
public byte rightTrigger = 0x00;

View File

@@ -20,6 +20,7 @@ import android.media.MediaFormat;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException;
import android.os.Build;
import android.os.SystemClock;
import android.util.Range;
import android.view.SurfaceHolder;
@@ -334,11 +335,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
}
// Operate at maximum rate to lower latency as much as possible on
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
// but that will actually result in the decoder crashing if it can't satisfy
// our (ludicrous) operating rate requirement.
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(selectedDecoderName)) {
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
}
}
configuredFormat = videoFormat;
@@ -412,7 +411,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
//
if (initialException != null) {
// This isn't the first time we've had an exception processing video
if (System.currentTimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) {
if (SystemClock.uptimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) {
// It's been over 3 seconds and we're still getting exceptions. Throw the original now.
if (!reportedCrash) {
reportedCrash = true;
@@ -429,7 +428,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
else {
initialException = new RendererException(this, e);
}
initialExceptionTimestamp = System.currentTimeMillis();
initialExceptionTimestamp = SystemClock.uptimeMillis();
}
}
}
@@ -639,7 +638,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
if (lastFrameNumber == 0) {
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis();
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
} else if (frameNumber != lastFrameNumber && frameNumber != lastFrameNumber + 1) {
// We can receive the same "frame" multiple times if it's an IDR frame.
// In that case, each frame start NALU is submitted independently.
@@ -651,7 +650,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
lastFrameNumber = frameNumber;
// Flip stats windows roughly every second
if (System.currentTimeMillis() >= activeWindowVideoStats.measurementStartTimestamp + 1000) {
if (SystemClock.uptimeMillis() >= activeWindowVideoStats.measurementStartTimestamp + 1000) {
if (prefs.enablePerfOverlay) {
VideoStats lastTwo = new VideoStats();
lastTwo.add(lastWindowVideoStats);
@@ -684,7 +683,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
globalVideoStats.add(activeWindowVideoStats);
lastWindowVideoStats.copy(activeWindowVideoStats);
activeWindowVideoStats.clear();
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis();
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
}
activeWindowVideoStats.totalFramesReceived++;

View File

@@ -41,6 +41,7 @@ public class MediaCodecHelper {
private static final List<String> qualcommDecoderPrefixes;
private static boolean isLowEndSnapdragon = false;
private static boolean isAdreno620 = false;
private static boolean initialized = false;
static {
@@ -152,9 +153,15 @@ public class MediaCodecHelper {
whitelistedHevcDecoders.add("omx.mtk");
}
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
whitelistedHevcDecoders.add("omx.amlogic");
}
// These theoretically have good HEVC decoding capabilities (potentially better than
// their AVC decoders), but haven't been tested enough
//whitelistedHevcDecoders.add("omx.amlogic");
//whitelistedHevcDecoders.add("omx.rk");
// Let's see if HEVC decoders are finally stable with C2
@@ -231,19 +238,23 @@ public class MediaCodecHelper {
return modelNumber.charAt(1) == '0';
}
private static int getAdrenoRendererModelNumber(String glRenderer) {
String modelNumber = getAdrenoVersionString(glRenderer);
if (modelNumber == null) {
// Not an Adreno GPU
return -1;
}
return Integer.parseInt(modelNumber);
}
// This is a workaround for some broken devices that report
// only GLES 3.0 even though the GPU is an Adreno 4xx series part.
// An example of such a device is the Huawei Honor 5x with the
// Snapdragon 616 SoC (Adreno 405).
private static boolean isGLES31SnapdragonRenderer(String glRenderer) {
String modelNumber = getAdrenoVersionString(glRenderer);
if (modelNumber == null) {
// Not an Adreno GPU
return false;
}
// Snapdragon 4xx and higher support GLES 3.1
return modelNumber.charAt(0) >= '4';
return getAdrenoRendererModelNumber(glRenderer) >= 400;
}
public static void initialize(Context context, String glRenderer) {
@@ -258,6 +269,7 @@ public class MediaCodecHelper {
LimeLog.info("OpenGL ES version: "+configInfo.reqGlEsVersion);
isLowEndSnapdragon = isLowEndSnapdragonRenderer(glRenderer);
isAdreno620 = getAdrenoRendererModelNumber(glRenderer) == 620;
// Tegra K1 and later can do reference frame invalidation properly
if (configInfo.reqGlEsVersion >= 0x30000) {
@@ -354,6 +366,21 @@ public class MediaCodecHelper {
return false;
}
public static boolean decoderSupportsMaxOperatingRate(String decoderName) {
// Operate at maximum rate to lower latency as much as possible on
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
// but that will actually result in the decoder crashing if it can't satisfy
// our (ludicrous) operating rate requirement. This seems to cause reliable
// crashes on the Xiaomi Mi 10 lite 5G and Redmi K30i 5G on Android 10, so
// we'll disable it on Snapdragon 765G and all non-Qualcomm devices to be safe.
//
// NB: Even on Android 10, this optimization still provides significant
// performance gains on Pixel 2.
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
isDecoderInList(qualcommDecoderPrefixes, decoderName) &&
!isAdreno620;
}
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
// Possibly enable adaptive playback on KitKat and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

View File

@@ -1,5 +1,7 @@
package com.limelight.binding.video;
import android.os.SystemClock;
class VideoStats {
long decoderTimeMs;
@@ -24,7 +26,7 @@ class VideoStats {
this.measurementStartTimestamp = other.measurementStartTimestamp;
}
assert other.measurementStartTimestamp <= this.measurementStartTimestamp;
assert other.measurementStartTimestamp >= this.measurementStartTimestamp;
}
void copy(VideoStats other) {
@@ -50,7 +52,7 @@ class VideoStats {
}
VideoStatsFps getFps() {
float elapsed = (System.currentTimeMillis() - this.measurementStartTimestamp) / (float) 1000;
float elapsed = (SystemClock.uptimeMillis() - this.measurementStartTimestamp) / (float) 1000;
VideoStatsFps fps = new VideoStatsFps();
if (elapsed > 0) {

View File

@@ -4,8 +4,10 @@ import java.io.IOException;
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;
import java.util.List;
@@ -38,6 +40,7 @@ import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import org.xmlpull.v1.XmlPullParserException;
@@ -134,6 +137,18 @@ public class ComputerManagerService extends Service {
dbManager.updateComputer(existingComputer);
}
else {
try {
// If the active address is a site-local address (RFC 1918),
// then use STUN to populate the external address field if
// it's not set already.
if (details.remoteAddress == null) {
InetAddress addr = InetAddress.getByName(details.activeAddress);
if (addr.isSiteLocalAddress()) {
populateExternalAddress(details);
}
}
} catch (UnknownHostException ignored) {}
dbManager.updateComputer(details);
}
}
@@ -162,7 +177,7 @@ public class ComputerManagerService extends Service {
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
offlineCount++;
} else {
tuple.lastSuccessfulPollMs = System.currentTimeMillis();
tuple.lastSuccessfulPollMs = SystemClock.elapsedRealtime();
offlineCount = 0;
}
}
@@ -193,7 +208,7 @@ public class ComputerManagerService extends Service {
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
// Enforce the poll data TTL
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
if (SystemClock.elapsedRealtime() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
LimeLog.info("Timing out polled state for "+tuple.computer.name);
tuple.computer.state = ComputerDetails.State.UNKNOWN;
}

View File

@@ -17,8 +17,12 @@ import com.limelight.grid.assets.NetworkAssetLoader;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.preferences.PreferenceConfiguration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SuppressWarnings("unchecked")
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
@@ -28,18 +32,47 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
private final ComputerDetails computer;
private final String uniqueId;
private final boolean showHiddenApps;
private CachedAppAssetLoader loader;
private Set<Integer> hiddenAppIds = new HashSet<>();
private ArrayList<AppView.AppObject> allApps = new ArrayList<>();
public AppGridAdapter(Context context, PreferenceConfiguration prefs, ComputerDetails computer, String uniqueId) {
public AppGridAdapter(Context context, PreferenceConfiguration prefs, ComputerDetails computer, String uniqueId, boolean showHiddenApps) {
super(context, getLayoutIdForPreferences(prefs));
this.computer = computer;
this.uniqueId = uniqueId;
this.showHiddenApps = showHiddenApps;
updateLayoutWithPreferences(context, prefs);
}
public void updateHiddenApps(Set<Integer> newHiddenAppIds, boolean hideImmediately) {
this.hiddenAppIds.clear();
this.hiddenAppIds.addAll(newHiddenAppIds);
if (hideImmediately) {
// Reconstruct the itemList with the new hidden app set
itemList.clear();
for (AppView.AppObject app : allApps) {
app.isHidden = hiddenAppIds.contains(app.app.getAppId());
if (!app.isHidden || showHiddenApps) {
itemList.add(app);
}
}
}
else {
// Just update the isHidden state to show the correct UI indication
for (AppView.AppObject app : allApps) {
app.isHidden = hiddenAppIds.contains(app.app.getAppId());
}
}
notifyDataSetChanged();
}
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
if (prefs.smallIconMode) {
return R.layout.app_grid_item_small;
@@ -88,8 +121,8 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
loader.freeCacheMemory();
}
private void sortList() {
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
private static void sortList(List<AppView.AppObject> list) {
Collections.sort(list, new Comparator<AppView.AppObject>() {
@Override
public int compare(AppView.AppObject lhs, AppView.AppObject rhs) {
return lhs.app.getAppName().toLowerCase().compareTo(rhs.app.getAppName().toLowerCase());
@@ -98,20 +131,37 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
}
public void addApp(AppView.AppObject app) {
// Queue a request to fetch this bitmap into cache
loader.queueCacheLoad(app.app);
// Update hidden state
app.isHidden = hiddenAppIds.contains(app.app.getAppId());
// Add the app to our sorted list
itemList.add(app);
sortList();
// Always add the app to the all apps list
allApps.add(app);
sortList(allApps);
// Add the app to the adapter data if it's not hidden
if (showHiddenApps || !app.isHidden) {
// Queue a request to fetch this bitmap into cache
loader.queueCacheLoad(app.app);
// Add the app to our sorted list
itemList.add(app);
sortList(itemList);
}
}
public void removeApp(AppView.AppObject app) {
itemList.remove(app);
allApps.remove(app);
}
@Override
public void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, AppView.AppObject obj) {
public void clear() {
super.clear();
allApps.clear();
}
@Override
public void populateView(View parentView, ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, AppView.AppObject obj) {
// Let the cached asset loader handle it
loader.populateImageView(obj.app, imgView, txtView);
@@ -123,5 +173,12 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
else {
overlayView.setVisibility(View.GONE);
}
if (obj.isHidden) {
parentView.setAlpha(0.40f);
}
else {
parentView.setAlpha(1.0f);
}
}
}

View File

@@ -54,7 +54,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
return i;
}
public abstract void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, T obj);
public abstract void populateView(View parentView, ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, T obj);
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
@@ -67,7 +67,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
TextView txtView = convertView.findViewById(R.id.grid_text);
ProgressBar prgView = convertView.findViewById(R.id.grid_spinner);
populateView(imgView, prgView, txtView, overlayView, itemList.get(i));
populateView(convertView, imgView, prgView, txtView, overlayView, itemList.get(i));
return convertView;
}

View File

@@ -49,7 +49,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
}
@Override
public void populateView(ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, PcView.ComputerObject obj) {
public void populateView(View parentView, ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, PcView.ComputerObject obj) {
imgView.setImageResource(R.drawable.ic_computer);
if (obj.details.state == ComputerDetails.State.ONLINE) {
imgView.setAlpha(1.0f);

View File

@@ -362,7 +362,7 @@ public class CachedAppAssetLoader {
return false;
}
public class LoaderTuple {
public static class LoaderTuple {
public final ComputerDetails computer;
public final NvApp app;

View File

@@ -43,25 +43,26 @@ public class NvConnection {
this.context = new ConnectionContext();
this.context.streamConfig = config;
this.context.serverCert = serverCert;
try {
// This is unique per connection
this.context.riKey = generateRiAesKey();
} catch (NoSuchAlgorithmException e) {
// Should never happen
e.printStackTrace();
}
this.context.riKeyId = generateRiKeyId();
// This is unique per connection
this.context.riKey = generateRiAesKey();
context.riKeyId = generateRiKeyId();
this.isMonkey = ActivityManager.isUserAMonkey();
}
private static SecretKey generateRiAesKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
// RI keys are 128 bits
keyGen.init(128);
return keyGen.generateKey();
private static SecretKey generateRiAesKey() {
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
// RI keys are 128 bits
keyGen.init(128);
return keyGen.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static int generateRiKeyId() {
@@ -230,18 +231,19 @@ public class NvConnection {
try {
if (!startApp()) {
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, 0, 0);
return;
}
context.connListener.stageComplete(appName);
} catch (GfeHttpResponseException e) {
e.printStackTrace();
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, e.getErrorCode());
context.connListener.stageFailed(appName, 0, e.getErrorCode());
return;
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, MoonBridge.ML_PORT_FLAG_TCP_47984 | MoonBridge.ML_PORT_FLAG_TCP_47989, 0);
return;
}
@@ -254,7 +256,7 @@ public class NvConnection {
connectionAllowed.acquire();
} catch (InterruptedException e) {
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, 0, 0);
return;
}

View File

@@ -3,7 +3,7 @@ package com.limelight.nvstream;
public interface NvConnectionListener {
void stageStarting(String stage);
void stageComplete(String stage);
void stageFailed(String stage, int errorCode);
void stageFailed(String stage, int portFlags, int errorCode);
void connectionStarted();
void connectionTerminated(int errorCode);

View File

@@ -1,20 +1,26 @@
package com.limelight.nvstream.http;
import android.os.Build;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
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;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
@@ -24,11 +30,16 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
@@ -42,7 +53,6 @@ import com.limelight.nvstream.ConnectionContext;
import com.limelight.nvstream.http.PairingManager.PairState;
import okhttp3.ConnectionPool;
import okhttp3.Handshake;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@@ -66,34 +76,36 @@ public class NvHTTP {
private OkHttpClient httpClient;
private OkHttpClient httpClientWithReadTimeout;
private X509TrustManager defaultTrustManager;
private X509TrustManager trustManager;
private X509KeyManager keyManager;
private X509Certificate serverCert;
void setServerCert(X509Certificate serverCert) {
this.serverCert = serverCert;
trustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
throw new IllegalStateException("Should never be called");
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
// Check the server certificate if we've paired to this host
if (!certs[0].equals(NvHTTP.this.serverCert)) {
throw new CertificateException("Certificate mismatch");
}
}
};
}
private void initializeHttpState(final X509Certificate serverCert, final LimelightCryptoProvider cryptoProvider) {
// Set up TrustManager
setServerCert(serverCert);
private static X509TrustManager getDefaultTrustManager() {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
throw new IllegalStateException("No X509 trust manager found");
}
private void initializeHttpState(final LimelightCryptoProvider cryptoProvider) {
keyManager = new X509KeyManager() {
public String chooseClientAlias(String[] keyTypes,
Principal[] issuers, Socket socket) { return "Limelight-RSA"; }
@@ -109,9 +121,51 @@ public class NvHTTP {
public String[] getServerAliases(String keyType, Principal[] issuers) { return null; }
};
// Ignore differences between given hostname and certificate hostname
defaultTrustManager = getDefaultTrustManager();
trustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
throw new IllegalStateException("Should never be called");
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
try {
// Try the default trust manager first to allow pairing with certificates
// that chain up to a trusted root CA. This will raise CertificateException
// if the certificate is not trusted (expected for GFE's self-signed certs).
defaultTrustManager.checkServerTrusted(certs, authType);
} catch (CertificateException e) {
// Check the server certificate if we've paired to this host
if (certs.length == 1 && NvHTTP.this.serverCert != null) {
if (!certs[0].equals(NvHTTP.this.serverCert)) {
throw new CertificateException("Certificate mismatch");
}
}
else {
// The cert chain doesn't look like a self-signed cert or we don't have
// a certificate pinned, so re-throw the original validation error.
throw e;
}
}
}
};
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) { return true; }
public boolean verify(String hostname, SSLSession session) {
try {
Certificate[] certificates = session.getPeerCertificates();
if (certificates.length == 1 && certificates[0].equals(NvHTTP.this.serverCert)) {
// Allow any hostname if it's our pinned cert
return true;
}
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
}
// Fall back to default HostnameVerifier for validating CA-issued certs
return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);
}
};
httpClient = new OkHttpClient.Builder()
@@ -131,7 +185,9 @@ public class NvHTTP {
// started by other Moonlight clients.
this.uniqueId = "0123456789ABCDEF";
initializeHttpState(serverCert, cryptoProvider);
this.serverCert = serverCert;
initializeHttpState(cryptoProvider);
try {
// The URI constructor takes care of escaping IPv6 literals
@@ -270,11 +326,7 @@ public class NvHTTP {
// This has some extra logic to always report unpaired if the pinned cert isn't there
details.pairState = getPairState(serverInfo);
try {
details.runningGameId = getCurrentGame(serverInfo);
} catch (NumberFormatException e) {
details.runningGameId = 0;
}
details.runningGameId = getCurrentGame(serverInfo);
// We could reach it so it's online
details.state = ComputerDetails.State.ONLINE;
@@ -290,24 +342,21 @@ public class NvHTTP {
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(new KeyManager[] { keyManager }, new TrustManager[] { trustManager }, new SecureRandom());
return client.newBuilder().sslSocketFactory(sc.getSocketFactory(), trustManager).build();
// TLS 1.2 is not enabled by default prior to Android 5.0, so we'll need a custom
// SSLSocketFactory in order to connect to GFE 3.20.4 which requires TLSv1.2 or later.
// We don't just always use TLSv12SocketFactory because explicitly specifying TLS versions
// prevents later TLS versions from being negotiated even if client and server otherwise
// support them.
return client.newBuilder().sslSocketFactory(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
sc.getSocketFactory() : new TLSv12SocketFactory(sc),
trustManager).build();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}
}
public X509Certificate getCertificateIfTrusted() {
try {
Response resp = httpClient.newCall(new Request.Builder().url(baseUrlHttps).get().build()).execute();
Handshake handshake = resp.handshake();
if (handshake != null) {
return (X509Certificate)handshake.peerCertificates().get(0);
}
} catch (IOException ignored) {}
return null;
}
// 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
@@ -316,10 +365,6 @@ public class NvHTTP {
Request request = new Request.Builder().url(url).get().build();
Response response;
if (serverCert == null && !url.startsWith(baseUrlHttp)) {
throw new IllegalStateException("Attempted HTTPS fetch without pinned cert");
}
if (enableReadTimeout) {
response = performAndroidTlsHack(httpClientWithReadTimeout).newCall(request).execute();
}
@@ -379,11 +424,6 @@ public class NvHTTP {
}
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
// If we don't have a server cert, we can't be paired even if the host thinks we are
if (serverCert == null) {
return PairState.NOT_PAIRED;
}
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
return PairState.NOT_PAIRED;
}
@@ -394,11 +434,7 @@ public class NvHTTP {
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
if (str != null) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return 0;
}
return Long.parseLong(str);
} else {
return 0;
}
@@ -407,11 +443,7 @@ public class NvHTTP {
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
if (str != null) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return 0;
}
return Long.parseLong(str);
} else {
return 0;
}
@@ -428,11 +460,7 @@ public class NvHTTP {
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
if (str != null) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return 0;
}
return Long.parseLong(str);
} else {
return 0;
}
@@ -588,36 +616,23 @@ public class NvHTTP {
}
public int getServerMajorVersion(String serverInfo) throws XmlPullParserException, IOException {
int[] appVersionQuad = getServerAppVersionQuad(serverInfo);
if (appVersionQuad != null) {
return appVersionQuad[0];
}
else {
return 0;
}
return getServerAppVersionQuad(serverInfo)[0];
}
public int[] getServerAppVersionQuad(String serverInfo) throws XmlPullParserException, IOException {
try {
String serverVersion = getServerVersion(serverInfo);
if (serverVersion == null) {
LimeLog.warning("Missing server version field");
return null;
}
String[] serverVersionSplit = serverVersion.split("\\.");
if (serverVersionSplit.length != 4) {
LimeLog.warning("Malformed server version field");
return null;
}
int[] ret = new int[serverVersionSplit.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = Integer.parseInt(serverVersionSplit[i]);
}
return ret;
} catch (NumberFormatException e) {
e.printStackTrace();
return null;
String serverVersion = getServerVersion(serverInfo);
if (serverVersion == null) {
throw new IllegalArgumentException("Missing server version field");
}
String[] serverVersionSplit = serverVersion.split("\\.");
if (serverVersionSplit.length != 4) {
throw new IllegalArgumentException("Malformed server version field: "+serverVersion);
}
int[] ret = new int[serverVersionSplit.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = Integer.parseInt(serverVersionSplit[i]);
}
return ret;
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
@@ -695,4 +710,62 @@ public class NvHTTP {
return true;
}
// Based on example code from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
private static class TLSv12SocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSv12SocketFactory(SSLContext context) {
internalSSLSocketFactory = context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSv12OnSocket(Socket socket) {
if (socket instanceof SSLSocket) {
// TLS 1.2 is not enabled by default prior to Android 5.0. We must enable it
// explicitly to ensure we can communicate with GFE 3.20.4 which blocks TLS 1.0.
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
}
return socket;
}
}
}

View File

@@ -14,7 +14,6 @@ import java.security.*;
import java.security.cert.*;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
public class PairingManager {
@@ -55,6 +54,10 @@ public class PairingManager {
private static byte[] hexToBytes(String s) {
int len = s.length();
if (len % 2 != 0) {
throw new IllegalArgumentException("Illegal string length: "+len);
}
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
@@ -74,7 +77,7 @@ public class PairingManager {
return (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certBytes));
} catch (CertificateException e) {
e.printStackTrace();
return null;
throw new RuntimeException(e);
}
}
else {
@@ -168,7 +171,7 @@ public class PairingManager {
}
public static String generatePinString() {
Random r = new Random();
SecureRandom r = new SecureRandom();
return String.format((Locale)null, "%d%d%d%d",
r.nextInt(10), r.nextInt(10),
r.nextInt(10), r.nextInt(10));
@@ -314,9 +317,8 @@ public class PairingManager {
return md.digest(data);
}
catch (NoSuchAlgorithmException e) {
// Shouldn't ever happen
e.printStackTrace();
return null;
throw new RuntimeException(e);
}
}
}
@@ -332,9 +334,8 @@ public class PairingManager {
return md.digest(data);
}
catch (NoSuchAlgorithmException e) {
// Shouldn't ever happen
e.printStackTrace();
return null;
throw new RuntimeException(e);
}
}
}

View File

@@ -35,6 +35,26 @@ public class MoonBridge {
public static final int ML_ERROR_GRACEFUL_TERMINATION = 0;
public static final int ML_ERROR_NO_VIDEO_TRAFFIC = -100;
public static final int ML_ERROR_NO_VIDEO_FRAME = -101;
public static final int ML_PORT_INDEX_TCP_47984 = 0;
public static final int ML_PORT_INDEX_TCP_47989 = 1;
public static final int ML_PORT_INDEX_TCP_48010 = 2;
public static final int ML_PORT_INDEX_UDP_47998 = 8;
public static final int ML_PORT_INDEX_UDP_47999 = 9;
public static final int ML_PORT_INDEX_UDP_48000 = 10;
public static final int ML_PORT_INDEX_UDP_48010 = 11;
public static final int ML_PORT_FLAG_ALL = 0xFFFFFFFF;
public static final int ML_PORT_FLAG_TCP_47984 = 0x0001;
public static final int ML_PORT_FLAG_TCP_47989 = 0x0002;
public static final int ML_PORT_FLAG_TCP_48010 = 0x0004;
public static final int ML_PORT_FLAG_UDP_47998 = 0x0100;
public static final int ML_PORT_FLAG_UDP_47999 = 0x0200;
public static final int ML_PORT_FLAG_UDP_48000 = 0x0400;
public static final int ML_PORT_FLAG_UDP_48010 = 0x0800;
public static final int ML_TEST_RESULT_INCONCLUSIVE = 0xFFFFFFFF;
private static AudioRenderer audioRenderer;
private static VideoDecoderRenderer videoRenderer;
@@ -186,7 +206,7 @@ public class MoonBridge {
public static void bridgeClStageFailed(int stage, int errorCode) {
if (connectionListener != null) {
connectionListener.stageFailed(getStageName(stage), errorCode);
connectionListener.stageFailed(getStageName(stage), getPortFlagsFromStage(stage), errorCode);
}
}
@@ -271,5 +291,15 @@ public class MoonBridge {
public static native int getPendingVideoFrames();
public static native int testClientConnectivity(String testServerHostName, int referencePort, int testFlags);
public static native int getPortFromPortFlagIndex(int portFlagIndex);
public static native String getProtocolFromPortFlagIndex(int portFlagIndex);
public static native int getPortFlagsFromStage(int stage);
public static native int getPortFlagsFromTerminationErrorCode(int errorCode);
public static native void init();
}

View File

@@ -11,8 +11,9 @@ import com.limelight.nvstream.http.ComputerDetails;
public class WakeOnLanSender {
private static final int[] PORTS_TO_TRY = new int[] {
7, 9, // Standard WOL ports
47998, 47999, 48000, 48002, 48010 // Ports opened by GFE
9, // Standard WOL port (privileged port)
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
};
public static void sendWolPacket(ComputerDetails computer) throws IOException {

View File

@@ -15,7 +15,9 @@ import com.limelight.computers.ComputerManagerService;
import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.jni.MoonBridge;
import com.limelight.utils.Dialog;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
@@ -97,6 +99,7 @@ public class AddComputerManually extends Activity {
private void doAddPc(String host) {
boolean wrongSiteLocal = false;
boolean success;
int portTestResult;
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
getResources().getString(R.string.msg_add_pc), false);
@@ -104,12 +107,6 @@ public class AddComputerManually extends Activity {
try {
ComputerDetails details = new ComputerDetails();
details.manualAddress = host;
try {
NvHTTP http = new NvHTTP(host, managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(this));
details.serverCert = http.getCertificateIfTrusted();
} catch (IOException ignored) {}
success = managerBinder.addComputerBlocking(details);
} catch (IllegalArgumentException e) {
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
@@ -120,6 +117,14 @@ public class AddComputerManually extends Activity {
if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
}
if (!success && !wrongSiteLocal) {
// Run the test before dismissing the spinner because it can take a few seconds.
portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443,
MoonBridge.ML_PORT_FLAG_TCP_47984 | MoonBridge.ML_PORT_FLAG_TCP_47989);
} else {
// Don't bother with the test if we succeeded or the IP address was bogus
portTestResult = MoonBridge.ML_TEST_RESULT_INCONCLUSIVE;
}
dialog.dismiss();
@@ -127,7 +132,14 @@ public class AddComputerManually extends Activity {
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_wrong_sitelocal), false);
}
else if (!success) {
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_fail), false);
String dialogText;
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
dialogText = getResources().getString(R.string.nettest_text_blocked);
}
else {
dialogText = getResources().getString(R.string.addpc_fail);
}
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), dialogText, false);
}
else {
AddComputerManually.this.runOnUiThread(new Runnable() {

View File

@@ -14,6 +14,8 @@ import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.util.Locale;
// Based on a Stack Overflow example: http://stackoverflow.com/questions/1974193/slider-on-my-preferencescreen
public class SeekBarPreference extends DialogPreference
{
@@ -30,6 +32,8 @@ public class SeekBarPreference extends DialogPreference
private final int maxValue;
private final int minValue;
private final int stepSize;
private final int keyStepSize;
private final int divisor;
private int currentValue;
public SeekBarPreference(Context context, AttributeSet attrs) {
@@ -59,6 +63,8 @@ public class SeekBarPreference extends DialogPreference
maxValue = attrs.getAttributeIntValue(ANDROID_SCHEMA_URL, "max", 100);
minValue = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "min", 1);
stepSize = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "step", 1);
divisor = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "divisor", 1);
keyStepSize = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "keyStep", 0);
}
@Override
@@ -101,7 +107,14 @@ public class SeekBarPreference extends DialogPreference
return;
}
String t = String.valueOf(value);
String t;
if (divisor != 1) {
float floatValue = roundedValue / (float)divisor;
t = String.format((Locale)null, "%.1f", floatValue);
}
else {
t = String.valueOf(value);
}
valueText.setText(suffix == null ? t : t.concat(suffix.length() > 1 ? " "+suffix : suffix));
}
@@ -119,6 +132,9 @@ public class SeekBarPreference extends DialogPreference
}
seekBar.setMax(maxValue);
if (keyStepSize != 0) {
seekBar.setKeyProgressIncrement(keyStepSize);
}
seekBar.setProgress(currentValue);
return layout;
@@ -128,6 +144,9 @@ public class SeekBarPreference extends DialogPreference
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
seekBar.setMax(maxValue);
if (keyStepSize != 0) {
seekBar.setKeyProgressIncrement(keyStepSize);
}
seekBar.setProgress(currentValue);
}

View File

@@ -6,6 +6,7 @@ import android.widget.Toast;
import com.limelight.AppView;
import com.limelight.Game;
import com.limelight.PcView;
import com.limelight.R;
import com.limelight.ShortcutTrampoline;
import com.limelight.binding.PlatformBinding;
@@ -14,6 +15,7 @@ import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.jni.MoonBridge;
import org.xmlpull.v1.XmlPullParserException;
@@ -23,6 +25,8 @@ import java.net.UnknownHostException;
import java.security.cert.CertificateEncodingException;
public class ServerHelper {
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
return computer.activeAddress;
}
@@ -76,6 +80,42 @@ public class ServerHelper {
parent.startActivity(createStartIntent(parent, app, computer, managerBinder));
}
public static void doNetworkTest(final Activity parent) {
new Thread(new Runnable() {
@Override
public void run() {
SpinnerDialog spinnerDialog = SpinnerDialog.displayDialog(parent,
parent.getResources().getString(R.string.nettest_title_waiting),
parent.getResources().getString(R.string.nettest_text_waiting),
false);
int ret = MoonBridge.testClientConnectivity(CONNECTION_TEST_SERVER, 443, MoonBridge.ML_PORT_FLAG_ALL);
spinnerDialog.dismiss();
String dialogSummary;
if (ret == MoonBridge.ML_TEST_RESULT_INCONCLUSIVE) {
dialogSummary = parent.getResources().getString(R.string.nettest_text_inconclusive);
}
else if (ret == 0) {
dialogSummary = parent.getResources().getString(R.string.nettest_text_success);
}
else {
dialogSummary = parent.getResources().getString(R.string.nettest_text_failure);
for (int i = 0; i < 32; i++) {
if ((ret & (1 << i)) != 0) {
dialogSummary += MoonBridge.getProtocolFromPortFlagIndex(i) + " " + MoonBridge.getPortFromPortFlagIndex(i) + "\n";
}
}
}
Dialog.displayDialog(parent,
parent.getResources().getString(R.string.nettest_title_done),
dialogSummary,
false);
}
}).start();
}
public static void doQuit(final Activity parent,
final ComputerDetails computer,
final NvApp app,

View File

@@ -11,6 +11,7 @@ LOCAL_MODULE := moonlight-core
LOCAL_SRC_FILES := moonlight-common-c/src/AudioStream.c \
moonlight-common-c/src/ByteBuffer.c \
moonlight-common-c/src/Connection.c \
moonlight-common-c/src/ConnectionTester.c \
moonlight-common-c/src/ControlStream.c \
moonlight-common-c/src/FakeCallbacks.c \
moonlight-common-c/src/InputStream.c \

View File

@@ -157,7 +157,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
decodeUnit->frameNumber, decodeUnit->receiveTimeMs);
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
@@ -178,7 +178,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
decodeUnit->frameNumber,
decodeUnit->receiveTimeMs);
(jlong)decodeUnit->receiveTimeMs);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);

View File

@@ -101,4 +101,37 @@ Java_com_limelight_nvstream_jni_MoonBridge_getPendingAudioDuration(JNIEnv *env,
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPendingVideoFrames(JNIEnv *env, jclass clazz) {
return LiGetPendingVideoFrames();
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_testClientConnectivity(JNIEnv *env, jclass clazz, jstring testServerHostName, jint referencePort, jint testFlags) {
int ret;
const char* testServerHostNameStr = (*env)->GetStringUTFChars(env, testServerHostName, NULL);
ret = LiTestClientConnectivity(testServerHostNameStr, (unsigned short)referencePort, testFlags);
(*env)->ReleaseStringUTFChars(env, testServerHostName, testServerHostNameStr);
return ret;
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFromPortFlagIndex(JNIEnv *env, jclass clazz, jint portFlagIndex) {
return LiGetPortFromPortFlagIndex(portFlagIndex);
}
JNIEXPORT jstring JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getProtocolFromPortFlagIndex(JNIEnv *env, jclass clazz, jint portFlagIndex) {
int protocol = LiGetProtocolFromPortFlagIndex(portFlagIndex);
return (*env)->NewStringUTF(env, protocol == IPPROTO_TCP ? "TCP" : "UDP");
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFlagsFromStage(JNIEnv *env, jclass clazz, jint stage) {
return LiGetPortFlagsFromStage(stage);
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFlagsFromTerminationErrorCode(JNIEnv *env, jclass clazz, jint errorCode) {
return LiGetPortFlagsFromTerminationErrorCode(errorCode);
}

View File

@@ -121,7 +121,6 @@
<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">Kbps</string>
<string name="title_unlock_fps">Alle Bildwiederholungsraten freigeben</string>
<string name="summary_unlock_fps">Streaming mit 90 oder 120 FPS reduziert gegebenfalls die Latenz auf High-End Geräten, für jedoch zu Crashes oder Lag auf Geräten die dies nicht untersützen können.</string>
<string name="title_checkbox_stretch_video">Video auf den ganzen Bildschirm ausdehnen</string>

View File

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

View File

@@ -19,6 +19,15 @@
<string name="pcview_menu_send_wol">Envoyer la requête Wake-On-LAN</string>
<string name="pcview_menu_delete_pc">Supprimer PC</string>
<string name="pcview_menu_details">Voir les détails</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Test de la connexion réseau</string>
<string name="nettest_text_waiting">Moonlight teste votre connexion réseau pour déterminer si NVIDIA GameStream est bloqué.\n\nCela peut prendre quelques secondes.</string>
<string name="nettest_title_done">Test du réseau terminé</string>
<string name="nettest_text_success">Votre réseau ne semble pas bloquer Moonlight. Si vous avez encore de la difficulté à vous connecter, vérifiez les paramètres du pare-feu de votre PC.\n\nSi vous essayez de diffuser sur Internet, installez l\'outil d\'hébergement Internet Moonlight sur votre PC et exécutez le testeur de streaming Internet inclus pour vérifier la connexion Internet de votre PC.</string>
<string name="nettest_text_inconclusive">Le test réseau na pas pu être effectué car aucun des serveurs de test de connexion Moonlight nétait accessible. Vérifiez votre connexion Internet ou réessayez plus tard.</string>
<string name="nettest_text_failure">La connexion réseau actuelle de votre appareil semble bloquer Moonlight. Le streaming sur Internet peut ne pas fonctionner lorsquil est connecté à ce réseau.\n\nLes ports réseau suivants ont été bloqués:\n</string>
<string name="nettest_text_blocked">La connexion réseau actuelle de votre appareil bloque Moonlight. Le streaming sur Internet peut ne pas fonctionner lorsquil est connecté à ce réseau.</string>
<!-- Pair messages -->
<string name="pairing">Appariement…</string>
@@ -122,12 +131,8 @@
<string name="summary_fps_list">Augmenter pour un flux vidéo plus lisse. Diminution pour de meilleures performances sur les périphériques bas de gamme.</string>
<string name="title_seekbar_bitrate">Sélectionnez le bitrate vidéo à obtenir</string>
<string name="summary_seekbar_bitrate">Bitrate inférieur pour réduire la saccade. Augmentez le bitrate pour augmenter la qualité de l\'image.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps">Débloquer tous les taux d\'images possibles</string>
<string name="summary_unlock_fps">La diffusion en continu à 90 ou 120 FPS peut réduire la latence sur les périphériques haut de gamme, mais peut provoquer des retards ou des blocages sur les périphériques qui ne peuvent \pas le prendre en charge</string>
<string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string>
<string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string>
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string>
<string name="title_checkbox_enable_pip">Activer le mode observateur dans l\'image</string>
<string name="summary_checkbox_enable_pip">Permet de visualiser le flux (sans le contrôleur) tout en multitâche</string>
@@ -136,6 +141,8 @@
<string name="summary_audio_config_list">Activer le son surround 5.1 ou 7.1 pour les systèmes home cinéma</string>
<string name="category_input_settings">Paramètres d\'entrée</string>
<string name="title_checkbox_touchscreen_trackpad">Utilisez l\'écran tactile comme trackpad</string>
<string name="summary_checkbox_touchscreen_trackpad">S\'il est activé, l\'écran tactile agit comme un trackpad. S\'il est désactivé, l\'écran tactile contrôle directement le curseur de la souris.</string>
<string name="title_checkbox_multi_controller">Prise en charge de plusieurs contrôleurs</string>
<string name="summary_checkbox_multi_controller">Lorsqu\'elle n\'est pas cochée, tous les contrôleurs sont regroupés</string>
<string name="title_checkbox_vibrate_fallback">Emuler support vibration de secours</string>
@@ -150,6 +157,8 @@
<string name="summary_checkbox_mouse_emulation">Appuyez longuement sur le bouton Start pour faire basculer la manette de jeu en mode souris.</string>
<string name="title_checkbox_mouse_nav_buttons">Activer les boutons de la souris arrière et avant</string>
<string name="summary_checkbox_mouse_nav_buttons">L\' activation de cette option peut entraîner un clic droit sur certains périphériques.</string>
<string name="title_checkbox_flip_face_buttons">Boutons de face inversé</string>
<string name="summary_checkbox_flip_face_buttons">Commute les boutons de face A/B et X/Y pour les manettes de jeu et les commandes à l\'écran</string>
<string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string>
<string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string>
@@ -181,7 +190,10 @@
<string name="summary_checkbox_host_audio">Lire l\'audio de l\'ordinateur et de ce périphérique</string>
<string name="category_advanced_settings">Réglages avancés</string>
<string name="title_disable_frame_drop">Désactiver la suppression d\'image</string>
<string name="title_unlock_fps">Débloquez toutes les fréquences d\'images possibles</string>
<string name="summary_unlock_fps">Le streaming à 90 ou 120 FPS peut réduire la latence sur les appareils haut de gamme, mais peut entraîner un décalage ou une instabilité sur les appareils qui ne peuvent pas le prendre en charge</string>
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant la diffusion</string>
<string name="title_disable_frame_drop">Ne jamais laisser tomber les frames</string>
<string name="summary_disable_frame_drop">Peut réduire les micro-saccades sur certains appareils, mais peut augmenter la latence</string>
<string name="title_video_format">Modifier les paramètres H.265</string>
<string name="summary_video_format">H.265 réduit les besoins en bande passante vidéo mais nécessite un périphérique très récent</string>
@@ -189,5 +201,6 @@
<string name="summary_enable_hdr">Diffuser du HDR lorsque le jeu et le processeur graphique du PC le prennent en charge. HDR nécessite un GPU série GTX 1000 ou une version ultérieure.</string>
<string name="title_enable_perf_overlay">Activer la superposition de performance</string>
<string name="summary_enable_perf_overlay">Afficher une superposition à l\'écran avec des informations de performance en temps réel pendant la lecture en continu</string>
<string name="title_enable_post_stream_toast">Afficher le message de latence après la diffusion en continu</string>
<string name="summary_enable_post_stream_toast">Afficher un message dinformations de latence après la fin du flux</string>
</resources>

View File

@@ -105,7 +105,6 @@
<string name="summary_resolution_list">Valori troppo elevati possono causare lag o crash</string>
<string name="title_seekbar_bitrate">Velocità di trasmissione video</string>
<string name="summary_seekbar_bitrate">Abbassa la velocità di trasmissione per ridurre lo stuttering; alzala per migliorare la qualità dell\'immagine</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Forza video a schermo intero</string>
<string name="title_checkbox_disable_warnings">Disabilita messaggi di warning</string>
<string name="summary_checkbox_disable_warnings">Disabilita i messaggi di warning sullo schermo durante lo streaming</string>

View File

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

View File

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

View File

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

View File

@@ -120,7 +120,6 @@
<string name="summary_fps_list">Crește-o pentru a îmbunătăți fluiditatea imaginilor. Descrește-o pentru dispozitive neperformante sau conexiune slabă.</string>
<string name="title_seekbar_bitrate">Rata de biți</string>
<string name="summary_seekbar_bitrate">Crește-o pentru a îmbunătăți calitatea imaginilor. Descrește-o pentru dispozitive neperformante sau conexiune slabă.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps">Deblochează toate ratele de cadre posibile</string>
<string name="summary_unlock_fps">Fluxul video de rate mai mari poate reduce latența folosind dispozitive performante, dar poate introduce erori daca nu sunt suportate.</string>
<string name="title_checkbox_stretch_video">Întindeți video pe ecranul complet</string>

View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="audio_config_names">
<item>Стерео</item>
<item>5.1 Объёмный звук</item>
<item>7.1 Объёмный звук</item>
</string-array>
<string-array name="decoder_names">
<item>Автоматический выбор декодера</item>
<item>Принудительное программное декодирование</item>

View File

@@ -8,6 +8,14 @@
<string name="pcview_menu_send_wol">Отправить Wake-On-LAN запрос</string>
<string name="pcview_menu_delete_pc">Удалить PC</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Тестирование Сетевого Подключения</string>
<string name="nettest_text_waiting">Moonlight тестирует ваше сетевое подключение, чтобы определить, заблокирован ли NVIDIA GameStream.\n\nЭто может занять некоторое время…</string>
<string name="nettest_title_done">Тестирование Подключения Завершено</string>
<string name="nettest_text_success">Ваша сеть не блокирует Moonlight. Если у вас всё ещё есть проблемы с соединением, проверьте настройки брандмауэра на компьютере.\n\nЕсли вы пытаетесь транслировать через интернет, установите Moonlight Internet Hosting Tool на ваш компьютер и запустите установленный Internet Streaming Tester, чтобы проверить Интернет-соединение на вашем компьютере.</string>
<string name="nettest_text_inconclusive">Тестирование подключения не может быть выполнено, потому что сервера тестирования подключения Moonlight не доступны. Проверьте ваше Интернет-соединение или повторите попытку позже.</string>
<string name="nettest_text_failure">Похоже, что текущее сетевое подключение вашего устройства блокирует Moonlight. Трансляция через Интернет может не работать при подключении к этой сети.\n\nСледующие сетевые порты были заблокированы:\n</string>
<!-- Pair messages -->
<string name="pairing">Создание пары…</string>
<string name="pair_pc_offline">Компьютер выключен или находится не в сети</string>
@@ -90,12 +98,13 @@
<string name="summary_resolution_list">Выбор слишком высокого значеня для своего устройства может вызвать тормоза или вылеты</string>
<string name="title_seekbar_bitrate">Выберите битрейт видео</string>
<string name="summary_seekbar_bitrate">Низкий битрейт уменьшит зависания. Увеличение битрейта улучшит качество изображения.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Растягивать видео на весь экран</string>
<string name="title_checkbox_disable_warnings">Отключить сообщения с предупреждениями</string>
<string name="summary_checkbox_disable_warnings">Выключить экранные предупреждения о соединении во время трансляции</string>
<string name="category_audio_settings">Аудио Настройки</string>
<string name="title_audio_config_list">Настройка объёмного звука</string>
<string name="summary_audio_config_list">Включить объёмный звук 5.1 или 7.1 для систем домашнего кинотеатра</string>
<string name="title_checkbox_multi_controller">Поддержка нескольких контроллеров</string>
<string name="summary_checkbox_multi_controller">Когда отключена, все контроллеры определяются как один</string>
@@ -103,6 +112,8 @@
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Драйвер контроллеров Xbox 360/One</string>
<string name="summary_checkbox_xb1_driver">Включить встроенный USB драйвер для устройств без собственной поддержки контроллеров Xbox</string>
<string name="title_checkbox_flip_face_buttons">Перевернуть кнопки A/B и X/Y</string>
<string name="summary_checkbox_flip_face_buttons">Меняет местами кнопки A/B и X/Y на геймпадах и экранных кнопках</string>
<string name="category_ui_settings">Настройки Интерфейса</string>
<string name="title_language_list">Язык</string>
@@ -124,6 +135,8 @@
<string name="summary_checkbox_show_onscreen_controls">Отображать оверлей виртуального контроллера на сенсорном экране</string>
<string name="title_only_l3r3">Показывать только L3 и R3</string>
<string name="summary_only_l3r3">Скрывать все экранные кнопки кроме L3 и R3</string>
<string name="summary_osc_opacity">Сделать экранные кнопки более/менее прозрачными</string>
<string name="dialog_title_osc_opacity">Изменить прозрачность</string>
<string name="scut_deleted_pc">PC удален</string>
<string name="scut_not_paired">PC не сопряжен</string>
<string name="help_loading_title">Просмотр Помощи</string>
@@ -152,12 +165,16 @@
<string name="summary_disable_frame_drop">Может уменьшить микрозависания на некоторых устройствах, но также увеличить задержку</string>
<string name="title_enable_hdr">Включить HDR (Экспериментально)</string>
<string name="summary_enable_hdr">Транслировать в HDR если игра и GPU компьютера поддерживают это. HDR требует видеокарты GTX 1000 серии или более новой.</string>
<string name="title_enable_post_stream_toast">Показывать отчёт о задержке после трансляции</string>
<string name="summary_enable_post_stream_toast">Отобразить сообщение с информацией о задержке после окончания трансляции.</string>
<string name="title_checkbox_vibrate_osc">Включить вибрацию</string>
<string name="title_fps_list">Частота кадров</string>
<string name="applist_menu_details">Детали</string>
<string name="applist_menu_scut">Создать ярлык</string>
<string name="category_input_settings">Настройки ввода</string>
<string name="title_checkbox_touchscreen_trackpad">Использовать сенсор как тачпад</string>
<string name="summary_checkbox_touchscreen_trackpad">Если включено, сенсор выступает в роли тачпада. Если отключено, сенсор напрямую контролирует курсор мыши.</string>
<string name="delete_pc_msg">Вы уверены что хотите удалить этот PC?</string>
<string name="pcview_menu_details">Детали</string>
<string name="poor_connection_msg">Слабое соединение с PC</string>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="decoder_names">
<item>Автоматичний вибір декодера</item>
<item>Примусове програмне декодування</item>
<item>Примусове апаратне декодування</item>
</string-array>
<string-array name="video_format_names">
<item>Використовувати H.265 тільки якщо безпечно</item>
<item>Завжди використовувати H.265 якщо доступно</item>
<item>Ніколи не використовувати H.265</item>
</string-array>
</resources>

View File

@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Подивитися список ігор</string>
<string name="pcview_menu_pair_pc">Створити пару з ПК</string>
<string name="pcview_menu_unpair_pc">Розірвати пару</string>
<string name="pcview_menu_send_wol">Надіслати Wake-On-LAN запит</string>
<string name="pcview_menu_delete_pc">Видалити ПК</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="pair_pairing_msg">Будь ласка, введіть цей PIN на ПК:</string>
<string name="pair_incorrect_pin">Неправильний PIN</string>
<string name="pair_fail">Створення пари не вдалося</string>
<!-- WOL messages -->
<string name="wol_pc_online">Комп\'ютер в мережі</string>
<string name="wol_no_mac">Неможливо розбудити ПК бо GFE не відправили MAC адреса</string>
<string name="wol_waking_pc">Пробудження ПК…</string>
<string name="wol_waking_msg">Пробудження ПК може зайняти кілька секунд.
Якщо цього не відбувається, упевніться що Wake-On-LAN налаштований правильно.
</string>
<string name="wol_fail">Помилка при відправці Wake-On-LAN пакетів</string>
<!-- Unpair messages -->
<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_manager_not_running">Сервіс ComputerManager не запущено. Будь ласка, зачекайте кілька секунд або запустіть програму.</string>
<string name="error_unknown_host">Неможливо знайти хост</string>
<string name="error_404">GFE повернув помилку HTTP 404. Переконайтеся що Ваш ПК використовує підтримуваний GPU.
Використання програм для віддаленого доступу також може викликати цю помилку. Спробуйте перезавантажити комп\'ютер або перевстановити GFE.
</string>
<!-- Start application messages -->
<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_error_title">Помилка з\'єднання</string>
<string name="conn_error_msg">Запуск не вдався</string>
<string name="conn_terminated_title">З\'єднання припинено</string>
<string name="conn_terminated_msg">Підключення було перервано</string>
<!-- General strings -->
<string name="ip_hint">IP-адреса комп\'ютера з GeForce</string>
<string name="searching_pc">Пошук комп\'ютерів із запущеним GameStream…\n\n
Переконайтеся що GameStream увімкнений в налаштуваннях GeForce Experience в розділі SHIELD.</string>
<string name="yes">Так</string>
<string name="no">Ні</string>
<string name="lost_connection">З\'єднання втрачено з ПК</string>
<!-- AppList activity -->
<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_tv_channel">Додати на канал</string>
<string name="applist_refresh_title">Список додатків</string>
<string name="applist_refresh_msg">Оновлення додатків…</string>
<string name="applist_refresh_error_title">Помилка</string>
<string name="applist_refresh_error_msg">Помилка при отриманні списку додатків</string>
<string name="applist_quit_app">Вихід з</string>
<string name="applist_quit_success">Вихід відбувся успішно з</string>
<string name="applist_quit_fail">Помилка при виході</string>
<string name="applist_quit_confirmation">Ви впевнені, що хочете вийти з запущеного додатку? Усі незбережені дані будуть втрачені.</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Додавання ПК вручну</string>
<string name="msg_add_pc">З\'єднання з ПК…</string>
<string name="addpc_fail">Неможливо підключитися до вибраного комп\'ютера. Переконайтеся, що необхідні порти дозволені в налаштуваннях брандмауера.</string>
<string name="addpc_success">Комп\'ютер доданий успішно</string>
<string name="addpc_unknown_host">Неможливо знайти ПК за вказаною адресою. Переконайтеся, що Ви не зробили помилок під час його написання.</string>
<string name="addpc_enter_ip">Ви повинні ввести IP адресу</string>
<!-- Preferences -->
<string name="category_basic_settings">Загальні Налаштування</string>
<string name="title_resolution_list">Виберіть розширення і частоту кадрів</string>
<string name="summary_resolution_list">Вибір занадто високого значеня для свого пристрою може викликати гальма або вильоти</string>
<string name="title_seekbar_bitrate">Виберіть бітрейт відео</string>
<string name="summary_seekbar_bitrate">Низький бітрейт зменшить зависання. Збільшення бітрейта поліпшить якість зображення.</string>
<string name="title_checkbox_stretch_video">Розтягувати відео на весь екран</string>
<string name="title_checkbox_disable_warnings">Відключити повідомлення з попередженнями</string>
<string name="summary_checkbox_disable_warnings">Вимкнути екранні попередження про з\'єднання під час трансляції</string>
<string name="category_audio_settings">Аудіо Налаштування</string>
<string name="title_audio_config_list">Конфігурація об\'ємного звуку</string>
<string name="summary_audio_config_list">Увімкнути 5.1 або 7.1 об\'ємний звук для систем домашнього кінотеатру</string>
<string name="title_checkbox_multi_controller">Підтримка декількох контролерів</string>
<string name="summary_checkbox_multi_controller">Коли вимкнено, всі контролери визначаються як один</string>
<string name="title_seekbar_deadzone">Регулювати мертву зону аналогового стіку</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Драйвер контролерів Xbox 360/One</string>
<string name="summary_checkbox_xb1_driver">Увімкнути вбудований USB драйвер для пристроїв без власної підтримки контролерів Xbox</string>
<string name="category_ui_settings">Налаштування інтерфейсу</string>
<string name="title_language_list">Мова</string>
<string name="summary_language_list">Мова, яка буде використовуватися в Moonlight</string>
<string name="title_checkbox_small_icon_mode">Використовувати маленькі іконки</string>
<string name="summary_checkbox_small_icon_mode">Використовувати маленькі іконки в сітці для відображення більшої кількості елементів на екрані</string>
<string name="category_host_settings">Налаштування Хоста</string>
<string name="title_checkbox_enable_sops">Оптимізувати ігрові налаштування</string>
<string name="summary_checkbox_enable_sops">Дозволити GFE змінювати налаштування ігор для оптимальної трансляції</string>
<string name="title_checkbox_host_audio">Програвати звук на ПК</string>
<string name="summary_checkbox_host_audio">Програвати звук на комп\'ютері і поточному пристрої</string>
<string name="category_advanced_settings">Розширені Налаштування</string>
<string name="title_video_format">Змінити налаштування H.265</string>
<string name="summary_video_format">H.265 знижує вимоги до пропускної здатності, але вимагає дуже нового пристрою</string>
<string name="category_on_screen_controls_settings">Налаштування дисплею з кнопками</string>
<string name="title_checkbox_show_onscreen_controls">Показувати екранні кнопки</string>
<string name="summary_checkbox_show_onscreen_controls">Відображати оверлей віртуального контролера на сенсорному екрані</string>
<string name="title_only_l3r3">Показувати тільки L3 і R3</string>
<string name="summary_only_l3r3">Приховувати всі екранні кнопки крім L3 і R3</string>
<string name="scut_deleted_pc">ПК видалений</string>
<string name="scut_not_paired">ПК не сполучений</string>
<string name="help_loading_title">Перегляд Допомоги</string>
<string name="help_loading_msg">Завантаження сторінки допомоги…</string>
<string name="pair_already_in_progress">Сполучення вже в процесі</string>
<string name="help">Допомога</string>
<string name="applist_connect_msg">Підключення до ПК…</string>
<string name="title_decoding_error">Збій відео декодера</string>
<string name="message_decoding_error">Стався збій Moonlight через проблеми з відео декодером даного пристрою. Спробуйте змінити налаштування трансляції якщо збої будуть продовжуватися.</string>
<string name="title_decoding_reset">Відео Налаштування Скинуті</string>
<string name="message_decoding_reset">Відео декодер Вашого пристрою давав збої з вибраними налаштуваннями. Налаштування трансляції були скинуті до значень за замовчуванням.</string>
<string name="error_usb_prohibited">USB доступ заборонений адміністратором пристрою. Перевірте налаштування Knox або MDM.</string>
<string name="addpc_wrong_sitelocal">Адреса вказана невірно. Ви повинні ввести публічну IP-адресу Вашого роутера для передачі через інтернет.</string>
<string name="title_checkbox_enable_pip">Увімкнути перегляд у режимі \"Картинка в картинці\"</string>
<string name="summary_checkbox_enable_pip">Дозволяє переглядати трансляцію (але не керувати нею) під час роботи в інших додатках</string>
<string name="title_checkbox_usb_bind_all">Відхилити підтримку контролерів Android</string>
<string name="summary_checkbox_usb_bind_all">Змушує USB драйвер Moonlight взяти на себе роботу з усіма підтримуваними Xbox геймпадами</string>
<string name="title_checkbox_mouse_emulation">Емуляція миші на геймпаді</string>
<string name="summary_checkbox_mouse_emulation">Довге натиснення кнопки Start перемкне геймпад в режим миші</string>
<string name="title_reset_osc">Скинути схему розташування екранних кнопок</string>
<string name="summary_reset_osc">Повертає всі екранні елементи керування до їх розташуванням за замовчуванням</string>
<string name="dialog_title_reset_osc">Скинути Схему</string>
<string name="dialog_text_reset_osc">Ви впевнені що хочете видалити збережену схему розташування кнопок?</string>
<string name="toast_reset_osc_success">Екранні елементи керування повернуті до положень за замовчуванням</string>
<string name="title_disable_frame_drop">Ніколи не пропускати кадри</string>
<string name="summary_disable_frame_drop">Може зменшити мікрозависання на деяких пристроях, але також збільшити затримку</string>
<string name="title_enable_hdr">Увімкнути HDR (Експериментально)</string>
<string name="summary_enable_hdr">Транслювати в HDR якщо гра і GPU комп\'ютера підтримують це. HDR вимагає відеокарти GTX 1000 серії або більш нової.</string>
<string name="title_checkbox_vibrate_osc">Увімкнути вібрацію</string>
<string name="title_fps_list">Частота кадрів</string>
<string name="applist_menu_details">Деталі</string>
<string name="applist_menu_scut">Створити ярлик</string>
<string name="category_input_settings">Налаштування введення</string>
<string name="title_checkbox_touchscreen_trackpad">Використовувати сенсорний екран як трекпад</string>
<string name="summary_checkbox_touchscreen_trackpad">Якщо увімкнено, сенсорний екран працює як трекпад. Якщо його вимкнено, сенсорний екран безпосередньо керує курсором миші.</string>
<string name="delete_pc_msg">Ви впевнені що хочете видалити цей ПК?</string>
<string name="pcview_menu_details">Деталі</string>
<string name="poor_connection_msg">Слабке з\'єднання з ПК</string>
<string name="title_details">Деталі</string>
<string name="title_enable_perf_overlay">Увімкнути відображення статистики</string>
<string name="title_unlock_fps">Розблокувати всі можливі частоти оновлення</string>
<string name="applist_details_id">ID додатку:</string>
<string name="title_checkbox_vibrate_fallback">Емуляція вібровіддачі</string>
<string name="summary_checkbox_vibrate_osc">Вібрація пристрою для емуляції вібровіддачі при екранному управлінні</string>
<string name="summary_checkbox_vibrate_fallback">Вібрувати пристрій для емуляції вібровіддачі для геймпадов без підтримки вібрації</string>
<string name="summary_checkbox_mouse_nav_buttons">Включення цієї опції може привести до неправильної роботи правої кнопки миші на деяких пристроях</string>
<string name="title_checkbox_flip_face_buttons">Відкідні кнопки</string>
<string name="summary_checkbox_flip_face_buttons">Перемикає лицьові кнопки A/B і X/Y для геймпадів та екранних елементів керування</string>
<string name="scut_pc_not_found">ПК не знайдено</string>
<string name="unable_to_pin_shortcut">Поточний лаунчер не дозволяє створювати закріплені ярлики</string>
<string name="title_checkbox_mouse_nav_buttons">Увімкнути кнопки вперед і назад для миші</string>
<string name="slow_connection_msg">Повільне підключення до ПК\nЗменшити бітрейт</string>
<string name="summary_unlock_fps">Трансляція зі швидкістю 90 або 120 кадрів в секунду може зменшити затримку на пристроях високого класу, але може викликати затримки або збій на пристроях без підтримки цього функціоналу</string>
<string name="summary_enable_perf_overlay">Відображення накладення на екрані з інформацією про продуктивність під час трансляції в режимі реального часу</string>
<string name="perf_overlay_text">Розширення відео: %1$s\nДекодер: %2$s\nРозрахункова частота кадрів ПК-хоста: %3$.2f FPS\nВхідна частота кадрів з мережі: %4$.2f FPS\nЧастота кадрів під час рендерінгу: %5$.2f FPS\nВідкинутих кадрів вашою мережею: %6$.2f%%\nСередній час отримання: %7$.2f ms\nСередній час декодування: %8$.2f ms</string>
<string name="summary_fps_list">Збільшення для більш плавного відео потоку. Зменшіть для кращої продуктивності на більш слабких пристроях.</string>
<string name="scut_invalid_uuid">Зазначений ПК недійсний</string>
<string name="scut_invalid_app_id">Зазначений додаток недійсне</string>
<string name="title_osc_opacity">Змінити непрозорість екранних елементів керування</string>
<string name="summary_osc_opacity">Зробити екранні елементи керування більш/менш прозорими</string>
<string name="dialog_title_osc_opacity">Зміна непрозорості</string>
<string name="suffix_osc_opacity">%</string>
<string name="title_enable_post_stream_toast">Показувати затримку після трансляції</string>
<string name="summary_enable_post_stream_toast">Вивести інформаційне повідомлення про затримку після закінчення потоку</string>
</resources>

View File

@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_label" translatable="false">Moonlight</string>
<string name="app_label_root" translatable="false">Moonlight Root</string>
<!-- Shortcut strings -->
<string name="scut_deleted_pc"> 电脑已删除 </string>
@@ -124,7 +122,6 @@
<string name="summary_fps_list"> 高帧数提升视频流流畅度。 \n 低帧数提升在低端设备中的串流体验。</string>
<string name="title_seekbar_bitrate"> 视频码率 </string>
<string name="summary_seekbar_bitrate"> 高码率提升图像质量。 \n 低码率提升在较慢网络中的串流体验。 </string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps"> 解锁所有可用帧数 </string>
<string name="summary_unlock_fps"> 以90或120帧串流可能会减少在高端设备上的网络延迟但会在不支持的设备上造成卡顿或崩溃。 </string>
<string name="title_checkbox_stretch_video"> 将画面拉伸至全屏 </string>

View File

@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_label" translatable="false">Moonlight</string>
<string name="app_label_root" translatable="false">Moonlight Root</string>
<!-- Shortcut strings -->
<string name="scut_deleted_pc"> 電腦已刪除 </string>
@@ -124,7 +122,6 @@
<string name="summary_fps_list"> 高幀數提升視頻流流暢度。 \n 低幀數提升在低端設備中的串流體驗。</string>
<string name="title_seekbar_bitrate"> 視頻碼率 </string>
<string name="summary_seekbar_bitrate"> 高碼率提升圖像品質。 \n 低碼率提升在較慢網路中的串流體驗。 </string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps"> 解鎖所有可用幀數 </string>
<string name="summary_unlock_fps"> 以90或120幀串流可能會減少在高端設備上的網路延遲但會在不支援的設備上造成卡頓或崩潰。 </string>
<string name="title_checkbox_stretch_video"> 將畫面拉伸至全屏 </string>

View File

@@ -55,6 +55,7 @@
<item>Français</item>
<item>Deutsch</item>
<item>Română</item>
<item>Українська</item>
</string-array>
<string-array name="language_values" translatable="false">
<item>default</item>
@@ -70,6 +71,7 @@
<item>fr</item>
<item>de</item>
<item>ro</item>
<item>uk</item>
</string-array>
<string-array name="decoder_names">

View File

@@ -15,13 +15,24 @@
<string name="help_loading_msg">Loading help page…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">View Game List</string>
<string name="pcview_menu_app_list">View Apps</string>
<string name="pcview_menu_full_app_list">View Hidden Apps</string>
<string name="pcview_menu_pair_pc">Pair with PC</string>
<string name="pcview_menu_unpair_pc">Unpair</string>
<string name="pcview_menu_send_wol">Send Wake-On-LAN request</string>
<string name="pcview_menu_delete_pc">Delete PC</string>
<string name="pcview_menu_test_network">Test Network Connection</string>
<string name="pcview_menu_details">View Details</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Testing Network Connection</string>
<string name="nettest_text_waiting">Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.\n\nThis may take a few seconds…</string>
<string name="nettest_title_done">Network Test Complete</string>
<string name="nettest_text_success">Your network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC\'s firewall settings.\n\nIf you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your PC and run the included Internet Streaming Tester to check your PC\'s Internet connection.</string>
<string name="nettest_text_inconclusive">The network test could not be performed because none of Moonlight\'s connection testing servers were reachable. Check your Internet connection or try again later.</string>
<string name="nettest_text_failure">Your device\'s current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.\n\nThe following network ports were blocked:\n</string>
<string name="nettest_text_blocked">Your device\'s current network connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</string>
<!-- Pair messages -->
<string name="pairing">Pairing…</string>
<string name="pair_pc_offline">Computer is offline</string>
@@ -62,6 +73,7 @@
<string name="unable_to_pin_shortcut">Your current launcher does not allow for creating pinned shortcuts.</string>
<string name="video_decoder_init_failed">Video decoder failed to initialize. Your device may not support the selected resolution or frame rate.</string>
<string name="no_video_received_error">No video received from host. Check the host PC\'s firewall and port forwarding rules.</string>
<string name="no_frame_received_error">Your network connection isn\'t performing well. Reduce your video bitrate setting or try a faster connection.</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Establishing Connection</string>
@@ -99,6 +111,7 @@
<string name="applist_menu_details">View Details</string>
<string name="applist_menu_scut">Create Shortcut</string>
<string name="applist_menu_tv_channel">Add to Channel</string>
<string name="applist_menu_hide_app">Hide App</string>
<string name="applist_refresh_title">App List</string>
<string name="applist_refresh_msg">Refreshing apps…</string>
<string name="applist_refresh_error_title">Error</string>
@@ -126,7 +139,7 @@
<string name="summary_fps_list">Increase for a smoother video stream. Decrease for better performance on lower end devices.</string>
<string name="title_seekbar_bitrate">Video bitrate</string>
<string name="summary_seekbar_bitrate">Increase for better image quality. Decrease to improve performance on slower connections.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="title_checkbox_stretch_video">Stretch video to full-screen</string>
<string name="category_audio_settings">Audio Settings</string>

View File

@@ -22,10 +22,12 @@
android:key="seekbar_bitrate_kbps"
android:dialogMessage="@string/summary_seekbar_bitrate"
seekbar:min="500"
android:max="150000"
seekbar:step="500"
android:max="100000"
seekbar:keyStep="1000"
seekbar:divisor="1000"
android:summary="@string/summary_seekbar_bitrate"
android:text="@string/suffix_seekbar_bitrate"
android:text="@string/suffix_seekbar_bitrate_mbps"
android:title="@string/title_seekbar_bitrate" />
<CheckBoxPreference
android:key="checkbox_stretch_video"

View File

@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.1.0'
}
}

View File

@@ -0,0 +1,4 @@
- Added Ukrainian translation
- Fixed SSL error connecting to GFE 3.20.4 on Android 4.1-4.4
- Fixed a random crash when starting a stream in bad network conditions
- Fixed decoder-related crashes on Xiaomi Mi 10 Lite 5G devices

View File

@@ -0,0 +1,4 @@
- Added the ability to hide unwanted games via a long press on the game icon
- Added a network test to check if Moonlight's connection is being blocked
- Improved display mode selection logic for better frame rate matching
- Fixed crashes on some Snapdragon 765G devices

View File

@@ -0,0 +1,4 @@
- Hiding games now only takes effect after returning to the app list
- Display a warning if the connection is too unstable to stream
- Increase maximum bitrate to 150 Mbps for Ethernet-connected devices
- Update Russian and French translations

View File

@@ -0,0 +1 @@
- Improved connection reliability

View File

@@ -0,0 +1 @@
- Improved performance on Google TV Chromecast and other newer Amlogic devices by using HEVC by default

View File

@@ -0,0 +1,3 @@
- Gamepad button combos to emulate missing Select and Guide buttons have been reintroduced
- For gamepads without a Select button, LB+Start will trigger the Select button and RB+Start will trigger the Guide button
- For gamepads with a Select button, Start+Select will trigger the Guide button

View File

@@ -0,0 +1 @@
- Enabled raw mouse input on the Nvidia Shield TV to avoid mouse acceleration

View File

@@ -1,6 +1,6 @@
#Thu May 28 11:41:09 PDT 2020
#Mon Oct 12 13:42:18 CDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip