WIP: Resolution dialog

This commit is contained in:
Cameron Gutman 2022-11-29 20:56:09 -06:00
parent 00415aac79
commit 41592b2ef4
5 changed files with 231 additions and 169 deletions

View File

@ -220,7 +220,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Enter landscape unless we're on a square screen // Enter landscape unless we're on a square screen
setPreferredOrientationForCurrentDisplay(); setPreferredOrientationForCurrentDisplay();
if (prefConfig.stretchVideo || shouldIgnoreInsetsForResolution(prefConfig.width, prefConfig.height)) { if (prefConfig.stretchVideo || prefConfig.resolution.fullScreen) {
// Allow the activity to layout under notches if the fill-screen option // Allow the activity to layout under notches if the fill-screen option
// was turned on by the user or it's a full-screen native resolution // was turned on by the user or it's a full-screen native resolution
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@ -456,7 +456,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
StreamConfiguration config = new StreamConfiguration.Builder() StreamConfiguration config = new StreamConfiguration.Builder()
.setResolution(prefConfig.width, prefConfig.height) .setResolution(prefConfig.resolution.width, prefConfig.resolution.height)
.setLaunchRefreshRate(prefConfig.fps) .setLaunchRefreshRate(prefConfig.fps)
.setRefreshRate(chosenFrameRate) .setRefreshRate(chosenFrameRate)
.setApp(new NvApp(appName != null ? appName : "app", appId, appSupportsHdr)) .setApp(new NvApp(appName != null ? appName : "app", appId, appSupportsHdr))
@ -551,12 +551,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
// For native resolution, we will lock the orientation to the one that matches the specified resolution // For native resolution, we will lock the orientation to the one that matches the specified resolution
if (PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height)) { if (prefConfig.resolution.type == PreferenceConfiguration.StreamResolution.ResolutionType.NATIVE) {
if (prefConfig.width > prefConfig.height) { if (prefConfig.resolution.portrait) {
desiredOrientation = Configuration.ORIENTATION_LANDSCAPE; desiredOrientation = Configuration.ORIENTATION_PORTRAIT;
} }
else { else {
desiredOrientation = Configuration.ORIENTATION_PORTRAIT; desiredOrientation = Configuration.ORIENTATION_LANDSCAPE;
} }
} }
@ -649,7 +649,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private PictureInPictureParams getPictureInPictureParams(boolean autoEnter) { private PictureInPictureParams getPictureInPictureParams(boolean autoEnter) {
PictureInPictureParams.Builder builder = PictureInPictureParams.Builder builder =
new PictureInPictureParams.Builder() new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height)) .setAspectRatio(new Rational(prefConfig.resolution.width, prefConfig.resolution.height))
.setSourceRectHint(new Rect( .setSourceRectHint(new Rect(
streamView.getLeft(), streamView.getTop(), streamView.getLeft(), streamView.getTop(),
streamView.getRight(), streamView.getBottom())); streamView.getRight(), streamView.getBottom()));
@ -771,26 +771,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Math.round(refreshRate) % prefConfig.fps <= 3; Math.round(refreshRate) % prefConfig.fps <= 3;
} }
private boolean shouldIgnoreInsetsForResolution(int width, int height) {
// Never ignore insets for non-native resolutions
if (!PreferenceConfiguration.isNativeResolution(width, height)) {
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display display = getWindowManager().getDefaultDisplay();
for (Display.Mode candidate : display.getSupportedModes()) {
// Ignore insets if this is an exact match for the display resolution
if ((width == candidate.getPhysicalWidth() && height == candidate.getPhysicalHeight()) ||
(height == candidate.getPhysicalWidth() && width == candidate.getPhysicalHeight())) {
return true;
}
}
}
return false;
}
private boolean mayReduceRefreshRate() { private boolean mayReduceRefreshRate() {
return prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS || return prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS ||
prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS || prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS ||
@ -805,7 +785,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// On M, we can explicitly set the optimal display mode // On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display.Mode bestMode = display.getMode(); Display.Mode bestMode = display.getMode();
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate()); boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate()); boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate());
@ -813,13 +792,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate(); boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() || boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
candidate.getPhysicalHeight() < bestMode.getPhysicalHeight(); candidate.getPhysicalHeight() < bestMode.getPhysicalHeight();
boolean resolutionFitsStream = candidate.getPhysicalWidth() >= prefConfig.width && boolean resolutionFitsStream = candidate.getPhysicalWidth() >= prefConfig.resolution.width &&
candidate.getPhysicalHeight() >= prefConfig.height; candidate.getPhysicalHeight() >= prefConfig.resolution.height;
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+ LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate()); candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
if (candidate.getPhysicalWidth() > 4096 && prefConfig.width <= 4096) { if (candidate.getPhysicalWidth() > 4096 && prefConfig.resolution.width <= 4096) {
// Avoid resolutions options above 4K to be safe // Avoid resolutions options above 4K to be safe
continue; continue;
} }
@ -827,7 +806,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// On non-4K streams, we force the resolution to never change unless it's above // 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, // 60 FPS, which may require a resolution reduction due to HDMI bandwidth limitations,
// or it's a native resolution stream. // or it's a native resolution stream.
if (prefConfig.width < 3840 && prefConfig.fps <= 60 && !isNativeResolutionStream) { if (prefConfig.resolution.width < 3840 && prefConfig.fps <= 60 &&
prefConfig.resolution.type != PreferenceConfiguration.StreamResolution.ResolutionType.NATIVE) {
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() || if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) { display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
continue; continue;
@ -940,7 +920,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
display.getSize(screenSize); display.getSize(screenSize);
double screenAspectRatio = ((double)screenSize.y) / screenSize.x; double screenAspectRatio = ((double)screenSize.y) / screenSize.x;
double streamAspectRatio = ((double)prefConfig.height) / prefConfig.width; double streamAspectRatio = ((double)prefConfig.resolution.height) / prefConfig.resolution.width;
if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) { if (Math.abs(screenAspectRatio - streamAspectRatio) < 0.001) {
LimeLog.info("Stream has compatible aspect ratio with output display"); LimeLog.info("Stream has compatible aspect ratio with output display");
aspectRatioMatch = true; aspectRatioMatch = true;
@ -949,11 +929,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (prefConfig.stretchVideo || aspectRatioMatch) { if (prefConfig.stretchVideo || aspectRatioMatch) {
// Set the surface to the size of the video // Set the surface to the size of the video
streamView.getHolder().setFixedSize(prefConfig.width, prefConfig.height); streamView.getHolder().setFixedSize(prefConfig.resolution.width, prefConfig.resolution.height);
} }
else { else {
// Set the surface to scale based on the aspect ratio of the stream // Set the surface to scale based on the aspect ratio of the stream
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height); streamView.setDesiredAspectRatio((double)prefConfig.resolution.width / (double)prefConfig.resolution.height);
} }
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) || if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) ||

View File

@ -134,7 +134,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) { private boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps); MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.resolution.width, prefs.resolution.height, prefs.fps);
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints(); List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
if (perfPoints != null) { if (perfPoints != null) {
for (MediaCodecInfo.VideoCapabilities.PerformancePoint perfPoint : perfPoints) { for (MediaCodecInfo.VideoCapabilities.PerformancePoint perfPoint : perfPoints) {
@ -155,7 +155,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
try { try {
// We'll ask the decoder what it can do for us at this resolution and see if our // We'll ask the decoder what it can do for us at this resolution and see if our
// requested frame rate falls below or inside the range of achievable frame rates. // requested frame rate falls below or inside the range of achievable frame rates.
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height); Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.resolution.width, prefs.resolution.height);
if (fpsRange != null) { if (fpsRange != null) {
return prefs.fps <= fpsRange.getUpper(); return prefs.fps <= fpsRange.getUpper();
} }
@ -170,7 +170,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
// As a last resort, we will use areSizeAndRateSupported() which is explicitly NOT a // As a last resort, we will use areSizeAndRateSupported() which is explicitly NOT a
// performance metric, but it can work at least for the purpose of determining if // performance metric, but it can work at least for the purpose of determining if
// the codec is going to die when given a stream with the specified settings. // the codec is going to die when given a stream with the specified settings.
return caps.areSizeAndRateSupported(prefs.width, prefs.height, prefs.fps); return caps.areSizeAndRateSupported(prefs.resolution.width, prefs.resolution.height, prefs.fps);
} }
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) { private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
@ -211,7 +211,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
LimeLog.info("Forcing HEVC enabled for HDR streaming"); LimeLog.info("Forcing HEVC enabled for HDR streaming");
} }
// > 4K streaming also requires HEVC, so force it on there too. // > 4K streaming also requires HEVC, so force it on there too.
else if (prefs.width > 4096 || prefs.height > 4096) { else if (prefs.resolution.width > 4096 || prefs.resolution.height > 4096) {
LimeLog.info("Forcing HEVC enabled for over 4K streaming"); LimeLog.info("Forcing HEVC enabled for over 4K streaming");
} }
// Use HEVC if the H.264 decoder is unable to meet the performance point // Use HEVC if the H.264 decoder is unable to meet the performance point
@ -271,7 +271,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
int hevcOptimalSlicesPerFrame = 0; int hevcOptimalSlicesPerFrame = 0;
if (avcDecoder != null) { if (avcDecoder != null) {
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(avcDecoder.getName()); directSubmit = MediaCodecHelper.decoderCanDirectSubmit(avcDecoder.getName());
refFrameInvalidationAvc = MediaCodecHelper.decoderSupportsRefFrameInvalidationAvc(avcDecoder.getName(), prefs.height); refFrameInvalidationAvc = MediaCodecHelper.decoderSupportsRefFrameInvalidationAvc(avcDecoder.getName(), prefs.resolution.height);
avcOptimalSlicesPerFrame = MediaCodecHelper.getDecoderOptimalSlicesPerFrame(avcDecoder.getName()); avcOptimalSlicesPerFrame = MediaCodecHelper.getDecoderOptimalSlicesPerFrame(avcDecoder.getName());
if (directSubmit) { if (directSubmit) {

View File

@ -90,15 +90,15 @@ public class PreferenceConfiguration {
public static final int FRAME_PACING_CAP_FPS = 2; public static final int FRAME_PACING_CAP_FPS = 2;
public static final int FRAME_PACING_MAX_SMOOTHNESS = 3; public static final int FRAME_PACING_MAX_SMOOTHNESS = 3;
public static final String RES_360P = "640x360"; public static final String RES_360P = "s_640x360";
public static final String RES_480P = "854x480"; public static final String RES_480P = "s_854x480";
public static final String RES_720P = "1280x720"; public static final String RES_720P = "s_1280x720";
public static final String RES_1080P = "1920x1080"; public static final String RES_1080P = "s_1920x1080";
public static final String RES_1440P = "2560x1440"; public static final String RES_1440P = "s_2560x1440";
public static final String RES_4K = "3840x2160"; public static final String RES_4K = "s_3840x2160";
public static final String RES_NATIVE = "Native";
public int width, height, fps; public StreamResolution resolution;
public int fps;
public int bitrate; public int bitrate;
public int videoFormat; public int videoFormat;
public int deadzonePercentage; public int deadzonePercentage;
@ -125,7 +125,9 @@ public class PreferenceConfiguration {
public boolean enableAudioFx; public boolean enableAudioFx;
public boolean reduceRefreshRate; public boolean reduceRefreshRate;
public static boolean isNativeResolution(int width, int height) { // This is used only for migration to the new type code format of resolution strings
// It doesn't properly handle custom resolutions!
private static boolean legacyIsNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option // It's not a native resolution if it matches an existing resolution option
if (width == 640 && height == 360) { if (width == 640 && height == 360) {
return false; return false;
@ -175,6 +177,8 @@ public class PreferenceConfiguration {
} }
private static String convertFromLegacyResolutionString(String resString) { private static String convertFromLegacyResolutionString(String resString) {
if (!resString.contains("x")) {
// Convert from hardcoded strings to strings with dimensions embedded
if (resString.equalsIgnoreCase("360p")) { if (resString.equalsIgnoreCase("360p")) {
return RES_360P; return RES_360P;
} }
@ -198,36 +202,22 @@ public class PreferenceConfiguration {
return RES_720P; return RES_720P;
} }
} }
else if (Character.isDigit(resString.charAt(0))) {
private static int getWidthFromResolutionString(String resString) { // Convert to type code prefix format
return Integer.parseInt(resString.split("x")[0]); String[] dims = resString.split("x");
int width = Integer.parseInt(dims[0]);
int height = Integer.parseInt(dims[1]);
return new StreamResolution(width, height,
legacyIsNativeResolution(width, height) ? StreamResolution.ResolutionType.NATIVE : StreamResolution.ResolutionType.STANDARD)
.toPrefString();
} }
else {
private static int getHeightFromResolutionString(String resString) { return resString;
return Integer.parseInt(resString.split("x")[1]);
}
private static String getResolutionString(int width, int height) {
switch (height) {
case 360:
return RES_360P;
case 480:
return RES_480P;
default:
case 720:
return RES_720P;
case 1080:
return RES_1080P;
case 1440:
return RES_1440P;
case 2160:
return RES_4K;
} }
} }
public static int getDefaultBitrate(String resString, String fpsString) { public static int getDefaultBitrate(String resString, String fpsString) {
int width = getWidthFromResolutionString(resString); StreamResolution res = new StreamResolution(resString);
int height = getHeightFromResolutionString(resString);
int fps = Integer.parseInt(fpsString); int fps = Integer.parseInt(fpsString);
// This table prefers 16:10 resolutions because they are // This table prefers 16:10 resolutions because they are
@ -238,23 +228,23 @@ public class PreferenceConfiguration {
// This logic is shamelessly stolen from Moonlight Qt: // This logic is shamelessly stolen from Moonlight Qt:
// https://github.com/moonlight-stream/moonlight-qt/blob/master/app/settings/streamingpreferences.cpp // https://github.com/moonlight-stream/moonlight-qt/blob/master/app/settings/streamingpreferences.cpp
if (width * height <= 640 * 360) { if (res.width * res.height <= 640 * 360) {
return (int)(1000 * (fps / 30.0)); return (int)(1000 * (fps / 30.0));
} }
else if (width * height <= 854 * 480) { else if (res.width * res.height <= 854 * 480) {
return (int)(1500 * (fps / 30.0)); return (int)(1500 * (fps / 30.0));
} }
// This covers 1280x720 and 1280x800 too // This covers 1280x720 and 1280x800 too
else if (width * height <= 1366 * 768) { else if (res.width * res.height <= 1366 * 768) {
return (int)(5000 * (fps / 30.0)); return (int)(5000 * (fps / 30.0));
} }
else if (width * height <= 1920 * 1200) { else if (res.width * res.height <= 1920 * 1200) {
return (int)(10000 * (fps / 30.0)); return (int)(10000 * (fps / 30.0));
} }
else if (width * height <= 2560 * 1600) { else if (res.width * res.height <= 2560 * 1600) {
return (int)(20000 * (fps / 30.0)); return (int)(20000 * (fps / 30.0));
} }
else /* if (width * height <= 3840 * 2160) */ { else /* if (res.width * res.height <= 3840 * 2160) */ {
return (int)(40000 * (fps / 30.0)); return (int)(40000 * (fps / 30.0));
} }
} }
@ -381,55 +371,46 @@ public class PreferenceConfiguration {
String str = prefs.getString(LEGACY_RES_FPS_PREF_STRING, null); String str = prefs.getString(LEGACY_RES_FPS_PREF_STRING, null);
if (str != null) { if (str != null) {
if (str.equals("360p30")) { if (str.equals("360p30")) {
config.width = 640; config.resolution = new StreamResolution(640, 360, StreamResolution.ResolutionType.STANDARD);
config.height = 360;
config.fps = 30; config.fps = 30;
} }
else if (str.equals("360p60")) { else if (str.equals("360p60")) {
config.width = 640; config.resolution = new StreamResolution(640, 360, StreamResolution.ResolutionType.STANDARD);
config.height = 360;
config.fps = 60; config.fps = 60;
} }
else if (str.equals("720p30")) { else if (str.equals("720p30")) {
config.width = 1280; config.resolution = new StreamResolution(1280, 720, StreamResolution.ResolutionType.STANDARD);
config.height = 720;
config.fps = 30; config.fps = 30;
} }
else if (str.equals("720p60")) { else if (str.equals("720p60")) {
config.width = 1280; config.resolution = new StreamResolution(1280, 720, StreamResolution.ResolutionType.STANDARD);
config.height = 720;
config.fps = 60; config.fps = 60;
} }
else if (str.equals("1080p30")) { else if (str.equals("1080p30")) {
config.width = 1920; config.resolution = new StreamResolution(1920, 1080, StreamResolution.ResolutionType.STANDARD);
config.height = 1080;
config.fps = 30; config.fps = 30;
} }
else if (str.equals("1080p60")) { else if (str.equals("1080p60")) {
config.width = 1920; config.resolution = new StreamResolution(1920, 1080, StreamResolution.ResolutionType.STANDARD);
config.height = 1080;
config.fps = 60; config.fps = 60;
} }
else if (str.equals("4K30")) { else if (str.equals("4K30")) {
config.width = 3840; config.resolution = new StreamResolution(3840, 2160, StreamResolution.ResolutionType.STANDARD);
config.height = 2160;
config.fps = 30; config.fps = 30;
} }
else if (str.equals("4K60")) { else if (str.equals("4K60")) {
config.width = 3840; config.resolution = new StreamResolution(3840, 2160, StreamResolution.ResolutionType.STANDARD);
config.height = 2160;
config.fps = 60; config.fps = 60;
} }
else { else {
// Should never get here // Should never get here
config.width = 1280; config.resolution = new StreamResolution(1280, 720, StreamResolution.ResolutionType.STANDARD);
config.height = 720;
config.fps = 60; config.fps = 60;
} }
prefs.edit() prefs.edit()
.remove(LEGACY_RES_FPS_PREF_STRING) .remove(LEGACY_RES_FPS_PREF_STRING)
.putString(RESOLUTION_PREF_STRING, getResolutionString(config.width, config.height)) .putString(RESOLUTION_PREF_STRING, config.resolution.toPrefString())
.putString(FPS_PREF_STRING, ""+config.fps) .putString(FPS_PREF_STRING, ""+config.fps)
.apply(); .apply();
} }
@ -438,13 +419,12 @@ public class PreferenceConfiguration {
String resStr = prefs.getString(RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION); String resStr = prefs.getString(RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION);
// Convert legacy resolution strings to the new style // Convert legacy resolution strings to the new style
if (!resStr.contains("x")) { if (Character.isDigit(resStr.charAt(0)) || !resStr.contains("x")) {
resStr = PreferenceConfiguration.convertFromLegacyResolutionString(resStr); resStr = PreferenceConfiguration.convertFromLegacyResolutionString(resStr);
prefs.edit().putString(RESOLUTION_PREF_STRING, resStr).apply(); prefs.edit().putString(RESOLUTION_PREF_STRING, resStr).apply();
} }
config.width = PreferenceConfiguration.getWidthFromResolutionString(resStr); config.resolution = new StreamResolution(resStr);
config.height = PreferenceConfiguration.getHeightFromResolutionString(resStr);
config.fps = Integer.parseInt(prefs.getString(FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS)); config.fps = Integer.parseInt(prefs.getString(FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS));
} }
@ -508,4 +488,112 @@ public class PreferenceConfiguration {
return config; return config;
} }
public static class StreamResolution {
public int width;
public int height;
public ResolutionType type;
public boolean portrait;
public boolean fullScreen;
private static final char RES_START_DELIMITER = '_';
private static final char RES_DIM_DELIMITER = 'x';
private static final char TYPE_CODE_STANDARD = 's';
private static final char TYPE_CODE_NATIVE = 'n';
private static final char OPTION_CODE_PORTRAIT = 'p';
private static final char OPTION_CODE_FULL_SCREEN = 'f';
public enum ResolutionType {
STANDARD,
NATIVE
}
private StreamResolution(int width, int height, ResolutionType type) {
this.width = width;
this.height = height;
this.type = type;
// Just guess for the options, since we don't know for sure
this.portrait = height > width;
this.fullScreen = false;
}
public StreamResolution(int width, int height, ResolutionType type, boolean portrait, boolean fullScreen) {
this.width = width;
this.height = height;
this.type = type;
this.portrait = portrait;
this.fullScreen = fullScreen;
}
StreamResolution(String resString) {
// Type code is the first character in the string
switch (resString.charAt(0)) {
case TYPE_CODE_NATIVE:
this.type = StreamResolution.ResolutionType.NATIVE;
break;
case TYPE_CODE_STANDARD:
this.type = StreamResolution.ResolutionType.STANDARD;
break;
default:
throw new IllegalArgumentException("Invalid type code: " + resString.charAt(0));
}
// Parse options until we reach the resolution start delimiter
int currentIndex = 1;
while (resString.charAt(currentIndex) != RES_START_DELIMITER) {
switch (resString.charAt(currentIndex)) {
case OPTION_CODE_PORTRAIT:
portrait = true;
break;
case OPTION_CODE_FULL_SCREEN:
fullScreen = true;
break;
default:
throw new IllegalArgumentException("Invalid option code: " + resString.charAt(currentIndex));
}
currentIndex++;
}
// Skip the type code before splitting
String[] dims = resString.substring(currentIndex).split(""+RES_DIM_DELIMITER);
this.width = Integer.parseInt(dims[0]);
this.height = Integer.parseInt(dims[1]);
}
public String toPrefString() {
String str = "";
// Start with the type code
switch (type) {
case NATIVE:
str += TYPE_CODE_NATIVE;
break;
case STANDARD:
str += TYPE_CODE_STANDARD;
break;
default:
throw new IllegalArgumentException("Invalid type: "+type);
}
// Now any option codes that may apply
if (portrait) {
str += OPTION_CODE_PORTRAIT;
}
if (fullScreen) {
str += OPTION_CODE_FULL_SCREEN;
}
// And finally the dimensions
str += RES_START_DELIMITER;
str += width;
str += RES_DIM_DELIMITER;
str += height;
return str;
}
}
} }

View File

@ -122,19 +122,21 @@ public class StreamSettings extends Activity {
} }
public static class SettingsFragment extends PreferenceFragment { public static class SettingsFragment extends PreferenceFragment {
private int nativeResolutionStartIndex = Integer.MAX_VALUE;
private void setValue(String preferenceKey, String value) { private void setValue(String preferenceKey, String value) {
ListPreference pref = (ListPreference) findPreference(preferenceKey); ListPreference pref = (ListPreference) findPreference(preferenceKey);
pref.setValue(value); pref.setValue(value);
} }
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight, boolean insetsRemoved, boolean portrait) { private void addResolutionEntry(int width, int height, PreferenceConfiguration.StreamResolution.ResolutionType type,
boolean insetsRemoved, boolean portrait) {
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING); ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
PreferenceConfiguration.StreamResolution newRes =
new PreferenceConfiguration.StreamResolution(width, height, type, portrait, insetsRemoved);
String newName; String newName;
if (type == PreferenceConfiguration.StreamResolution.ResolutionType.NATIVE) {
if (insetsRemoved) { if (insetsRemoved) {
newName = getResources().getString(R.string.resolution_prefix_native_fullscreen); newName = getResources().getString(R.string.resolution_prefix_native_fullscreen);
} }
@ -142,7 +144,7 @@ public class StreamSettings extends Activity {
newName = getResources().getString(R.string.resolution_prefix_native); newName = getResources().getString(R.string.resolution_prefix_native);
} }
if (PreferenceConfiguration.isSquarishScreen(nativeWidth, nativeHeight)) { if (PreferenceConfiguration.isSquarishScreen(width, height)) {
if (portrait) { if (portrait) {
newName += " " + getResources().getString(R.string.resolution_prefix_native_portrait); newName += " " + getResources().getString(R.string.resolution_prefix_native_portrait);
} }
@ -150,16 +152,19 @@ public class StreamSettings extends Activity {
newName += " " + getResources().getString(R.string.resolution_prefix_native_landscape); newName += " " + getResources().getString(R.string.resolution_prefix_native_landscape);
} }
} }
}
else {
newName = null;
}
newName += " ("+nativeWidth+"x"+nativeHeight+")"; newName += " ("+width+"x"+height+")";
String newValue = nativeWidth+"x"+nativeHeight;
CharSequence[] values = pref.getEntryValues(); CharSequence[] values = pref.getEntryValues();
// Check if the native resolution is already present // Check if the new resolution is already present
for (CharSequence value : values) { for (CharSequence value : values) {
if (newValue.equals(value.toString())) { PreferenceConfiguration.StreamResolution existingResolution = new PreferenceConfiguration.StreamResolution(value.toString());
if (existingResolution.width == newRes.width && existingResolution.height == newRes.height) {
// It is present in the default list, so don't add it again // It is present in the default list, so don't add it again
return; return;
} }
@ -168,23 +173,23 @@ public class StreamSettings extends Activity {
CharSequence[] newEntries = Arrays.copyOf(pref.getEntries(), pref.getEntries().length + 1); CharSequence[] newEntries = Arrays.copyOf(pref.getEntries(), pref.getEntries().length + 1);
CharSequence[] newValues = Arrays.copyOf(values, values.length + 1); CharSequence[] newValues = Arrays.copyOf(values, values.length + 1);
// Add the new native option // Add the new resolution option
newEntries[newEntries.length - 1] = newName; newEntries[newEntries.length - 1] = newName;
newValues[newValues.length - 1] = newValue; newValues[newValues.length - 1] = newRes.toPrefString();
pref.setEntries(newEntries); pref.setEntries(newEntries);
pref.setEntryValues(newValues); pref.setEntryValues(newValues);
if (newValues.length - 1 < nativeResolutionStartIndex) {
nativeResolutionStartIndex = newValues.length - 1;
}
} }
private void addNativeResolutionEntries(int nativeWidth, int nativeHeight, boolean insetsRemoved) { private void addNativeResolutionEntries(int nativeWidth, int nativeHeight, boolean insetsRemoved) {
if (PreferenceConfiguration.isSquarishScreen(nativeWidth, nativeHeight)) { if (PreferenceConfiguration.isSquarishScreen(nativeWidth, nativeHeight)) {
addNativeResolutionEntry(nativeHeight, nativeWidth, insetsRemoved, true); addResolutionEntry(nativeHeight, nativeWidth,
PreferenceConfiguration.StreamResolution.ResolutionType.NATIVE,
insetsRemoved, true);
} }
addNativeResolutionEntry(nativeWidth, nativeHeight, insetsRemoved, false); addResolutionEntry(nativeWidth, nativeHeight,
PreferenceConfiguration.StreamResolution.ResolutionType.NATIVE,
insetsRemoved, false);
} }
private void removeValue(String preferenceKey, String value, Runnable onMatched) { private void removeValue(String preferenceKey, String value, Runnable onMatched) {
@ -608,20 +613,10 @@ public class StreamSettings extends Activity {
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
String valueStr = (String) newValue; String valueStr = (String) newValue;
PreferenceConfiguration.StreamResolution resolution = new PreferenceConfiguration.StreamResolution(valueStr);
// 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 this is native resolution, show the warning dialog
if (isNativeRes) { if (resolution.type == PreferenceConfiguration.StreamResolution.ResolutionType.NATIVE) {
Dialog.displayDialog(getActivity(), Dialog.displayDialog(getActivity(),
getResources().getString(R.string.title_native_res_dialog), getResources().getString(R.string.title_native_res_dialog),
getResources().getString(R.string.text_native_res_dialog), getResources().getString(R.string.text_native_res_dialog),

View File

@ -9,14 +9,13 @@
<item>@string/resolution_4k</item> <item>@string/resolution_4k</item>
</string-array> </string-array>
<!-- Keep this in sync with PreferenceConfiguration.isNativeResolution()! -->
<string-array name="resolution_values" translatable="false"> <string-array name="resolution_values" translatable="false">
<item>640x360</item> <item>s_640x360</item>
<item>854x480</item> <item>s_854x480</item>
<item>1280x720</item> <item>s_1280x720</item>
<item>1920x1080</item> <item>s_1920x1080</item>
<item>2560x1440</item> <item>s_2560x1440</item>
<item>3840x2160</item> <item>s_3840x2160</item>
</string-array> </string-array>
<string-array name="fps_names"> <string-array name="fps_names">