diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java
index 992fc00a71..fff1711a20 100755
--- a/app/src/main/java/com/limelight/Game.java
+++ b/app/src/main/java/com/limelight/Game.java
@@ -227,9 +227,14 @@ public class Game extends AppCompatActivity implements SurfaceHolder.Callback,
private View performanceOverlayView;
private TextView performanceOverlayLite;
+
+ private TextView performanceOverlayMini;
private TextView performanceOverlayBig;
+ private TextView androidTvForceGpuComposition;
+ private boolean gpuCompositionToggle = false;
+
private MediaCodecDecoderRenderer decoderRenderer;
private boolean reportedCrash;
@@ -521,8 +526,12 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
performanceOverlayLite = findViewById(R.id.performanceOverlayLite);
+ performanceOverlayMini = findViewById(R.id.performanceOverlayMini);
+
performanceOverlayBig = findViewById(R.id.performanceOverlayBig);
+ androidTvForceGpuComposition = findViewById(R.id.androidTvForceGpuComposition);
+
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -633,14 +642,21 @@ public boolean onCapturedPointer(View view, MotionEvent motionEvent) {
// Check if the user has enabled performance stats overlay
if (prefConfig.enablePerfOverlay) {
performanceOverlayView.setVisibility(View.VISIBLE);
+
if (prefConfig.enablePerfOverlayLite) {
performanceOverlayLite.setVisibility(View.VISIBLE);
if(prefConfig.enablePerfOverlayLiteDialog){
performanceOverlayLite.setOnClickListener(v -> showGameMenu(null));
}
+ } else if (prefConfig.enablePerfOverlayMini) {
+ performanceOverlayMini.setVisibility(View.VISIBLE);
+ if(prefConfig.enablePerfOverlayMiniDialog){
+ performanceOverlayMini.setOnClickListener(v -> showGameMenu(null));
+ }
} else {
performanceOverlayBig.setVisibility(View.VISIBLE);
}
+
if (prefConfig.enablePerfOverlayBottom) {
//performanceOverlayView.getLayoutParams().layout_gravity = Gravity.BOTTOM;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) performanceOverlayView.getLayoutParams();
@@ -649,6 +665,15 @@ public boolean onCapturedPointer(View view, MotionEvent motionEvent) {
}
}
+ // Configure Force GPU Composition for Android TV
+ if (androidTvForceGpuComposition != null) {
+ if (prefConfig.enableAndroidTvForceGpuComposition) {
+ androidTvForceGpuComposition.setVisibility(View.VISIBLE);
+ } else {
+ androidTvForceGpuComposition.setVisibility(View.GONE);
+ }
+ }
+
decoderRenderer = new MediaCodecDecoderRenderer(
this,
prefConfig,
@@ -3934,9 +3959,17 @@ public void onPerfUpdate(final String text) {
public void run() {
if(prefConfig.enablePerfOverlayLite){
performanceOverlayLite.setText(text);
+ }else if(prefConfig.enablePerfOverlayMini){
+ performanceOverlayMini.setText(text);
}else{
performanceOverlayBig.setText(text);
}
+
+ // Toggle GPU composition on Android TV by alternating an invisible character
+ if (androidTvForceGpuComposition != null && prefConfig.enableAndroidTvForceGpuComposition) {
+ gpuCompositionToggle = !gpuCompositionToggle;
+ androidTvForceGpuComposition.setText(gpuCompositionToggle ? "·" : ".");
+ }
}
});
}
@@ -4198,6 +4231,8 @@ public void toggleHUD() {
performanceOverlayView.setVisibility(View.VISIBLE);
if(prefConfig.enablePerfOverlayLite){
performanceOverlayLite.setVisibility(View.VISIBLE);
+ }else if(prefConfig.enablePerfOverlayMini){
+ performanceOverlayMini.setVisibility(View.VISIBLE);
}else{
performanceOverlayBig.setVisibility(View.VISIBLE);
}
diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
index b131684e2b..c0e5e9ff28 100755
--- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
+++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
@@ -1791,7 +1791,48 @@ public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int dec
float decodeTimeMs = (float)lastTwo.decoderTimeMs / lastTwo.totalFramesReceived;
long rttInfo = MoonBridge.getEstimatedRttInfo();
StringBuilder sb = new StringBuilder();
- if(prefs.enablePerfOverlayLite){
+ if(prefs.enablePerfOverlayMini){
+ if(TrafficStatsHelper.getPackageRxBytes(Process.myUid()) != TrafficStats.UNSUPPORTED){
+ long netData=TrafficStatsHelper.getPackageRxBytes(Process.myUid())+TrafficStatsHelper.getPackageTxBytes(Process.myUid());
+ if(lastNetDataNum!=0){
+ float realtimeNetData=(netData-lastNetDataNum)/1024f;
+ if(realtimeNetData>=1000){
+ sb.append("BW: ").append(String.format("%.1f", realtimeNetData/1024f)).append(" M/s\n");
+ }else{
+ sb.append("BW: ").append(String.format("%.1f", realtimeNetData)).append(" K/s\n");
+ }
+ }
+ lastNetDataNum=netData;
+ }
+ sb.append("PL: ").append(String.format("%.0f", (float)lastTwo.framesLost / lastTwo.totalFrames * 100)).append("%\n");
+ sb.append("Net: ").append((int)(rttInfo >> 32)).append("ms | Dec: ").append(String.format("%.1f", decodeTimeMs)).append("ms\n");
+ sb.append(String.format("%.2f", fps.totalFps)).append(" FPS");
+
+ } else if(prefs.enablePerfOverlayLite){
+ if(TrafficStatsHelper.getPackageRxBytes(Process.myUid()) != TrafficStats.UNSUPPORTED){
+ long netData=TrafficStatsHelper.getPackageRxBytes(Process.myUid())+TrafficStatsHelper.getPackageTxBytes(Process.myUid());
+ if(lastNetDataNum!=0){
+ sb.append(context.getString(R.string.perf_overlay_lite_bandwidth) + ": ");
+ float realtimeNetData=(netData-lastNetDataNum)/1024f;
+ if(realtimeNetData>=1000){
+ sb.append(String.format("%.2f", realtimeNetData/1024f) +"M/s\t ");
+ }else{
+ sb.append(String.format("%.2f", realtimeNetData) +"K/s\t ");
+ }
+ }
+ lastNetDataNum=netData;
+ }
+ // sb.append("分辨率:");
+ // sb.append(initialWidth + "x" + initialHeight);
+ sb.append(context.getString(R.string.perf_overlay_lite_network_decoding_delay) + ": ");
+ sb.append(context.getString(R.string.perf_overlay_lite_net,(int)(rttInfo >> 32)));
+ sb.append(" / ");
+ sb.append(context.getString(R.string.perf_overlay_lite_dectime,decodeTimeMs));
+ sb.append("\t");
+ sb.append(context.getString(R.string.perf_overlay_lite_packet_loss) + ": ");
+ sb.append(context.getString(R.string.perf_overlay_lite_netdrops,(float)lastTwo.framesLost / lastTwo.totalFrames * 100));
+ sb.append("\t FPS:");
+ sb.append(context.getString(R.string.perf_overlay_lite_fps, fps.totalFps));
if(TrafficStatsHelper.getPackageRxBytes(Process.myUid()) != TrafficStats.UNSUPPORTED){
long netData=TrafficStatsHelper.getPackageRxBytes(Process.myUid())+TrafficStatsHelper.getPackageTxBytes(Process.myUid());
if(lastNetDataNum!=0){
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index 96249d87ac..440c5a280a 100755
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -276,8 +276,14 @@ public enum AnalogStickForScrolling {
public boolean enablePerfOverlayLiteDialog;
+ public boolean enablePerfOverlayMini;
+
+ public boolean enablePerfOverlayMiniDialog;
+
public boolean enablePerfOverlayBottom;
+ public boolean enableAndroidTvForceGpuComposition;
+
public boolean enableLatencyToast;
public boolean enableBackMenu;
public boolean enableFloatingButton;
@@ -923,7 +929,9 @@ else if (audioConfig.equals("51")) {
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
config.enablePerfLogging = prefs.getBoolean(ENABLE_PERF_LOGGING, DEFAULT_ENABLE_PERF_LOGGING);
config.enablePerfOverlayLite = prefs.getBoolean("checkbox_enable_perf_overlay_lite",DEFAULT_ENABLE_PERF_OVERLAY);
+ config.enablePerfOverlayMini = prefs.getBoolean("checkbox_enable_perf_overlay_mini",DEFAULT_ENABLE_PERF_OVERLAY);
config.enablePerfOverlayBottom = prefs.getBoolean("checkbox_enable_perf_overlay_bottom",DEFAULT_PERF_OVERLAY_BOTTOM);
+ config.enableAndroidTvForceGpuComposition = prefs.getBoolean("checkbox_enable_android_tv_force_gpu_composition", false);
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);
@@ -981,7 +989,7 @@ else if (audioConfig.equals("51")) {
config.enableMultiTouchGestures = prefs.getBoolean("checkbox_multi_touch_gestures", false);
-
+
config.enablePerfOverlayLiteDialog=prefs.getBoolean("checkbox_enable_perf_overlay_lite_dialog",false);
config.disableDefaultExtraKeys =prefs.getBoolean("checkbox_enable_clear_default_special_button", false);
diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java
index 5b2d20de13..719cc1c7a0 100755
--- a/app/src/main/java/com/limelight/preferences/StreamSettings.java
+++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java
@@ -956,6 +956,36 @@ public boolean onPreferenceClick(@NonNull Preference preference) {
}
});
}
+
+ // Mutual exclusion between Lite and Mini overlay modes
+ CheckBoxPreference litePref = findPreference("checkbox_enable_perf_overlay_lite");
+ CheckBoxPreference miniPref = findPreference("checkbox_enable_perf_overlay_mini");
+
+ if (litePref != null && miniPref != null) {
+ litePref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Boolean isEnabled = (Boolean) newValue;
+ if (isEnabled && miniPref.isChecked()) {
+ // Disable mini when enabling lite
+ miniPref.setChecked(false);
+ }
+ return true;
+ }
+ });
+
+ miniPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Boolean isEnabled = (Boolean) newValue;
+ if (isEnabled && litePref.isChecked()) {
+ // Disable lite when enabling mini
+ litePref.setChecked(false);
+ }
+ return true;
+ }
+ });
+ }
}
private void removeEntryFromListAndSetValue(String resolutionPrefString, String entryToRemove, String nextDefault) {
diff --git a/app/src/main/res/layout/activity_game.xml b/app/src/main/res/layout/activity_game.xml
index 8f388d00be..0f3e878ccf 100755
--- a/app/src/main/res/layout/activity_game.xml
+++ b/app/src/main/res/layout/activity_game.xml
@@ -86,6 +86,40 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 95f64c92a5..d2587b8881 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -450,6 +450,8 @@
If you still like the old style of official virtual gamepad buttons, you can select this option.\nButton shapes can be square if you selected the bottom virtual button [Normal buttons are square buttons]
Enable Lite mode
Display simplified performance information: Network Speed / Latency / Decode / Packet Loss Rate / FPS
+ Enable Mini mode
+ Display simplified performance mini information
Enable Performance Logging
"Stores performance data per configuration. (Offline only) "
Lite mode click to pop up quick options
@@ -698,6 +700,8 @@
Save changes to zoom and pan position between sessions
Move overlay to bottom
Useful when Display in Top Center and Lite mode are enabled during non-native resolution streaming.
+ Force GPU Composition (Android TV)
+ Fixes frame sync issues on Android TV by forcing GPU composition. Enable if video playback has stuttering or frame drops.
Edit Profile
New Profile
Profile not found
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 5b009c891f..6285e6158a 100755
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -907,6 +907,13 @@
android:summary="@string/summary_checkbox_enable_perf_overlay_lite_dialog"
android:title="@string/title_checkbox_enable_perf_overlay_lite_dialog"
app:iconSpaceReserved="false" />
+
+