Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0762a6213 | |||
| 04efec101e | |||
| a6c69012cc | |||
| 0045c54d8e | |||
| 45436c006f | |||
| cc183c0da8 | |||
| 523f1df98b | |||
| 5843dff278 | |||
| 7f24f47978 | |||
| b1f9fd459e | |||
| 48988eb785 | |||
| 0045a885b9 | |||
| 0b57f60454 | |||
| f0857c7da2 | |||
| 15faa2e841 | |||
| da103f7197 | |||
| 1d3e42f92e | |||
| 20ced841dd | |||
| 54ebd0a796 | |||
| e636a7171b | |||
| e8f847065b | |||
| 1c806bb572 | |||
| 963133598f | |||
| fedaa74c47 | |||
| e322baf1d7 | |||
| 173a07cb59 | |||
| 364afff860 | |||
| 1b59e61b8e | |||
| b1f453f7ba | |||
| 175e842feb | |||
| d7a9a37a0e | |||
| 836b9240de | |||
| bdac2df4b9 | |||
| 57b507ad50 | |||
| 35201b69f6 | |||
| 0d138c26e9 | |||
| b4a7393dca | |||
| d86092df1a | |||
| b392d7f8e3 | |||
| 7cc7953879 | |||
| 7b26852a1f | |||
| f26b384697 | |||
| ab0531aa76 | |||
| 6873720d81 | |||
| 1e30c4a219 | |||
| 0a0e3ff970 | |||
| 5c42fd86a6 | |||
| 16cc829906 | |||
| 829e7cf33c | |||
| 02bfa90417 | |||
| 0b2466cf26 | |||
| 9d8df04c5c | |||
| 34a1697d50 | |||
| 17cf711c3d | |||
| ce0b19605a | |||
| 35bd9ecda3 | |||
| ca89849dd2 | |||
| ac1cb6d56b | |||
| dfbffea0fc | |||
| 7ae9c993f1 | |||
| 91d739f8d6 | |||
| f0c625d85c | |||
| b5f5e73076 | |||
| 1fb5eff7f1 | |||
| 5116cfd141 | |||
| e53a1f90b0 | |||
| 766c9628b0 | |||
| 6a4abdd74c | |||
| fc8bc5ba1e | |||
| 0fde5d44c0 | |||
| dc6b5a3d49 | |||
| 396522f249 | |||
| 86ab39e4ca | |||
| a4c9cb0e55 | |||
| e6c6feac10 | |||
| ca0aee58ab | |||
| 6391f2c43d | |||
| 32171bb70c | |||
| fd6675a3a3 | |||
| 9d883978a8 | |||
| 1aae65575c | |||
| c5d58e1aab | |||
| 56394471fa | |||
| 4cae6959df | |||
| f02d7b4516 | |||
| f5c83112df | |||
| a413dc81c1 | |||
| c9eddab191 | |||
| ec1268bd71 | |||
| 22eb2b5823 | |||
| 9669da026f | |||
| 7b14e54eab | |||
| 6b30ee4593 | |||
| 17c47a15da | |||
| 8f55517236 | |||
| 41ad086dfa | |||
| e19ef7dcae | |||
| f361265d70 | |||
| ef72e3ef77 | |||
| 770f1a1ca0 | |||
| e8fc91191f | |||
| 105ad3317d | |||
| 22bf4775cd | |||
| 5c6be7969a | |||
| c6e23f4be2 | |||
| 05547c22ec |
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Follow the troubleshooting guide before reporting a bug
|
||||
|
||||
---
|
||||
**READ ME FIRST!**
|
||||
If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting
|
||||
|
||||
If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to reproduce**
|
||||
Any special steps that are required for the bug to appear.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
|
||||
|
||||
**Affected games**
|
||||
List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
|
||||
|
||||
**Other Moonlight clients**
|
||||
- Does the issue occur when using Moonlight on PC or iOS?
|
||||
|
||||
**Moonlight settings (please complete the following information)**
|
||||
- Have any settings been adjusted from defaults?
|
||||
- If so, which settings have been changed?
|
||||
- Does the problem still occur after reverting settings back to default?
|
||||
|
||||
**Gamepad-related issues (please complete if problem is gamepad-related)**
|
||||
- Do you have any gamepads connected to your host PC directly?
|
||||
- If gamepad input is not working, does it work if you use Moonlight's on-screen controls?
|
||||
- Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad?
|
||||
- Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide
|
||||
|
||||
**Device details (please complete the following information)**
|
||||
- Android version: [e.g. Android 10]
|
||||
- Device model: [e.g. Samsung Galaxy S21]
|
||||
|
||||
**Server PC details (please complete the following information)**
|
||||
- OS: [e.g. Windows 10 1809]
|
||||
- GeForce Experience version: [e.g. 3.16.0.140]
|
||||
- Nvidia GPU driver: [e.g. 417.35]
|
||||
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
|
||||
|
||||
**Additional context**
|
||||
Anything else you think may be relevant to the issue or special about your specific setup.
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -3,6 +3,7 @@
|
||||
*.ap_
|
||||
*.aab
|
||||
output.json
|
||||
output-metadata.json
|
||||
out/
|
||||
|
||||
# files for the dex VM
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
language: android
|
||||
dist: trusty
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-30.0.0
|
||||
- android-30
|
||||
|
||||
before_install:
|
||||
- sdkmanager --list
|
||||
|
||||
install:
|
||||
- yes | sdkmanager "ndk;21.0.6113669"
|
||||
@@ -1,6 +1,7 @@
|
||||
# Moonlight Android
|
||||
|
||||
[](https://travis-ci.org/moonlight-stream/moonlight-android)
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
|
||||
[](https://hosted.weblate.org/projects/moonlight/moonlight-android/)
|
||||
|
||||
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||
|
||||
@@ -9,7 +10,7 @@ whether in your own home or over the internet.
|
||||
|
||||
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [iOS/tvOS client](https://github.com/moonlight-stream/moonlight-ios).
|
||||
|
||||
Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed project information, setup guide, or troubleshooting steps.
|
||||
You can follow development on our [Discord server](https://moonlight-stream.org/discord) and help translate Moonlight into your language on [Weblate](https://hosted.weblate.org/projects/moonlight/moonlight-android/).
|
||||
|
||||
## Downloads
|
||||
* [Google Play Store](https://play.google.com/store/apps/details?id=com.limelight)
|
||||
|
||||
+7
-5
@@ -1,14 +1,16 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
ndkVersion "22.0.7026061"
|
||||
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
|
||||
versionName "9.6.4"
|
||||
versionCode = 237
|
||||
versionName "9.8.4"
|
||||
versionCode = 255
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
@@ -114,10 +116,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'
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".PcView"
|
||||
android:exported="true"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<intent-filter>
|
||||
@@ -126,6 +127,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
|
||||
|
||||
@@ -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
|
||||
@@ -355,9 +386,12 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
||||
|
||||
menu.setHeaderTitle(selectedApp.app.getAppName());
|
||||
|
||||
if (lastRunningAppId != 0) {
|
||||
if (lastRunningAppId == selectedApp.app.getAppId()) {
|
||||
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
|
||||
@@ -365,10 +399,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 +420,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 +471,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();
|
||||
@@ -593,6 +643,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
public static class AppObject {
|
||||
public final NvApp app;
|
||||
public boolean isRunning;
|
||||
public boolean isHidden;
|
||||
|
||||
public AppObject(NvApp app) {
|
||||
if (app == null) {
|
||||
|
||||
@@ -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;
|
||||
@@ -597,6 +598,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
// We can't guarantee the state of modifiers keys which may have
|
||||
// lifted while focus was not on us. Clear the modifier state.
|
||||
this.modifierFlags = 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Capture is lost when focus is lost, so it must be requested again
|
||||
// when focus is regained.
|
||||
@@ -616,6 +621,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 +634,61 @@ 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 isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
|
||||
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 && prefConfig.width <= 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,
|
||||
// or it's a native resolution stream.
|
||||
if (prefConfig.width < 3840 && prefConfig.fps <= 60 && !isNativeResolutionStream) {
|
||||
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 +699,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) {
|
||||
@@ -970,8 +1000,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
private static byte getModifierState(KeyEvent event) {
|
||||
byte modifier = 0;
|
||||
// We cannot simply use modifierFlags for all key event processing, because
|
||||
// some IMEs will not generate real key events for pressing Shift. Instead
|
||||
// they will simply send key events with isShiftPressed() returning true,
|
||||
// and we will need to send the modifier flag ourselves.
|
||||
private byte getModifierState(KeyEvent event) {
|
||||
// Start with the global modifier state to ensure we cover the case
|
||||
// detailed in https://github.com/moonlight-stream/moonlight-android/issues/840
|
||||
byte modifier = getModifierState();
|
||||
if (event.isShiftPressed()) {
|
||||
modifier |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
}
|
||||
@@ -1052,7 +1088,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
byte modifiers = getModifierState(event);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_DOWN, modifiers);
|
||||
}
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
|
||||
}
|
||||
@@ -1118,9 +1153,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
|
||||
}
|
||||
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
|
||||
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
|
||||
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_UP, getModifierState(event));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1161,6 +1193,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
{
|
||||
// This case is for mice and non-finger touch devices
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||
event.getSource() == InputDevice.SOURCE_TOUCHPAD ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
(event.getPointerCount() >= 1 &&
|
||||
(event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE ||
|
||||
@@ -1502,7 +1535,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 +1557,18 @@ 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 (portFlags != 0) {
|
||||
dialogText += "\n\n" + getResources().getString(R.string.check_ports_msg) + "\n" +
|
||||
MoonBridge.stringifyPortFlags(portFlags, "\n");
|
||||
}
|
||||
|
||||
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 +1576,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 portFlags = MoonBridge.getPortFlagsFromTerminationErrorCode(errorCode);
|
||||
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER,443, portFlags);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -1548,14 +1600,33 @@ 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;
|
||||
|
||||
case MoonBridge.ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
||||
message = getResources().getString(R.string.early_termination_error);
|
||||
break;
|
||||
|
||||
default:
|
||||
message = getResources().getString(R.string.conn_terminated_msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (portFlags != 0) {
|
||||
message += "\n\n" + getResources().getString(R.string.check_ports_msg) + "\n" +
|
||||
MoonBridge.stringifyPortFlags(portFlags, "\n");
|
||||
}
|
||||
|
||||
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
||||
|
||||
@@ -109,7 +109,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
private final static int APP_LIST_ID = 1;
|
||||
private final static int PAIR_ID = 2;
|
||||
private final static int UNPAIR_ID = 3;
|
||||
private final static int WOL_ID = 4;
|
||||
@@ -117,6 +116,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);
|
||||
@@ -316,15 +317,32 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
|
||||
|
||||
// Add a header with PC status details
|
||||
menu.clearHeader();
|
||||
String headerTitle = computer.details.name + " - ";
|
||||
switch (computer.details.state)
|
||||
{
|
||||
case ONLINE:
|
||||
headerTitle += getResources().getString(R.string.pcview_menu_header_online);
|
||||
break;
|
||||
case OFFLINE:
|
||||
menu.setHeaderIcon(R.drawable.ic_pc_offline);
|
||||
headerTitle += getResources().getString(R.string.pcview_menu_header_offline);
|
||||
break;
|
||||
case UNKNOWN:
|
||||
headerTitle += getResources().getString(R.string.pcview_menu_header_unknown);
|
||||
break;
|
||||
}
|
||||
|
||||
menu.setHeaderTitle(headerTitle);
|
||||
|
||||
// Inflate the context menu
|
||||
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) {
|
||||
@@ -332,13 +350,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
||||
}
|
||||
|
||||
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_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 +459,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 +558,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 +572,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 +610,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}, null);
|
||||
return true;
|
||||
|
||||
case APP_LIST_ID:
|
||||
doAppList(computer.details, false);
|
||||
case FULL_APP_LIST_ID:
|
||||
doAppList(computer.details, false, true);
|
||||
return true;
|
||||
|
||||
case RESUME_ID:
|
||||
@@ -625,6 +643,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 +657,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 +739,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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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<>();
|
||||
@@ -101,6 +107,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
defaultContext.rightStickDeadzoneRadius = (float) stickDeadzone;
|
||||
defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
|
||||
defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS;
|
||||
defaultContext.hatXAxis = MotionEvent.AXIS_HAT_X;
|
||||
defaultContext.hatYAxis = MotionEvent.AXIS_HAT_Y;
|
||||
defaultContext.controllerNumber = (short) 0;
|
||||
defaultContext.assignedControllerNumber = true;
|
||||
defaultContext.external = false;
|
||||
@@ -188,6 +196,28 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return true;
|
||||
}
|
||||
|
||||
// HACK for https://issuetracker.google.com/issues/163120692
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
if (device.getId() == -1) {
|
||||
// This "virtual" device could be input from any of the attached devices.
|
||||
// Look to see if any gamepads are connected.
|
||||
int[] ids = InputDevice.getDeviceIds();
|
||||
for (int id : ids) {
|
||||
InputDevice dev = InputDevice.getDevice(id);
|
||||
if (dev == null) {
|
||||
// This device was removed during enumeration
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are any gamepad devices connected, we'll
|
||||
// report that this virtual device is a gamepad.
|
||||
if (hasJoystickAxes(dev) || hasGamepadButtons(dev)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, we'll try anything that claims to be a non-alphabetic keyboard
|
||||
return device.getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC;
|
||||
}
|
||||
@@ -461,6 +491,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 +557,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 +642,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 +661,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 +671,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 +701,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -665,6 +727,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return null;
|
||||
}
|
||||
|
||||
// HACK for https://issuetracker.google.com/issues/163120692
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
if (event.getDeviceId() == -1) {
|
||||
return defaultContext;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the existing context if it exists
|
||||
InputDeviceContext context = inputDeviceContexts.get(event.getDeviceId());
|
||||
if (context != null) {
|
||||
@@ -826,7 +895,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||
}
|
||||
|
||||
if ((context.vendorId == 0x057e && context.productId == 0x2009) || // Switch Pro controller
|
||||
// This mapping was adding in Android 10, then changed based on
|
||||
// kernel changes (adding hid-nintendo) in Android 11. If we're
|
||||
// on anything newer than Pie, just use the built-in mapping.
|
||||
if ((context.vendorId == 0x057e && context.productId == 0x2009 && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) || // Switch Pro controller
|
||||
(context.vendorId == 0x0f0d && context.productId == 0x00c1)) { // HORIPAD for Switch
|
||||
switch (event.getScanCode()) {
|
||||
case 0x130:
|
||||
@@ -1018,6 +1090,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 +1492,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 +1555,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 +1567,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 +1648,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.
|
||||
@@ -1676,10 +1826,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
|
||||
|
||||
+13
-5
@@ -38,23 +38,31 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
||||
|
||||
@Override
|
||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE;
|
||||
// SOURCE_MOUSE_RELATIVE is how SOURCE_MOUSE appears when our view has pointer capture.
|
||||
// SOURCE_TOUCHPAD will have relative axes populated iff our view has pointer capture.
|
||||
// See https://developer.android.com/reference/android/view/View#requestPointerCapture()
|
||||
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
(event.getSource() == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRelativeAxisX(MotionEvent event) {
|
||||
float x = event.getX();
|
||||
int axis = (event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) ?
|
||||
MotionEvent.AXIS_X : MotionEvent.AXIS_RELATIVE_X;
|
||||
float x = event.getAxisValue(axis);
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
x += event.getHistoricalX(i);
|
||||
x += event.getHistoricalAxisValue(axis, i);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
float y = event.getY();
|
||||
int axis = (event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) ?
|
||||
MotionEvent.AXIS_Y : MotionEvent.AXIS_RELATIVE_Y;
|
||||
float y = event.getAxisValue(axis);
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
y += event.getHistoricalY(i);
|
||||
y += event.getHistoricalAxisValue(axis, i);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x1209, // Ardwiino
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
@@ -34,7 +35,9 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
0x162e, // Joytech
|
||||
0x1689, // Razer Onza
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2f24, // GameSir
|
||||
};
|
||||
|
||||
public static boolean canClaimDevice(UsbDevice device) {
|
||||
@@ -66,8 +69,8 @@ public class Xbox360Controller extends AbstractXboxController {
|
||||
|
||||
@Override
|
||||
protected boolean handleRead(ByteBuffer buffer) {
|
||||
if (buffer.limit() < 14) {
|
||||
LimeLog.severe("Read too small: "+buffer.limit());
|
||||
if (buffer.remaining() < 14) {
|
||||
LimeLog.severe("Read too small: "+buffer.remaining());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ public class XboxOneController extends AbstractXboxController {
|
||||
0x0e6f, // Unknown
|
||||
0x0f0d, // Hori
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2e24, // Hyperkin
|
||||
};
|
||||
|
||||
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
|
||||
@@ -101,11 +103,21 @@ public class XboxOneController extends AbstractXboxController {
|
||||
switch (buffer.get())
|
||||
{
|
||||
case 0x20:
|
||||
if (buffer.remaining() < 17) {
|
||||
LimeLog.severe("XBone button/axis read too small: "+buffer.remaining());
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.position(buffer.position()+3);
|
||||
processButtons(buffer);
|
||||
return true;
|
||||
|
||||
case 0x07:
|
||||
if (buffer.remaining() < 4) {
|
||||
LimeLog.severe("XBone mode read too small: "+buffer.remaining());
|
||||
return false;
|
||||
}
|
||||
|
||||
// The Xbox One S controller needs acks for mode reports otherwise
|
||||
// it retransmits them forever.
|
||||
if (buffer.get() == 0x30) {
|
||||
|
||||
@@ -18,9 +18,12 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private boolean cancelled;
|
||||
private boolean confirmedMove;
|
||||
private boolean confirmedDrag;
|
||||
private boolean confirmedScroll;
|
||||
private Timer dragTimer;
|
||||
private double distanceMoved;
|
||||
private double xFactor, yFactor;
|
||||
private int pointerCount;
|
||||
private int maxPointerCountInGesture;
|
||||
|
||||
private final NvConnection conn;
|
||||
private final int actionIndex;
|
||||
@@ -33,6 +36,8 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private static final int TAP_TIME_THRESHOLD = 250;
|
||||
private static final int DRAG_TIME_THRESHOLD = 650;
|
||||
|
||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
||||
|
||||
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
||||
int referenceWidth, int referenceHeight, View view)
|
||||
{
|
||||
@@ -59,8 +64,18 @@ public class RelativeTouchContext implements TouchContext {
|
||||
|
||||
private boolean isTap()
|
||||
{
|
||||
long timeDelta = SystemClock.uptimeMillis() - originalTouchTime;
|
||||
if (confirmedDrag || confirmedMove || confirmedScroll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this input wasn't the last finger down, do not report
|
||||
// a tap. This ensures we don't report duplicate taps for each
|
||||
// finger on a multi-finger tap gesture
|
||||
if (actionIndex + 1 != maxPointerCountInGesture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long timeDelta = SystemClock.uptimeMillis() - originalTouchTime;
|
||||
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
|
||||
}
|
||||
|
||||
@@ -83,13 +98,17 @@ public class RelativeTouchContext implements TouchContext {
|
||||
|
||||
originalTouchX = lastTouchX = eventX;
|
||||
originalTouchY = lastTouchY = eventY;
|
||||
originalTouchTime = SystemClock.uptimeMillis();
|
||||
cancelled = confirmedDrag = confirmedMove = false;
|
||||
distanceMoved = 0;
|
||||
|
||||
if (actionIndex == 0) {
|
||||
// Start the timer for engaging a drag
|
||||
startDragTimer();
|
||||
if (isNewFinger) {
|
||||
maxPointerCountInGesture = pointerCount;
|
||||
originalTouchTime = SystemClock.uptimeMillis();
|
||||
cancelled = confirmedDrag = confirmedMove = confirmedScroll = false;
|
||||
distanceMoved = 0;
|
||||
|
||||
if (actionIndex == 0) {
|
||||
// Start the timer for engaging a drag
|
||||
startDragTimer();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -128,6 +147,9 @@ public class RelativeTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
private synchronized void startDragTimer() {
|
||||
// Cancel any existing drag timers
|
||||
cancelDragTimer();
|
||||
|
||||
dragTimer = new Timer(true);
|
||||
dragTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
@@ -183,21 +205,33 @@ public class RelativeTouchContext implements TouchContext {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForConfirmedScroll() {
|
||||
// Enter scrolling mode if we've already left the tap zone
|
||||
// and we have 2 fingers on screen. Leave scroll mode if
|
||||
// we no longer have 2 fingers on screen
|
||||
confirmedScroll = (actionIndex == 0 && pointerCount == 2 && confirmedMove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchMoveEvent(int eventX, int eventY)
|
||||
{
|
||||
if (cancelled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventX != lastTouchX || eventY != lastTouchY)
|
||||
{
|
||||
checkForConfirmedMove(eventX, eventY);
|
||||
checkForConfirmedScroll();
|
||||
|
||||
// We only send moves and drags for the primary touch point
|
||||
if (actionIndex == 0) {
|
||||
checkForConfirmedMove(eventX, eventY);
|
||||
|
||||
int deltaX = eventX - lastTouchX;
|
||||
int deltaY = eventY - lastTouchY;
|
||||
|
||||
// Scale the deltas based on the factors passed to our constructor
|
||||
deltaX = (int)Math.round((double)Math.abs(deltaX) * xFactor);
|
||||
deltaY = (int)Math.round((double)Math.abs(deltaY) * yFactor);
|
||||
deltaX = (int) Math.round((double) Math.abs(deltaX) * xFactor);
|
||||
deltaY = (int) Math.round((double) Math.abs(deltaY) * yFactor);
|
||||
|
||||
// Fix up the signs
|
||||
if (eventX < lastTouchX) {
|
||||
@@ -207,6 +241,16 @@ public class RelativeTouchContext implements TouchContext {
|
||||
deltaY = -deltaY;
|
||||
}
|
||||
|
||||
if (pointerCount == 2) {
|
||||
if (confirmedScroll) {
|
||||
deltaY /= SCROLL_SPEED_DIVISOR;
|
||||
|
||||
conn.sendMouseHighResScroll((short) deltaY);
|
||||
}
|
||||
} else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
}
|
||||
|
||||
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
||||
// non-zero to update lastTouch that way devices that report small touch events often
|
||||
// will work correctly
|
||||
@@ -216,8 +260,6 @@ public class RelativeTouchContext implements TouchContext {
|
||||
if (deltaY != 0) {
|
||||
lastTouchY = eventY;
|
||||
}
|
||||
|
||||
conn.sendMouseMove((short)deltaX, (short)deltaY);
|
||||
}
|
||||
else {
|
||||
lastTouchX = eventX;
|
||||
@@ -247,5 +289,11 @@ public class RelativeTouchContext implements TouchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPointerCount(int pointerCount) {}
|
||||
public void setPointerCount(int pointerCount) {
|
||||
this.pointerCount = pointerCount;
|
||||
|
||||
if (pointerCount > maxPointerCountInGesture) {
|
||||
maxPointerCountInGesture = pointerCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -177,6 +177,15 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
listener.onClick();
|
||||
}
|
||||
|
||||
if (timerLongClick != null) {
|
||||
timerLongClick.cancel();
|
||||
timerLongClick = null;
|
||||
}
|
||||
if (longClickTimerTask != null) {
|
||||
longClickTimerTask.cancel();
|
||||
longClickTimerTask = null;
|
||||
}
|
||||
|
||||
timerLongClick = new Timer();
|
||||
longClickTimerTask = new TimerLongClickTimerTask();
|
||||
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
|
||||
@@ -200,9 +209,11 @@ public class DigitalButton extends VirtualControllerElement {
|
||||
// We may be called for a release without a prior click
|
||||
if (timerLongClick != null) {
|
||||
timerLongClick.cancel();
|
||||
timerLongClick = null;
|
||||
}
|
||||
if (longClickTimerTask != null) {
|
||||
longClickTimerTask.cancel();
|
||||
longClickTimerTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -109,8 +110,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
|
||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
||||
|
||||
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr) {
|
||||
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
|
||||
// > 4K streaming also requires HEVC, so force it on there too.
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr ||
|
||||
prefs.width > 4096 || prefs.height > 4096) {
|
||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
||||
}
|
||||
else {
|
||||
@@ -249,6 +252,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (width > 4096 || height > 4096) {
|
||||
LimeLog.severe("> 4K streaming only supported on HEVC");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// These fixups only apply to H264 decoders
|
||||
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderName);
|
||||
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderName);
|
||||
@@ -410,7 +418,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;
|
||||
@@ -427,7 +435,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
else {
|
||||
initialException = new RendererException(this, e);
|
||||
}
|
||||
initialExceptionTimestamp = System.currentTimeMillis();
|
||||
initialExceptionTimestamp = SystemClock.uptimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,14 +638,14 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs) {
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (stopping) {
|
||||
// Don't bother if we're stopping
|
||||
return MoonBridge.DR_OK;
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -649,7 +657,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);
|
||||
@@ -682,7 +690,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
globalVideoStats.add(activeWindowVideoStats);
|
||||
lastWindowVideoStats.copy(activeWindowVideoStats);
|
||||
activeWindowVideoStats.clear();
|
||||
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis();
|
||||
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
activeWindowVideoStats.totalFramesReceived++;
|
||||
@@ -691,11 +699,13 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
int inputBufferIndex;
|
||||
ByteBuffer buf;
|
||||
|
||||
long timestampUs = System.nanoTime() / 1000;
|
||||
long timestampUs = enqueueTimeMs * 1000;
|
||||
|
||||
if (!FRAME_RENDER_TIME_ONLY) {
|
||||
// Count time from first packet received to decode start
|
||||
activeWindowVideoStats.totalTimeMs += (timestampUs / 1000) - receiveTimeMs;
|
||||
// Count time from first packet received to enqueue time as receive time
|
||||
// We will count DU queue time as part of decoding, because it is directly
|
||||
// caused by a slow decoder.
|
||||
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
||||
}
|
||||
|
||||
if (timestampUs <= lastTimestampUs) {
|
||||
|
||||
@@ -40,7 +40,10 @@ public class MediaCodecHelper {
|
||||
private static final List<String> blacklisted59FpsDecoderPrefixes;
|
||||
private static final List<String> qualcommDecoderPrefixes;
|
||||
|
||||
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
||||
|
||||
private static boolean isLowEndSnapdragon = false;
|
||||
private static boolean isAdreno620 = false;
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
@@ -77,7 +80,7 @@ public class MediaCodecHelper {
|
||||
|
||||
// Blacklist software decoders that don't support H264 high profile,
|
||||
// but exclude the official AOSP and CrOS emulator from this restriction.
|
||||
if (!Build.HARDWARE.equals("ranchu") && !Build.HARDWARE.equals("cheets")) {
|
||||
if (!IS_EMULATOR) {
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||
}
|
||||
@@ -123,7 +126,7 @@ public class MediaCodecHelper {
|
||||
whitelistedHevcDecoders = new LinkedList<>();
|
||||
|
||||
// Allow software HEVC decoding in the official AOSP emulator
|
||||
if (Build.HARDWARE.equals("ranchu") && Build.BRAND.equals("google")) {
|
||||
if (Build.HARDWARE.equals("ranchu")) {
|
||||
whitelistedHevcDecoders.add("omx.google");
|
||||
}
|
||||
|
||||
@@ -152,9 +155,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 +240,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 +271,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) {
|
||||
@@ -359,14 +373,14 @@ public class MediaCodecHelper {
|
||||
// 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 on Android 10, so we'll disable it
|
||||
// on that device and all non-Qualcomm devices to be safe.
|
||||
// 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) &&
|
||||
!Build.DEVICE.equalsIgnoreCase("monet");
|
||||
!isAdreno620;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
@@ -574,7 +588,7 @@ public class MediaCodecHelper {
|
||||
private static boolean isCodecBlacklisted(MediaCodecInfo codecInfo) {
|
||||
// Use the new isSoftwareOnly() function on Android Q
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (codecInfo.isSoftwareOnly()) {
|
||||
if (!IS_EMULATOR && codecInfo.isSoftwareOnly()) {
|
||||
LimeLog.info("Skipping software-only decoder: "+codecInfo.getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -115,7 +115,17 @@ public class NvConnection {
|
||||
//
|
||||
|
||||
// Check for a supported stream resolution
|
||||
if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
||||
if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
|
||||
(h.getServerCodecModeSupport(serverInfo) & 0x200) == 0) {
|
||||
context.connListener.displayMessage("Your host PC does not support streaming at resolutions above 4K.");
|
||||
return false;
|
||||
}
|
||||
else if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
|
||||
!context.streamConfig.getHevcSupported()) {
|
||||
context.connListener.displayMessage("Your streaming device must support H.265 to stream at resolutions above 4K.");
|
||||
return false;
|
||||
}
|
||||
else if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
||||
// Client wants 4K but the server can't do it
|
||||
context.connListener.displayTransientMessage("You must update GeForce Experience to stream in 4K. The stream will be 1080p.");
|
||||
|
||||
@@ -231,19 +241,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;
|
||||
}
|
||||
|
||||
@@ -256,7 +266,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,7 +10,7 @@ public abstract class VideoDecoderRenderer {
|
||||
// This is called once for each frame-start NALU. This means it will be called several times
|
||||
// for an IDR frame which contains several parameter sets and the I-frame data.
|
||||
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs);
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs);
|
||||
|
||||
public abstract void cleanup();
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ public class NvHTTP {
|
||||
break;
|
||||
case (XmlPullParser.TEXT):
|
||||
if (currentTag.peek().equals(tagname)) {
|
||||
return xpp.getText().trim();
|
||||
return xpp.getText();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -557,11 +557,11 @@ public class NvHTTP {
|
||||
case (XmlPullParser.TEXT):
|
||||
NvApp app = appList.getLast();
|
||||
if (currentTag.peek().equals("AppTitle")) {
|
||||
app.setAppName(xpp.getText().trim());
|
||||
app.setAppName(xpp.getText());
|
||||
} else if (currentTag.peek().equals("ID")) {
|
||||
app.setAppId(xpp.getText().trim());
|
||||
app.setAppId(xpp.getText());
|
||||
} else if (currentTag.peek().equals("IsHdrSupported")) {
|
||||
app.setHdrSupported(xpp.getText().trim().equals("1"));
|
||||
app.setHdrSupported(xpp.getText().equals("1"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -172,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));
|
||||
|
||||
@@ -35,6 +35,27 @@ 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_ERROR_UNEXPECTED_EARLY_TERMINATION = -102;
|
||||
|
||||
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;
|
||||
@@ -128,11 +149,11 @@ public class MoonBridge {
|
||||
}
|
||||
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength,
|
||||
int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs) {
|
||||
int decodeUnitType, int frameNumber,
|
||||
long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (videoRenderer != null) {
|
||||
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
|
||||
decodeUnitType, frameNumber, receiveTimeMs);
|
||||
decodeUnitType, frameNumber, receiveTimeMs, enqueueTimeMs);
|
||||
}
|
||||
else {
|
||||
return DR_OK;
|
||||
@@ -186,7 +207,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 +292,13 @@ public class MoonBridge {
|
||||
|
||||
public static native int getPendingVideoFrames();
|
||||
|
||||
public static native int testClientConnectivity(String testServerHostName, int referencePort, int testFlags);
|
||||
|
||||
public static native int getPortFlagsFromStage(int stage);
|
||||
|
||||
public static native int getPortFlagsFromTerminationErrorCode(int errorCode);
|
||||
|
||||
public static native String stringifyPortFlags(int portFlags, String separator);
|
||||
|
||||
public static native void init();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
@@ -114,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();
|
||||
|
||||
@@ -121,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() {
|
||||
|
||||
@@ -83,6 +83,7 @@ public class PreferenceConfiguration {
|
||||
public static final String RES_1080P = "1920x1080";
|
||||
public static final String RES_1440P = "2560x1440";
|
||||
public static final String RES_4K = "3840x2160";
|
||||
public static final String RES_NATIVE = "Native";
|
||||
|
||||
public int width, height, fps;
|
||||
public int bitrate;
|
||||
@@ -108,6 +109,30 @@ public class PreferenceConfiguration {
|
||||
public boolean touchscreenTrackpad;
|
||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||
|
||||
public static boolean isNativeResolution(int width, int height) {
|
||||
// It's not a native resolution if it matches an existing resolution option
|
||||
if (width == 640 && height == 360) {
|
||||
return false;
|
||||
}
|
||||
else if (width == 854 && height == 480) {
|
||||
return false;
|
||||
}
|
||||
else if (width == 1280 && height == 720) {
|
||||
return false;
|
||||
}
|
||||
else if (width == 1920 && height == 1080) {
|
||||
return false;
|
||||
}
|
||||
else if (width == 2560 && height == 1440) {
|
||||
return false;
|
||||
}
|
||||
else if (width == 3840 && height == 2160) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String convertFromLegacyResolutionString(String resString) {
|
||||
if (resString.equalsIgnoreCase("360p")) {
|
||||
return RES_360P;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,11 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.PcView;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.video.MediaCodecHelper;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class StreamSettings extends Activity {
|
||||
private PreferenceConfiguration previousPrefs;
|
||||
|
||||
@@ -65,6 +68,7 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
private int nativeResolutionStartIndex = Integer.MAX_VALUE;
|
||||
|
||||
private void setValue(String preferenceKey, String value) {
|
||||
ListPreference pref = (ListPreference) findPreference(preferenceKey);
|
||||
@@ -72,6 +76,37 @@ public class StreamSettings extends Activity {
|
||||
pref.setValue(value);
|
||||
}
|
||||
|
||||
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight) {
|
||||
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
|
||||
|
||||
String newName = getResources().getString(R.string.resolution_prefix_native) + " ("+nativeWidth+"x"+nativeHeight+")";
|
||||
String newValue = nativeWidth+"x"+nativeHeight;
|
||||
|
||||
CharSequence[] values = pref.getEntryValues();
|
||||
|
||||
// Check if the native resolution is already present
|
||||
for (CharSequence value : values) {
|
||||
if (newValue.equals(value.toString())) {
|
||||
// It is present in the default list, so don't add it again
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CharSequence[] newEntries = Arrays.copyOf(pref.getEntries(), pref.getEntries().length + 1);
|
||||
CharSequence[] newValues = Arrays.copyOf(values, values.length + 1);
|
||||
|
||||
// Add the new native option
|
||||
newEntries[newEntries.length - 1] = newName;
|
||||
newValues[newValues.length - 1] = newValue;
|
||||
|
||||
pref.setEntries(newEntries);
|
||||
pref.setEntryValues(newValues);
|
||||
|
||||
if (newValues.length - 1 < nativeResolutionStartIndex) {
|
||||
nativeResolutionStartIndex = newValues.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeValue(String preferenceKey, String value, Runnable onMatched) {
|
||||
int matchingCount = 0;
|
||||
|
||||
@@ -108,8 +143,6 @@ public class StreamSettings extends Activity {
|
||||
pref.setEntryValues(entryValues);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void resetBitrateToDefault(SharedPreferences prefs, String res, String fps) {
|
||||
if (res == null) {
|
||||
res = prefs.getString(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION);
|
||||
@@ -200,6 +233,8 @@ public class StreamSettings extends Activity {
|
||||
int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
||||
int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
||||
|
||||
addNativeResolutionEntry(width, height);
|
||||
|
||||
if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) {
|
||||
maxSupportedResW = 3840;
|
||||
}
|
||||
@@ -299,6 +334,12 @@ public class StreamSettings extends Activity {
|
||||
// Never remove 720p
|
||||
}
|
||||
}
|
||||
else {
|
||||
Display display = getActivity().getWindowManager().getDefaultDisplay();
|
||||
int width = Math.max(display.getWidth(), display.getHeight());
|
||||
int height = Math.min(display.getWidth(), display.getHeight());
|
||||
addNativeResolutionEntry(width, height);
|
||||
}
|
||||
|
||||
if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) {
|
||||
// We give some extra room in case the FPS is rounded down
|
||||
@@ -408,6 +449,25 @@ public class StreamSettings extends Activity {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
||||
String valueStr = (String) newValue;
|
||||
|
||||
// Detect if this value is the native resolution option
|
||||
CharSequence[] values = ((ListPreference)preference).getEntryValues();
|
||||
boolean isNativeRes = true;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
// Look for a match prior to the start of the native resolution entries
|
||||
if (valueStr.equals(values[i].toString()) && i < nativeResolutionStartIndex) {
|
||||
isNativeRes = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is native resolution, show the warning dialog
|
||||
if (isNativeRes) {
|
||||
Dialog.displayDialog(getActivity(),
|
||||
getResources().getString(R.string.title_native_res_dialog),
|
||||
getResources().getString(R.string.text_native_res_dialog),
|
||||
false);
|
||||
}
|
||||
|
||||
// Write the new bitrate value
|
||||
resetBitrateToDefault(prefs, valueStr, null);
|
||||
|
||||
|
||||
@@ -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,38 @@ 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);
|
||||
dialogSummary += MoonBridge.stringifyPortFlags(ret, "\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,
|
||||
|
||||
@@ -77,8 +77,18 @@ public class TvChannelHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
Uri channelUri = context.getContentResolver().insert(
|
||||
TvContract.Channels.CONTENT_URI, builder.toContentValues());
|
||||
Uri channelUri;
|
||||
|
||||
try {
|
||||
channelUri = context.getContentResolver().insert(
|
||||
TvContract.Channels.CONTENT_URI, builder.toContentValues());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This can happen on HarmonyOS devices which report to
|
||||
// support Leanback APIs, yet don't implement this URI
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (channelUri != null) {
|
||||
long id = ContentUris.parseId(channelUri);
|
||||
updateChannelIcon(id);
|
||||
@@ -144,8 +154,15 @@ public class TvChannelHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
context.getContentResolver().insert(TvContract.PreviewPrograms.CONTENT_URI,
|
||||
builder.toContentValues());
|
||||
try {
|
||||
context.getContentResolver().insert(TvContract.PreviewPrograms.CONTENT_URI,
|
||||
builder.toContentValues());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This can happen on HarmonyOS devices which report to
|
||||
// support Leanback APIs, yet don't implement this URI
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
TvContract.requestChannelBrowsable(context, channelId);
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -79,7 +79,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
|
||||
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
||||
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJ)I");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJJ)I");
|
||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
||||
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
||||
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
||||
@@ -157,7 +157,8 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
|
||||
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs);
|
||||
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs,
|
||||
(jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
@@ -178,7 +179,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
|
||||
decodeUnit->frameNumber,
|
||||
(jlong)decodeUnit->receiveTimeMs);
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: 5c9935e11b...021fe902d9
@@ -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_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);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_stringifyPortFlags(JNIEnv *env, jclass clazz, jint portFlags, jstring separator) {
|
||||
const char* separatorStr = (*env)->GetStringUTFChars(env, separator, NULL);
|
||||
char outputBuffer[512];
|
||||
|
||||
LiStringifyPortFlags(portFlags, separatorStr, outputBuffer, sizeof(outputBuffer));
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, separator, separatorStr);
|
||||
return (*env)->NewStringUTF(env, outputBuffer);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_label" translatable="false">Moonlight</string>
|
||||
<string name="app_label_root" translatable="false">Moonlight (Root)</string>
|
||||
|
||||
<!-- Shortcut strings -->
|
||||
<string name="scut_deleted_pc">PC gelöscht</string>
|
||||
@@ -13,13 +15,23 @@
|
||||
<string name="help_loading_msg">Lade Hilfesanzeige…</string>
|
||||
|
||||
<!-- PC view menu entries -->
|
||||
<string name="pcview_menu_app_list">Spiele List anzeigen</string>
|
||||
<string name="pcview_menu_app_list">Spiele anzeigen</string>
|
||||
<string name="pcview_menu_pair_pc">Mit PC verbinden</string>
|
||||
<string name="pcview_menu_unpair_pc">Verbindung beenden</string>
|
||||
<string name="pcview_menu_send_wol">Wake-On-LAN Anfrage senden</string>
|
||||
<string name="pcview_menu_delete_pc">PC löschen</string>
|
||||
<string name="pcview_menu_test_network">Netzwerkverbindung testen</string>
|
||||
<string name="pcview_menu_details">Details anzeigen</string>
|
||||
|
||||
<!-- Network test strings -->
|
||||
<string name="nettest_title_waiting">Netzwerkverbindung wird getest</string>
|
||||
<string name="nettest_text_waiting">Moonlight testet Ihre Netzwerkverbindung, um festzustellen, ob NVIDIA GameStream blockiert ist. Dies kann einige Sekunden dauern...</string>
|
||||
<string name="nettest_title_done">Netzwerktest abgeschlossen</string>
|
||||
<string name="nettest_text_success">Ihr Netzwerk scheint das Mondlicht nicht zu blockieren. Wenn Sie weiterhin Probleme mit der Verbindung haben, überprüfen Sie die Firewall-Einstellungen Ihres PC\n\n\n Wenn Sie versuchen, über das Internet zu streamen, installieren Sie das Moonlight Internet Hosting Tool auf Ihrem PC und führen Sie den mitgelieferten Internet-Streaming-Tester aus, um die Internetverbindung Ihres PCs zu überprüfen.</string>
|
||||
<string name="nettest_text_inconclusive">Der Netzwerktest konnte nicht durchgeführt werden, da keiner der Verbindungstestserver von Moonlight erreichbar war. Überprüfen Sie Ihre Internetverbindung oder versuchen Sie es später noch einmal.</string>
|
||||
<string name="nettest_text_failure">Die aktuelle Netzwerkverbindung Ihres Geräts scheint das Mondlicht zu blockieren. Das Streaming über das Internet funktioniert möglicherweise nicht, während Sie mit diesem Netzwerk verbunden sind.\n\n Folgende Netzwerkports wurden blockiert:\n</string>
|
||||
<string name="nettest_text_blocked">Die aktuelle Netzwerkverbindung Ihres Geräts blockiert Moonlight. Das Streaming über das Internet funktioniert möglicherweise nicht, während Sie mit diesem Netzwerk verbunden sind.</string>
|
||||
|
||||
<!-- Pair messages -->
|
||||
<string name="pairing">Verbinden…</string>
|
||||
<string name="pair_pc_offline">Computer ist offline</string>
|
||||
@@ -28,16 +40,16 @@
|
||||
<string name="pair_pairing_msg">Bitte geben sie folgenden PIN auf ihrem PC ein:</string>
|
||||
<string name="pair_incorrect_pin">PIN inkorrekt</string>
|
||||
<string name="pair_fail">Verbindung fehlgeschlagen</string>
|
||||
<string name="pair_already_in_progress">Verbindungsaufbau rebreits im gange</string>
|
||||
<string name="pair_already_in_progress">Verbindungsaufbau bereits im Gange</string>
|
||||
|
||||
<!-- WOL messages -->
|
||||
<string name="wol_pc_online">Computer ist online</string>
|
||||
<string name="wol_no_mac">PC konnte nicht geweckt werden, da GFE keine MAC-Adresse gesendet hat.</string>
|
||||
<string name="wol_waking_pc">PC wird geweckt…</string>
|
||||
<string name="wol_waking_msg">Es kann einige Momente dauert ihren PC zu wecken.
|
||||
<string name="wol_waking_msg">Es kann einige Momente dauern ihren PC zu wecken.
|
||||
Sollte dies fehlschlagen stellen sie bitte sicher, dass Wake-On-LAN korrekt konfiguriert ist.
|
||||
</string>
|
||||
<string name="wol_fail">Sender der Wake-On-LAN Pakete ist fehlgeschlagen</string>
|
||||
<string name="wol_fail">Senden der Wake-On-LAN Pakete ist fehlgeschlagen.</string>
|
||||
|
||||
<!-- Unpair messages -->
|
||||
<string name="unpairing">Trenne Verbindung…</string>
|
||||
@@ -50,31 +62,35 @@
|
||||
<string name="error_manager_not_running">Der ComputerManager Service läuft nicht. Bitte warten sie ein paar Sekunden oder starten sie Die App neu.</string>
|
||||
<string name="error_unknown_host">Host konnte nicht aufgelößt werden.</string>
|
||||
<string name="error_404">GFE ist auf einen HTTP 404 Fehler gestoßen. Stellen sie sicher, dass die GPU ihres PCs unterstützt wird.
|
||||
Die verwendung von Remote-Desktop Software kann ebenso diesen Fehler verursachen. Starten sie ihren Computer neu oder reinstallieren sie GFE.
|
||||
Die Verwendung von Remote-Desktop Software kann ebenso diesen Fehler verursachen. Starten sie ihren Computer neu oder reinstallieren sie GFE.
|
||||
</string>
|
||||
<string name="title_decoding_error">Video Decoder ist gecrashed</string>
|
||||
<string name="message_decoding_error">Moonlight ist wegen einer Inkompatibilität zu dem Video-Decoder ihres Gerätes gecrasht. Stellen sie sicher, dass GeForce Experience Version auf ihrem PC auf dem neuesten Stand ist. Sollten weiterin Crashes auftreten, versuchen sie ihre Stream Einstellungen zu justieren.</string>
|
||||
<string name="title_decoding_reset">Video Einstellungen zurücksetzen</string>
|
||||
<string name="message_decoding_reset">Der Video-Decoder ihres Geärts ist wiederholt mit den ausgewählten Einstellungen gecrasht. Ihre streaming einstellungen wurden zurückgesetzt.</string>
|
||||
<string name="message_decoding_reset">Der Video-Decoder ihres Geräts ist wiederholt mit den ausgewählten Einstellungen gecrasht. Ihre Streamingeinstellungen wurden zurückgesetzt.</string>
|
||||
<string name="error_usb_prohibited">USB Zugriff ist administrativ unterbunden. Bitte überprüfen sie ihre Knox oder MDM Einstellungen.</string>
|
||||
<string name="unable_to_pin_shortcut">Die zur Zeit aktive Launcher App ünterstützt das erstellen angehefteter Shortcuts nicht.</string>
|
||||
<string name="video_decoder_init_failed">Der Videodecoder konnte nicht initialisiert werden. Ihr Gerät unterstützt möglicherweise die gewählte Auflösung oder Bildfrequenz nicht.</string>
|
||||
<string name="no_video_received_error">Kein Video vom Gastgeber empfangen. Überprüfen Sie die Firewall und Portweiterleitungsregeln des Host-PCs.</string>
|
||||
<string name="no_frame_received_error">Ihre Netzwerkverbindung funktioniert nicht gut. Reduzieren Sie die Einstellung der Videobitrate oder versuchen Sie eine schnellere Verbindung.</string>
|
||||
|
||||
<!-- Start application messages -->
|
||||
<string name="conn_establishing_title">Verbindung herstellen</string>
|
||||
<string name="conn_establishing_msg">Verbindung starten</string>
|
||||
<string name="conn_metered">Warnung: Das Datentransfairvolument ihrer Netzwerkverbindung ist limitiert!</string>
|
||||
<string name="conn_metered">Warnung: Das Datentransfervolumen ihrer Netzwerkverbindung ist limitiert!</string>
|
||||
<string name="conn_client_latency">Durchschnittliche Frame-Dekodierungslatenz:</string>
|
||||
<string name="conn_client_latency_hw">Hardware-Dekodierungslatenz:</string>
|
||||
<string name="conn_hardware_latency">Durchschnittliche Hardware-Dekodierungslatenz:</string>
|
||||
<string name="conn_starting">Startet</string>
|
||||
<string name="conn_error_title">Verbindingsfehler</string>
|
||||
<string name="conn_error_title">Verbindungsfehler</string>
|
||||
<string name="conn_error_msg">Start fehlgeschlagen</string>
|
||||
<string name="conn_terminated_title">Verbindung beendet</string>
|
||||
<string name="conn_terminated_msg">Die verbinding wurde beendet</string>
|
||||
<string name="conn_terminated_msg">Die Verbindung wurde beendet</string>
|
||||
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint">IP-Adresse des GeForce PCs</string>
|
||||
<string name="searching_pc">Suche nach PCs wo GeForce Experience aktiv ist…</string>
|
||||
<string name="searching_pc">Suche nach PCs wo GeForce Experience aktiv ist…\n\n
|
||||
Stellen Sie sicher, dass GameStream in den GeForce Experience SHIELD-Einstellungen aktiviert ist.</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="no">Nein</string>
|
||||
<string name="lost_connection">Verbindung zum PC verloren</string>
|
||||
@@ -83,7 +99,7 @@
|
||||
<string name="delete_pc_msg">Sind sie sicher, dass sie diesen PC löschen möchten?</string>
|
||||
<string name="slow_connection_msg">Langsame Verbindung zum PC\nReduzieren sie die Bitrate</string>
|
||||
<string name="poor_connection_msg">Sehr langsame Verbindung zum PC</string>
|
||||
<string name="perf_overlay_text">Videodimensionen: %1$s\nDecoder: %2$s\nGeschätzte PC Bildwiederholrate: %3$.2f FPS\nBildwiederholrate der Netzwerkübertragung: %4$.2f FPS\nWiedergabe-Bildwiederholungsrate: %5$.2f FPS\nWegen Netzwerkübertraung ausgelassene Frames: %6$.2f%%\nDurchschnittliche Übertragunsdauer: %7$.2f ms\nDurchschnittliche decoding dauer: %8$.2f ms</string>
|
||||
<string name="perf_overlay_text">Videodimensionen: %1$s\nDecoder: %2$s\nGeschätzte PC Bildwiederholrate: %3$.2f FPS\nBildwiederholrate der Netzwerkübertragung: %4$.2f FPS\nWiedergabe-Bildwiederholungsrate: %5$.2f FPS\nWegen Netzwerkübertraung ausgelassene Frames: %6$.2f%%\nDurchschnittliche Übertragungsdauer: %7$.2f ms\nDurchschnittliche decoding dauer: %8$.2f ms</string>
|
||||
|
||||
<!-- AppList activity -->
|
||||
<string name="applist_connect_msg">Verbinde mit PC…</string>
|
||||
@@ -94,12 +110,13 @@
|
||||
<string name="applist_menu_details">Details anzeigen</string>
|
||||
<string name="applist_menu_scut">Shortcut erstellen</string>
|
||||
<string name="applist_menu_tv_channel">Zu Kanal hinzufügen</string>
|
||||
<string name="applist_menu_hide_app">App ausblenden</string>
|
||||
<string name="applist_refresh_title">App Liste</string>
|
||||
<string name="applist_refresh_msg">Aktualisiere Apps…</string>
|
||||
<string name="applist_refresh_error_title">Fehler</string>
|
||||
<string name="applist_refresh_error_msg">Abfrage der App-Liste fehlgeschlagen</string>
|
||||
<string name="applist_quit_app">Beenden</string>
|
||||
<string name="applist_quit_success">Erfolgreich Beendet</string>
|
||||
<string name="applist_quit_success">Erfolgreich beendet</string>
|
||||
<string name="applist_quit_fail">Beenden fehlgeschlagen</string>
|
||||
<string name="applist_quit_confirmation">Sind sie sicher, dass die die laufende App schließen möchten? Ungespeicherte Daten gehen verloren.</string>
|
||||
<string name="applist_details_id">App ID:</string>
|
||||
@@ -107,7 +124,7 @@
|
||||
<!-- Add computer manually activity -->
|
||||
<string name="title_add_pc">PC manuell hinzufügen</string>
|
||||
<string name="msg_add_pc">Verbinde zum PC…</string>
|
||||
<string name="addpc_fail">Verbindung fehlgeschlagen. Stellen sie sicher, dass die benötigten ports von einer Firewall gefiltert werden.</string>
|
||||
<string name="addpc_fail">Verbindung fehlgeschlagen. Stellen sie sicher, dass die benötigten Ports von einer Firewall gefiltert werden.</string>
|
||||
<string name="addpc_success">Computer erfolgreich hinzugefügt</string>
|
||||
<string name="addpc_unknown_host">Auflößen der PC-Adresse fehlgeschlagen. Stellen sie sicher, dass die Adresse keine Tippfehler beinhaltet.</string>
|
||||
<string name="addpc_enter_ip">Sie müssen eine IP-Addresse eingeben.</string>
|
||||
@@ -119,34 +136,34 @@
|
||||
<string name="summary_resolution_list">Ehöhen für klarere Bilder. Reduzieren für flüssigere Darstellung auf langsameren Geräten und Netzwerken.</string>
|
||||
<string name="title_fps_list">Bildwiederholungsrate</string>
|
||||
<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="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>
|
||||
<string name="title_checkbox_disable_warnings">Warnhinweise deaktivieren</string>
|
||||
<string name="summary_checkbox_disable_warnings">Verbindungswarnungen nicht als Overlay wärend des Streamens anzeigen</string>
|
||||
<string name="title_checkbox_enable_pip">Bild-in-Bild Überwachungsmodus aktivieren</string>
|
||||
<string name="summary_checkbox_enable_pip">Stream auch während des Multitaskings anzeigen (ohne Steuerung)</string>
|
||||
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
|
||||
<string name="title_checkbox_stretch_video">Video auf Vollbildschirm strecken</string>
|
||||
|
||||
<string name="category_audio_settings">Audio Einstellungen</string>
|
||||
<string name="title_audio_config_list">Surround Sound Konfiguration </string>
|
||||
<string name="summary_audio_config_list">Aktivieren von 5.1- oder 7.1-Surround-Sound für Heimkinosysteme</string>
|
||||
|
||||
<string name="category_input_settings">Eingabe Einstellungen</string>
|
||||
<string name="title_checkbox_touchscreen_trackpad">Verwenden des Touchscreen als Trackpad</string>
|
||||
<string name="summary_checkbox_touchscreen_trackpad">Wenn aktiviert, verhält sich der Touchscreen wie ein Trackpad. Wenn er deaktiviert ist, steuert der Touchscreen den Mauszeiger direkt.</string>
|
||||
<string name="title_checkbox_multi_controller">Automatische GamePad-Erkennung</string>
|
||||
<string name="summary_checkbox_multi_controller">Abwählen dieser Option erzwingt dass immer ein GamePad present ist</string>
|
||||
<string name="summary_checkbox_multi_controller">Abwählen dieser Option erzwingt dass immer ein GamePad präsent ist</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Vibrationsemulation aktivieren</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Lässt das Gerät vibrieren falls das GamePad keine Vibration unterstütz</string>
|
||||
<string name="title_seekbar_deadzone">Tot Bereich des Analogsticks</string>
|
||||
<string name="title_seekbar_deadzone">Deadzone des Analogsticks</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Xbox 360/One GamePad Treiber</string>
|
||||
<string name="summary_checkbox_xb1_driver">Aktiviert eingebauten USB Treiber für Geräte die keinen Xbox GamePad-Unterstützung haben</string>
|
||||
<string name="title_checkbox_usb_bind_all">Android GamePad Unterstütung überlagern</string>
|
||||
<string name="summary_checkbox_usb_bind_all">Erzwingt die Vernwedung von Moonlight\'s USB Treiber für alle Xbox kompatiblen GamePads</string>
|
||||
<string name="summary_checkbox_usb_bind_all">Erzwingt die Verwendung von Moonlights USB Treiber für alle Xbox kompatiblen GamePads</string>
|
||||
<string name="title_checkbox_mouse_emulation">Maus Emulation via GamePad</string>
|
||||
<string name="summary_checkbox_mouse_emulation">Langes gedrückt halten der Start-Taste wechselt in den Maus Modus</string>
|
||||
<string name="title_checkbox_mouse_nav_buttons">Vor- und Zurück-Tasten aktivieren</string>
|
||||
<string name="summary_checkbox_mouse_nav_buttons">Aktivierung dieser Option kann auf fehleranfälligen Geräten Rechts-Clicks verunmöglichen</string>
|
||||
<string name="title_checkbox_flip_face_buttons">Buttons umkehren</string>
|
||||
<string name="summary_checkbox_flip_face_buttons">Kehrt die Buttons A/B und X/Y für Gamepads und die Bildschirmsteuerung um</string>
|
||||
|
||||
<string name="category_on_screen_controls_settings">On-Screen Steuerungseinstellungen</string>
|
||||
<string name="title_checkbox_show_onscreen_controls">Zeige On-Screen Steuerung</string>
|
||||
@@ -160,8 +177,14 @@
|
||||
<string name="dialog_title_reset_osc">Layout Zurücksetzen</string>
|
||||
<string name="dialog_text_reset_osc">Sind sie sicher, dass sie das gepseicherte on-screen Layout der Steuerelemte löschen wollen?</string>
|
||||
<string name="toast_reset_osc_success">On-Screen Steuerelemente wurden zurückgesetzt</string>
|
||||
<string name="title_osc_opacity">Ändern der Transparenz von Bildschirmsteuerelementen</string>
|
||||
<string name="summary_osc_opacity">Steuerelemente auf dem Bildschirm mehr/weniger transparent machen</string>
|
||||
<string name="suffix_osc_opacity">%</string>
|
||||
<string name="dialog_title_osc_opacity">Transparenz</string>
|
||||
|
||||
<string name="category_ui_settings">UI Einstellungen</string>
|
||||
<string name="title_checkbox_enable_pip">Aktivieren des Bild-in-Bild-Beobachtungsmodus</string>
|
||||
<string name="summary_checkbox_enable_pip">Ermöglicht die Anzeige (aber nicht die Steuerung) des Streams während des Multitasking</string>
|
||||
<string name="title_language_list">Sprache</string>
|
||||
<string name="summary_language_list">Sprache die Moonlight verwenden soll</string>
|
||||
<string name="title_checkbox_small_icon_mode">Verwende kleine Icons</string>
|
||||
@@ -175,14 +198,17 @@
|
||||
|
||||
<string name="category_advanced_settings">Erweiterte Einstellungen</string>
|
||||
<string name="title_disable_frame_drop">Nie Frames Überspringen</string>
|
||||
<string name="title_unlock_fps">Alle möglichen Bildfrequenzen freischalten</string>
|
||||
<string name="summary_unlock_fps">Streaming mit 90 oder 120 FPS kann die Latenzzeit auf High-End-Geräten verringern, kann aber bei Geräten, die dies nicht unterstützen, zu Verzögerungen oder Instabilität führen.</string>
|
||||
<string name="title_checkbox_disable_warnings">Warnhinweise deaktivieren</string>
|
||||
<string name="summary_checkbox_disable_warnings">On-Screen Warnmeldungen während des Streaming deaktivieren</string>
|
||||
<string name="summary_disable_frame_drop">Kann potentiell das Mikro-Ruckeln auf einigen Geräten reduzieren, allerdings erhöt dies gleichzeitig die Latenz</string>
|
||||
<string name="title_video_format">Ändere H.265 Einstellungen</string>
|
||||
<string name="summary_video_format">H.265 verringerd die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
|
||||
<string name="summary_video_format">H.265 verringert die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
|
||||
<string name="title_enable_hdr">HDR aktivieren (experimentell)</string>
|
||||
<string name="summary_enable_hdr">HDR-Streaming sofern dies von der PC GPU unterstützt wird. HDR erfordert eine GPU der GTX 1000 Serie oder neuer.</string>
|
||||
<string name="title_enable_perf_overlay">Performance Overlay aktivieren</string>
|
||||
<string name="summary_enable_perf_overlay">Leistungsmerkmale während des Streamens in Echtzeit einblenden.</string>
|
||||
<string name="suffix_osc_opacity">%</string>
|
||||
<string name="dialog_title_osc_opacity">Transparenz</string>
|
||||
|
||||
<string name="title_enable_post_stream_toast">Zeige Informationen über Latenz nach Streaming</string>
|
||||
<string name="summary_enable_post_stream_toast">Anzeige einer Informationsmeldung über Latenz nach dem Ende des Streams</string>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 n’a 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 lorsqu’il 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 lorsqu’il 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 d’informations de latence après la fin du flux</string>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -90,7 +90,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>
|
||||
|
||||
@@ -122,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>
|
||||
|
||||
@@ -122,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>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<item>1440p</item>
|
||||
<item>4K</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Keep this in sync with PreferenceConfiguration.isNativeResolution()! -->
|
||||
<string-array name="resolution_values" translatable="false">
|
||||
<item>640x360</item>
|
||||
<item>854x480</item>
|
||||
|
||||
@@ -15,13 +15,26 @@
|
||||
<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_header_online">Online</string>
|
||||
<string name="pcview_menu_header_offline">Offline</string>
|
||||
<string name="pcview_menu_header_unknown">Refreshing</string>
|
||||
<string name="pcview_menu_app_list">View All 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>
|
||||
@@ -61,7 +74,10 @@
|
||||
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
|
||||
<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_video_received_error">No video received from host.</string>
|
||||
<string name="no_frame_received_error">Your network connection isn\'t performing well. Reduce your video bitrate setting or try a faster connection.</string>
|
||||
<string name="early_termination_error">Something went wrong on your host PC when starting the stream.\n\nMake sure you don\'t have any DRM-protected content open on your host PC. You can also try restarting your host PC.\n\nIf the issue persists, try reinstalling your GPU drivers and GeForce Experience.</string>
|
||||
<string name="check_ports_msg">Check your firewall and port forwarding rules for port(s):</string>
|
||||
|
||||
<!-- Start application messages -->
|
||||
<string name="conn_establishing_title">Establishing Connection</string>
|
||||
@@ -99,6 +115,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>
|
||||
@@ -122,12 +139,15 @@
|
||||
<string name="category_basic_settings">Basic Settings</string>
|
||||
<string name="title_resolution_list">Video resolution</string>
|
||||
<string name="summary_resolution_list">Increase to improve image clarity. Decrease for better performance on lower end devices and slower networks.</string>
|
||||
<string name="title_native_res_dialog">Native Resolution Warning</string>
|
||||
<string name="text_native_res_dialog">Native resolution modes are not officially supported by GeForce Experience, so it will not set your host display resolution itself. You will need to set it manually while in game.\n\nIf you choose to create a custom resolution in NVIDIA Control Panel to match your device resolution, please ensure you have read and understood NVIDIA\'s warning regarding possible monitor damage, PC instability, and other potential problems.\n\nWe are not responsible for any problems resulting from creating a custom resolution on your PC.\n\nFinally, your device or host PC may not support streaming at native resolution. If it doesn\'t work on your device, you\'re just out of luck unfortunately.</string>
|
||||
<string name="title_fps_list">Video frame rate</string>
|
||||
<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="resolution_prefix_native">Native</string>
|
||||
|
||||
<string name="category_audio_settings">Audio Settings</string>
|
||||
<string name="title_audio_config_list">Surround sound configuration</string>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
version: 0.0.0.{build}
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
before_build:
|
||||
- 'git submodule update --init --recursive'
|
||||
- 'mklink /D C:\android-sdk "C:\Program Files (x86)\Android\android-sdk"'
|
||||
- 'set ANDROID_HOME=C:\android-sdk'
|
||||
|
||||
build_script:
|
||||
- gradlew.bat build connectedCheck
|
||||
|
||||
after_build:
|
||||
- 'appveyor PushArtifact app\build\reports\lint-results.html'
|
||||
|
||||
deploy: off
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
- Improved connection reliability
|
||||
@@ -0,0 +1 @@
|
||||
- Improved performance on Google TV Chromecast and other newer Amlogic devices by using HEVC by default
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
- Enabled raw mouse input on the Nvidia Shield TV to avoid mouse acceleration
|
||||
@@ -0,0 +1 @@
|
||||
- Fixed missing video or audio when streaming over some Internet connections
|
||||
@@ -0,0 +1,4 @@
|
||||
- Added the option to stream at your device's native resolution
|
||||
- Added 2 finger scrolling in touchpad mode
|
||||
- Added additional troubleshooting information when a connection fails
|
||||
- Minor UI tweaks and crash fixes
|
||||
@@ -0,0 +1,3 @@
|
||||
- Added support for multiple native resolution options
|
||||
- Improve accuracy of decoder latency reports for very slow decoders
|
||||
- Minor UI improvements
|
||||
@@ -0,0 +1,2 @@
|
||||
- Add a workaround for the Android 11 gamepad bug when Accessibility features are enabled
|
||||
- See https://issuetracker.google.com/issues/163120692 for more details
|
||||
@@ -0,0 +1,2 @@
|
||||
- Fixed mapping of Nintendo Switch Pro controller on Android 11
|
||||
- Fixed handling of multiple input devices with different modifier states
|
||||
@@ -0,0 +1,2 @@
|
||||
- Fixed typing upper-case letters with the software keyboard
|
||||
- Improved device compatibility with the built-in USB Xbox gamepad driver
|
||||
+2
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user