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" /> + +