Compare commits

...

23 Commits

Author SHA1 Message Date
Cameron Gutman 4cae6959df Update inconclusive test result text 2020-08-09 17:16:51 -07:00
Cameron Gutman f02d7b4516 Version 9.7.1 2020-08-09 17:13:44 -07:00
Cameron Gutman f5c83112df Update gitignore 2020-08-09 16:47:16 -07:00
Cameron Gutman a413dc81c1 Avoid doing client connectivity tests on the main thread 2020-08-09 16:22:50 -07:00
Cameron Gutman c9eddab191 Remove UDP 7 and add UDP 47009 for WoL 2020-08-09 14:40:44 -07:00
Cameron Gutman ec1268bd71 Version 9.7 2020-08-09 12:08:50 -07:00
Cameron Gutman 22eb2b5823 Always show the network test option 2020-08-06 22:11:22 -07:00
Cameron Gutman 9669da026f Test network when the connection terminates due to lack of video traffic 2020-08-06 22:01:45 -07:00
Cameron Gutman 7b14e54eab Test network connectivity when adding a PC fails 2020-08-06 20:43:17 -07:00
Cameron Gutman 6b30ee4593 Change connection test domain name 2020-08-06 20:31:15 -07:00
Cameron Gutman 17c47a15da Improve display mode selection algorithm
- Allow the refresh rate to drop if it results in a better match for the stream frame rate
- Allow the resolution to drop for > 60 FPS streams to allow matching a higher refresh rate
2020-08-06 20:14:56 -07:00
Cameron Gutman 8f55517236 Prevent assert when control stream connection fails 2020-08-06 19:13:50 -07:00
Cameron Gutman 41ad086dfa Upgrade to AGP 4.0.1 2020-08-06 19:07:32 -07:00
Cameron Gutman e19ef7dcae Remove redundant Cancel option in app grid menu 2020-08-04 02:09:33 -07:00
Cameron Gutman f361265d70 Add automatic network test for failed connection stages 2020-08-01 22:56:32 -07:00
Cameron Gutman ef72e3ef77 Only show the option to hide the app if it's not running or already hidden 2020-08-01 22:48:22 -07:00
Cameron Gutman 770f1a1ca0 Add network connection test 2020-08-01 22:19:40 -07:00
Cameron Gutman e8fc91191f Add the option to hide games in the app list
Fixes #640
2020-08-01 18:20:39 -07:00
Cameron Gutman 105ad3317d Pass parent view into grid adapters 2020-08-01 17:52:55 -07:00
Cameron Gutman 22bf4775cd Enable poll() in ENet 2020-07-27 00:12:26 -07:00
Cameron Gutman 5c6be7969a Disable max operating rate trick on all Snapdragon 765G devices
Fixes #783
2020-07-26 22:39:10 -07:00
Cameron Gutman c6e23f4be2 Update common-c with client connectivity test and select() replacement 2020-07-26 22:06:46 -07:00
Cameron Gutman 05547c22ec Use SecureRandom for PINs 2020-07-12 12:16:11 -07:00
22 changed files with 383 additions and 87 deletions
+1
View File
@@ -3,6 +3,7 @@
*.ap_
*.aab
output.json
output-metadata.json
out/
# files for the dex VM
+2 -2
View File
@@ -7,8 +7,8 @@ android {
minSdkVersion 16
targetSdkVersion 30
versionName "9.6.4"
versionCode = 237
versionName "9.7.1"
versionCode = 241
}
flavorDimensions "root"
+56 -8
View File
@@ -2,6 +2,7 @@ package com.limelight;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashSet;
import java.util.List;
import com.limelight.computers.ComputerManagerListener;
@@ -26,6 +27,7 @@ import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -59,17 +61,22 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private int lastRunningAppId;
private boolean suspendGridUpdates;
private boolean inForeground;
private boolean showHiddenApps;
private HashSet<Integer> hiddenAppIds = new HashSet<>();
private final static int START_OR_RESUME_ID = 1;
private final static int QUIT_ID = 2;
private final static int CANCEL_ID = 3;
private final static int START_WITH_QUIT = 4;
private final static int VIEW_DETAILS_ID = 5;
private final static int CREATE_SHORTCUT_ID = 6;
private final static int HIDE_APP_ID = 7;
public final static String HIDDEN_APPS_PREF_FILENAME = "HiddenApps";
public final static String NAME_EXTRA = "Name";
public final static String UUID_EXTRA = "UUID";
public final static String NEW_PAIR_EXTRA = "NewPair";
public final static String SHOW_HIDDEN_APPS_EXTRA = "ShowHiddenApps";
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@@ -98,13 +105,16 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
try {
appGridAdapter = new AppGridAdapter(AppView.this,
PreferenceConfiguration.readPreferences(AppView.this),
computer, localBinder.getUniqueId());
computer, localBinder.getUniqueId(),
showHiddenApps);
} catch (Exception e) {
e.printStackTrace();
finish();
return;
}
appGridAdapter.updateHiddenApps(hiddenAppIds);
// 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() {
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);
}
private void populateAppGridWithCache() {
try {
// Try to load from cache
@@ -365,10 +396,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}
else {
menu.add(Menu.NONE, START_WITH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
}
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
// Only show the hide checkbox if this is not the currently running app or it's already hidden
if (lastRunningAppId != selectedApp.app.getAppId() || selectedApp.isHidden) {
MenuItem hideAppItem = menu.add(Menu.NONE, HIDE_APP_ID, 3, getResources().getString(R.string.applist_menu_hide_app));
hideAppItem.setCheckable(true);
hideAppItem.setChecked(selectedApp.isHidden);
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 4, getResources().getString(R.string.applist_menu_details));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Only add an option to create shortcut if box art is loaded
@@ -379,7 +417,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
BitmapDrawable drawable = (BitmapDrawable)appImageView.getDrawable();
if (drawable != null && drawable.getBitmap() != null) {
// We have a bitmap loaded too
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 5, getResources().getString(R.string.applist_menu_scut));
}
}
}
@@ -430,13 +468,22 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case CANCEL_ID:
return true;
case VIEW_DETAILS_ID:
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details), app.app.toString(), false);
return true;
case HIDE_APP_ID:
if (item.isChecked()) {
// Transitioning hidden to shown
hiddenAppIds.remove(app.app.getAppId());
}
else {
// Transitioning shown to hidden
hiddenAppIds.add(app.app.getAppId());
}
updateHiddenApps();
return true;
case CREATE_SHORTCUT_ID:
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
Bitmap appBits = ((BitmapDrawable)appImageView.getDrawable()).getBitmap();
@@ -593,6 +640,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) {
+74 -30
View File
@@ -30,6 +30,7 @@ import com.limelight.ui.GameGestures;
import com.limelight.ui.StreamView;
import com.limelight.utils.Dialog;
import com.limelight.utils.NetHelper;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
@@ -616,6 +617,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
private boolean isRefreshRateGoodMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
Math.round(refreshRate) % prefConfig.fps <= 3;
}
private float prepareDisplayForRendering() {
Display display = getWindowManager().getDefaultDisplay();
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
@@ -624,41 +630,59 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display.Mode bestMode = display.getMode();
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateOk = candidate.getRefreshRate() >= bestMode.getRefreshRate();
boolean resolutionOk = candidate.getPhysicalWidth() >= bestMode.getPhysicalWidth() &&
candidate.getPhysicalHeight() >= bestMode.getPhysicalHeight() &&
candidate.getPhysicalWidth() <= 4096;
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
candidate.getPhysicalHeight() < bestMode.getPhysicalHeight();
boolean resolutionFitsStream = candidate.getPhysicalWidth() >= prefConfig.width &&
candidate.getPhysicalHeight() >= prefConfig.height;
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
// On non-4K streams, we force the resolution to never change
if (prefConfig.width < 3840) {
if (candidate.getPhysicalWidth() > 4096) {
// Avoid resolutions options above 4K to be safe
continue;
}
// On non-4K streams, we force the resolution to never change unless it's above
// 60 FPS, which may require a resolution reduction due to HDMI bandwidth limitations.
if (prefConfig.width < 3840 && prefConfig.fps <= 60) {
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
continue;
}
}
// Ensure the frame rate stays around 60 Hz for <= 60 FPS streams
if (prefConfig.fps <= 60) {
if (candidate.getRefreshRate() >= 63) {
// Make sure the resolution doesn't regress unless if it's over 60 FPS
// where we may need to reduce resolution to achieve the desired refresh rate.
if (resolutionReduced && !(prefConfig.fps > 60 && resolutionFitsStream)) {
continue;
}
if (refreshRateIsGood) {
// We have a good matching refresh rate, so we're looking for equal or greater
// that is also a good matching refresh rate for our stream frame rate.
if (refreshRateReduced || !isRefreshRateGoodMatch(candidate.getRefreshRate())) {
continue;
}
}
// Make sure the refresh rate doesn't regress
if (!refreshRateOk) {
continue;
}
// Make sure the resolution doesn't regress
if (!resolutionOk) {
continue;
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
// We didn't have a good match and this match isn't good either, so just don't
// reduce the refresh rate.
if (refreshRateReduced) {
continue;
}
} else {
// We didn't have a good match and this match is good. Prefer this refresh rate
// even if it reduces the refresh rate. Lowering the refresh rate can be beneficial
// when streaming a 60 FPS stream on a 90 Hz device. We want to select 60 Hz to
// match the frame rate even if the active display mode is 90 Hz.
}
bestMode = candidate;
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
@@ -669,9 +693,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float bestRefreshRate = display.getRefreshRate();
for (float candidate : display.getSupportedRefreshRates()) {
if (candidate > bestRefreshRate) {
LimeLog.info("Examining refresh rate: "+candidate);
LimeLog.info("Examining refresh rate: "+candidate);
if (candidate > bestRefreshRate) {
// Ensure the frame rate stays around 60 Hz for <= 60 FPS streams
if (prefConfig.fps <= 60) {
if (candidate >= 63) {
@@ -1502,7 +1526,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
@Override
public void stageFailed(final String stage, final int errorCode) {
public void stageFailed(final String stage, final int portFlags, final int errorCode) {
// Perform a connection test if the failure could be due to a blocked port
// This does network I/O, so don't do it on the main thread.
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443, portFlags);
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1520,8 +1548,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Toast.makeText(Game.this, getResources().getText(R.string.video_decoder_init_failed), Toast.LENGTH_LONG).show();
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.conn_error_msg) + " " + stage +" (error "+errorCode+")", true);
String dialogText = getResources().getString(R.string.conn_error_msg) + " " + stage +" (error "+errorCode+")";
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
dialogText += "\n\n" + getResources().getString(R.string.nettest_text_blocked);
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title), dialogText, true);
}
}
});
@@ -1529,6 +1562,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void connectionTerminated(final int errorCode) {
// Perform a connection test if the failure could be due to a blocked port
// This does network I/O, so don't do it on the main thread.
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER,
443, MoonBridge.getPortFlagsFromTerminationErrorCode(errorCode));
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1548,14 +1586,20 @@ 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;
default:
message = getResources().getString(R.string.conn_terminated_msg);
break;
}
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
+23 -11
View File
@@ -117,6 +117,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
private final static int RESUME_ID = 6;
private final static int QUIT_ID = 7;
private final static int VIEW_DETAILS_ID = 8;
private final static int FULL_APP_LIST_ID = 9;
private final static int TEST_NETWORK_ID = 10;
private void initializeViews() {
setContentView(R.layout.activity_pc_view);
@@ -320,11 +322,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (computer.details.state == ComputerDetails.State.OFFLINE ||
computer.details.state == ComputerDetails.State.UNKNOWN) {
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
else if (computer.details.pairState != PairState.PAIRED) {
menu.add(Menu.NONE, PAIR_ID, 1, getResources().getString(R.string.pcview_menu_pair_pc));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
else {
if (computer.details.runningGameId != 0) {
@@ -333,12 +333,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}
menu.add(Menu.NONE, APP_LIST_ID, 3, getResources().getString(R.string.pcview_menu_app_list));
// FIXME: We used to be able to unpair here but it's been broken since GFE 2.1.x, so I've replaced
// it with delete which actually work
menu.add(Menu.NONE, DELETE_ID, 4, getResources().getString(R.string.pcview_menu_delete_pc));
menu.add(Menu.NONE, FULL_APP_LIST_ID, 4, getResources().getString(R.string.pcview_menu_full_app_list));
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 5, getResources().getString(R.string.pcview_menu_details));
menu.add(Menu.NONE, TEST_NETWORK_ID, 5, getResources().getString(R.string.pcview_menu_test_network));
menu.add(Menu.NONE, DELETE_ID, 6, getResources().getString(R.string.pcview_menu_delete_pc));
menu.add(Menu.NONE, VIEW_DETAILS_ID, 7, getResources().getString(R.string.pcview_menu_details));
}
@Override
@@ -442,7 +442,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (toastSuccess) {
// Open the app list after a successful pairing attempt
doAppList(computer, true);
doAppList(computer, true, false);
}
else {
// Start polling again if we're still in the foreground
@@ -541,7 +541,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}).start();
}
private void doAppList(ComputerDetails computer, boolean newlyPaired) {
private void doAppList(ComputerDetails computer, boolean newlyPaired, boolean showHiddenGames) {
if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return;
@@ -555,6 +555,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid);
i.putExtra(AppView.NEW_PAIR_EXTRA, newlyPaired);
i.putExtra(AppView.SHOW_HIDDEN_APPS_EXTRA, showHiddenGames);
startActivity(i);
}
@@ -592,8 +593,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case FULL_APP_LIST_ID:
case APP_LIST_ID:
doAppList(computer.details, false);
doAppList(computer.details, false, item.getItemId() == FULL_APP_LIST_ID);
return true;
case RESUME_ID:
@@ -625,6 +627,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Dialog.displayDialog(PcView.this, getResources().getString(R.string.title_details), computer.details.toString(), false);
return true;
case TEST_NETWORK_ID:
ServerHelper.doNetworkTest(PcView.this);
return true;
default:
return super.onContextItemSelected(item);
}
@@ -635,6 +641,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
new DiskAssetLoader(this).deleteAssetsForComputer(details.uuid);
// Delete hidden games preference value
getSharedPreferences(AppView.HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE)
.edit()
.remove(details.uuid)
.apply();
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
@@ -711,7 +723,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Pair an unpaired machine by default
doPair(computer.details);
} else {
doAppList(computer.details, false);
doAppList(computer.details, false, false);
}
}
});
@@ -41,6 +41,7 @@ public class MediaCodecHelper {
private static final List<String> qualcommDecoderPrefixes;
private static boolean isLowEndSnapdragon = false;
private static boolean isAdreno620 = false;
private static boolean initialized = false;
static {
@@ -231,19 +232,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 +263,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 +365,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) {
@@ -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,38 @@ 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) {
this.hiddenAppIds.clear();
this.hiddenAppIds.addAll(newHiddenAppIds);
// 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);
}
}
notifyDataSetChanged();
}
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
if (prefs.smallIconMode) {
return R.layout.app_grid_item_small;
@@ -88,8 +112,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 +122,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 +164,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);
@@ -231,19 +231,19 @@ public class NvConnection {
try {
if (!startApp()) {
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, 0, 0);
return;
}
context.connListener.stageComplete(appName);
} catch (GfeHttpResponseException e) {
e.printStackTrace();
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, e.getErrorCode());
context.connListener.stageFailed(appName, 0, e.getErrorCode());
return;
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, MoonBridge.ML_PORT_FLAG_TCP_47984 | MoonBridge.ML_PORT_FLAG_TCP_47989, 0);
return;
}
@@ -256,7 +256,7 @@ public class NvConnection {
connectionAllowed.acquire();
} catch (InterruptedException e) {
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, 0, 0);
return;
}
@@ -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);
@@ -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));
@@ -36,6 +36,25 @@ 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_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;
private static NvConnectionListener connectionListener;
@@ -186,7 +205,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 +290,15 @@ public class MoonBridge {
public static native int getPendingVideoFrames();
public static native int testClientConnectivity(String testServerHostName, int referencePort, int testFlags);
public static native int getPortFromPortFlagIndex(int portFlagIndex);
public static native String getProtocolFromPortFlagIndex(int portFlagIndex);
public static native int getPortFlagsFromStage(int stage);
public static native int getPortFlagsFromTerminationErrorCode(int errorCode);
public static native void init();
}
@@ -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() {
@@ -6,6 +6,7 @@ import android.widget.Toast;
import com.limelight.AppView;
import com.limelight.Game;
import com.limelight.PcView;
import com.limelight.R;
import com.limelight.ShortcutTrampoline;
import com.limelight.binding.PlatformBinding;
@@ -14,6 +15,7 @@ import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.jni.MoonBridge;
import org.xmlpull.v1.XmlPullParserException;
@@ -23,6 +25,8 @@ import java.net.UnknownHostException;
import java.security.cert.CertificateEncodingException;
public class ServerHelper {
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
return computer.activeAddress;
}
@@ -76,6 +80,42 @@ public class ServerHelper {
parent.startActivity(createStartIntent(parent, app, computer, managerBinder));
}
public static void doNetworkTest(final Activity parent) {
new Thread(new Runnable() {
@Override
public void run() {
SpinnerDialog spinnerDialog = SpinnerDialog.displayDialog(parent,
parent.getResources().getString(R.string.nettest_title_waiting),
parent.getResources().getString(R.string.nettest_text_waiting),
false);
int ret = MoonBridge.testClientConnectivity(CONNECTION_TEST_SERVER, 443, MoonBridge.ML_PORT_FLAG_ALL);
spinnerDialog.dismiss();
String dialogSummary;
if (ret == MoonBridge.ML_TEST_RESULT_INCONCLUSIVE) {
dialogSummary = parent.getResources().getString(R.string.nettest_text_inconclusive);
}
else if (ret == 0) {
dialogSummary = parent.getResources().getString(R.string.nettest_text_success);
}
else {
dialogSummary = parent.getResources().getString(R.string.nettest_text_failure);
for (int i = 0; i < 32; i++) {
if ((ret & (1 << i)) != 0) {
dialogSummary += MoonBridge.getProtocolFromPortFlagIndex(i) + " " + MoonBridge.getPortFromPortFlagIndex(i) + "\n";
}
}
}
Dialog.displayDialog(parent,
parent.getResources().getString(R.string.nettest_title_done),
dialogSummary,
false);
}
}).start();
}
public static void doQuit(final Activity parent,
final ComputerDetails computer,
final NvApp app,
@@ -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 \
@@ -101,4 +101,37 @@ Java_com_limelight_nvstream_jni_MoonBridge_getPendingAudioDuration(JNIEnv *env,
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPendingVideoFrames(JNIEnv *env, jclass clazz) {
return LiGetPendingVideoFrames();
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_testClientConnectivity(JNIEnv *env, jclass clazz, jstring testServerHostName, jint referencePort, jint testFlags) {
int ret;
const char* testServerHostNameStr = (*env)->GetStringUTFChars(env, testServerHostName, NULL);
ret = LiTestClientConnectivity(testServerHostNameStr, (unsigned short)referencePort, testFlags);
(*env)->ReleaseStringUTFChars(env, testServerHostName, testServerHostNameStr);
return ret;
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFromPortFlagIndex(JNIEnv *env, jclass clazz, jint portFlagIndex) {
return LiGetPortFromPortFlagIndex(portFlagIndex);
}
JNIEXPORT jstring JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getProtocolFromPortFlagIndex(JNIEnv *env, jclass clazz, jint portFlagIndex) {
int protocol = LiGetProtocolFromPortFlagIndex(portFlagIndex);
return (*env)->NewStringUTF(env, protocol == IPPROTO_TCP ? "TCP" : "UDP");
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFlagsFromStage(JNIEnv *env, jclass clazz, jint stage) {
return LiGetPortFlagsFromStage(stage);
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFlagsFromTerminationErrorCode(JNIEnv *env, jclass clazz, jint errorCode) {
return LiGetPortFlagsFromTerminationErrorCode(errorCode);
}
+13 -1
View File
@@ -15,13 +15,24 @@
<string name="help_loading_msg">Loading help page…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">View Game List</string>
<string name="pcview_menu_app_list">View Apps</string>
<string name="pcview_menu_full_app_list">View Hidden Apps</string>
<string name="pcview_menu_pair_pc">Pair with PC</string>
<string name="pcview_menu_unpair_pc">Unpair</string>
<string name="pcview_menu_send_wol">Send Wake-On-LAN request</string>
<string name="pcview_menu_delete_pc">Delete PC</string>
<string name="pcview_menu_test_network">Test Network Connection</string>
<string name="pcview_menu_details">View Details</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Testing Network Connection</string>
<string name="nettest_text_waiting">Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.\n\nThis may take a few seconds…</string>
<string name="nettest_title_done">Network Test Complete</string>
<string name="nettest_text_success">Your network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC\'s firewall settings.\n\nIf you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your PC and run the included Internet Streaming Tester to check your PC\'s Internet connection.</string>
<string name="nettest_text_inconclusive">The network test could not be performed because none of Moonlight\'s connection testing servers were reachable. Check your Internet connection or try again later.</string>
<string name="nettest_text_failure">Your device\'s current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.\n\nThe following network ports were blocked:\n</string>
<string name="nettest_text_blocked">Your device\'s current network connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</string>
<!-- Pair messages -->
<string name="pairing">Pairing…</string>
<string name="pair_pc_offline">Computer is offline</string>
@@ -99,6 +110,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>
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.0.1'
}
}
@@ -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