diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java
index 87b9bbad..e0f3a62c 100644
--- a/app/src/main/java/com/limelight/Game.java
+++ b/app/src/main/java/com/limelight/Game.java
@@ -344,8 +344,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Hopefully, we can get rid of this once someone comes up with a better way
// to track the state of the pipeline and time frames.
int roundedRefreshRate = Math.round(displayRefreshRate);
- if (!prefConfig.disableFrameDrop && prefConfig.fps >= roundedRefreshRate) {
- if (roundedRefreshRate <= 49) {
+ if ((!prefConfig.disableFrameDrop || prefConfig.unlockFps) && prefConfig.fps >= roundedRefreshRate) {
+ if (prefConfig.unlockFps) {
+ // Use frame drops when rendering above the screen frame rate
+ decoderRenderer.enableLegacyFrameDropRendering();
+ LimeLog.info("Using drop mode for FPS > Hz");
+ }
+ else if (roundedRefreshRate <= 49) {
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
decoderRenderer.enableLegacyFrameDropRendering();
LimeLog.info("Bogus refresh rate: "+roundedRefreshRate);
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index b7a53b85..397c978a 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -34,6 +34,7 @@ public class PreferenceConfiguration {
private static final String BIND_ALL_USB_STRING = "checkbox_usb_bind_all";
private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation";
private static final String MOUSE_NAV_BUTTONS_STRING = "checkbox_mouse_nav_buttons";
+ static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
static final String DEFAULT_RESOLUTION = "720p";
static final String DEFAULT_FPS = "60";
@@ -56,6 +57,7 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_BIND_ALL_USB = false;
private static final boolean DEFAULT_MOUSE_EMULATION = true;
private static final boolean DEFAULT_MOUSE_NAV_BUTTONS = false;
+ private static final boolean DEFAULT_UNLOCK_FPS = false;
public static final int FORCE_H265_ON = -1;
public static final int AUTOSELECT_H265 = 0;
@@ -76,6 +78,7 @@ public class PreferenceConfiguration {
public boolean bindAllUsb;
public boolean mouseEmulation;
public boolean mouseNavButtons;
+ public boolean unlockFps;
private static int getHeightFromResolutionString(String resString) {
if (resString.equalsIgnoreCase("360p")) {
@@ -208,6 +211,7 @@ public class PreferenceConfiguration {
.remove(FPS_PREF_STRING)
.remove(VIDEO_FORMAT_PREF_STRING)
.remove(ENABLE_HDR_PREF_STRING)
+ .remove(UNLOCK_FPS_STRING)
.apply();
}
@@ -309,6 +313,7 @@ public class PreferenceConfiguration {
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION);
config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS);
+ config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
return config;
}
diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java
index e657c8ca..8f9a3481 100644
--- a/app/src/main/java/com/limelight/preferences/StreamSettings.java
+++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java
@@ -6,6 +6,7 @@ import android.media.MediaCodecInfo;
import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
+import android.os.Handler;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
@@ -24,6 +25,12 @@ import com.limelight.utils.UiHelper;
public class StreamSettings extends Activity {
private PreferenceConfiguration previousPrefs;
+ void reloadSettings() {
+ getFragmentManager().beginTransaction().replace(
+ R.id.stream_settings, new SettingsFragment()
+ ).commit();
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -33,9 +40,7 @@ public class StreamSettings extends Activity {
UiHelper.setLocale(this);
setContentView(R.layout.activity_stream_settings);
- getFragmentManager().beginTransaction().replace(
- R.id.stream_settings, new SettingsFragment()
- ).commit();
+ reloadSettings();
UiHelper.notifyNewRootView(this);
}
@@ -58,9 +63,17 @@ public class StreamSettings extends Activity {
public static class SettingsFragment extends PreferenceFragment {
- private static void removeValue(ListPreference pref, String value) {
+ private void setValue(String preferenceKey, String value) {
+ ListPreference pref = (ListPreference) findPreference(preferenceKey);
+
+ pref.setValue(value);
+ }
+
+ private void removeValue(String preferenceKey, String value, Runnable onMatched) {
int matchingCount = 0;
+ ListPreference pref = (ListPreference) findPreference(preferenceKey);
+
// Count the number of matching entries we'll be removing
for (CharSequence seq : pref.getEntryValues()) {
if (seq.toString().equalsIgnoreCase(value)) {
@@ -83,15 +96,34 @@ public class StreamSettings extends Activity {
outIndex++;
}
+ if (pref.getValue().equalsIgnoreCase(value)) {
+ onMatched.run();
+ }
+
// Update the preference with the new list
pref.setEntries(entries);
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);
+ }
+ if (fps == null) {
+ fps = prefs.getString(PreferenceConfiguration.FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS);
+ }
+
+ prefs.edit()
+ .putInt(PreferenceConfiguration.BITRATE_PREF_STRING,
+ PreferenceConfiguration.getDefaultBitrate(res, fps))
+ .apply();
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
addPreferencesFromResource(R.xml.preferences);
PreferenceScreen screen = getPreferenceScreen();
@@ -195,34 +227,69 @@ public class StreamSettings extends Activity {
LimeLog.info("Maximum resolution slot: "+maxSupportedResW);
- ListPreference resPref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
if (maxSupportedResW != 0) {
if (maxSupportedResW < 3840) {
// 4K is unsupported
- removeValue(resPref, "4K");
+ removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "4K", new Runnable() {
+ @Override
+ public void run() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
+ setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p");
+ resetBitrateToDefault(prefs, null, null);
+ }
+ });
}
if (maxSupportedResW < 2560) {
// 1440p is unsupported
- removeValue(resPref, "1440p");
+ removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p", new Runnable() {
+ @Override
+ public void run() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
+ setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p");
+ resetBitrateToDefault(prefs, null, null);
+ }
+ });
}
if (maxSupportedResW < 1920) {
// 1080p is unsupported
- removeValue(resPref, "1080p");
+ removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p", new Runnable() {
+ @Override
+ public void run() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
+ setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "720p");
+ resetBitrateToDefault(prefs, null, null);
+ }
+ });
}
// Never remove 720p
}
}
- ListPreference fpsPref = (ListPreference) findPreference(PreferenceConfiguration.FPS_PREF_STRING);
- // We give some extra room in case the FPS is rounded down
- if (maxSupportedFps < 118) {
- removeValue(fpsPref, "120");
+ if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) {
+ // We give some extra room in case the FPS is rounded down
+ if (maxSupportedFps < 118) {
+ removeValue(PreferenceConfiguration.FPS_PREF_STRING, "120", new Runnable() {
+ @Override
+ public void run() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
+ setValue(PreferenceConfiguration.FPS_PREF_STRING, "90");
+ resetBitrateToDefault(prefs, null, null);
+ }
+ });
+ }
+ if (maxSupportedFps < 88) {
+ // 1080p is unsupported
+ removeValue(PreferenceConfiguration.FPS_PREF_STRING, "90", new Runnable() {
+ @Override
+ public void run() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
+ setValue(PreferenceConfiguration.FPS_PREF_STRING, "60");
+ resetBitrateToDefault(prefs, null, null);
+ }
+ });
+ }
+ // Never remove 30 FPS or 60 FPS
}
- if (maxSupportedFps < 88) {
- // 1080p is unsupported
- removeValue(fpsPref, "90");
- }
- // Never remove 30 FPS or 60 FPS
// Remove HDR preference for devices below Nougat
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
@@ -263,12 +330,7 @@ public class StreamSettings extends Activity {
String valueStr = (String) newValue;
// Write the new bitrate value
- prefs.edit()
- .putInt(PreferenceConfiguration.BITRATE_PREF_STRING,
- PreferenceConfiguration.getDefaultBitrate(valueStr,
- prefs.getString(PreferenceConfiguration.FPS_PREF_STRING,
- PreferenceConfiguration.DEFAULT_FPS)))
- .apply();
+ resetBitrateToDefault(prefs, valueStr, null);
// Allow the original preference change to take place
return true;
@@ -281,12 +343,24 @@ public class StreamSettings extends Activity {
String valueStr = (String) newValue;
// Write the new bitrate value
- prefs.edit()
- .putInt(PreferenceConfiguration.BITRATE_PREF_STRING,
- PreferenceConfiguration.getDefaultBitrate(
- prefs.getString(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION),
- valueStr))
- .apply();
+ resetBitrateToDefault(prefs, null, valueStr);
+
+ // Allow the original preference change to take place
+ return true;
+ }
+ });
+ findPreference(PreferenceConfiguration.UNLOCK_FPS_STRING).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ // HACK: We need to let the preference change succeed before reinitializing to ensure
+ // it's reflected in the new layout.
+ final Handler h = new Handler();
+ h.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ ((StreamSettings)SettingsFragment.this.getActivity()).reloadSettings();
+ }
+ }, 500);
// Allow the original preference change to take place
return true;
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a01ed908..ed6f78a2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -175,6 +175,8 @@
May reduce micro-stuttering on some devices, but can increase latency
Change H.265 settings
H.265 lowers video bandwidth requirements but requires a very recent device
+ Unlock all FPS values
+ Streaming at 90 or 120 FPS may reduce latency on high-end devices but can cause lag or crashes on devices that can\'t support it
Enable HDR (Experimental)
Stream HDR when the game and PC GPU support it. HDR requires a GTX 1000 series GPU or later.
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 1f450b80..619b098f 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -140,6 +140,11 @@
android:entryValues="@array/video_format_values"
android:summary="@string/summary_video_format"
android:defaultValue="auto" />
+