From 512e98f2701585dca26e460f56a730362012c9cf Mon Sep 17 00:00:00 2001 From: SlpyLaw Date: Thu, 7 May 2026 00:24:17 +0200 Subject: [PATCH] fix(android): mediacodec-copy for screen capture visibility (#129) Use mediacodec-copy as default Android hwdec so decoded frames are not stuck in protected surfaces that screen share (Discord, Meet) shows as black. Route all Android VideoController hwdec through getDefaultHwdec() by passing null from main playback, home preview, and media bar (TV previously forced auto). Document that Flutter must stay RenderMode.surface: TextureView would hide the media_kit SurfaceView behind a non-punch-through layer. Co-authored-by: Cursor --- .../main/kotlin/org/moonfin/androidtv/MainActivity.kt | 10 +++++++++- lib/playback/media_kit_player_backend.dart | 9 +++++---- lib/ui/screens/home/home_screen.dart | 4 ++-- lib/ui/widgets/media_bar.dart | 4 ++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/kotlin/org/moonfin/androidtv/MainActivity.kt b/android/app/src/main/kotlin/org/moonfin/androidtv/MainActivity.kt index f9879a5c..1644f1d2 100644 --- a/android/app/src/main/kotlin/org/moonfin/androidtv/MainActivity.kt +++ b/android/app/src/main/kotlin/org/moonfin/androidtv/MainActivity.kt @@ -1,4 +1,4 @@ -package org.moonfin.androidtv +package org.moonfin.androidtv import android.app.PendingIntent import android.app.PictureInPictureParams @@ -35,6 +35,8 @@ import com.google.android.gms.cast.framework.CastSession import com.google.android.gms.cast.framework.SessionManagerListener import com.google.android.gms.common.images.WebImage import com.ryanheise.audioservice.AudioServiceActivity +import io.flutter.embedding.android.RenderMode +import io.flutter.embedding.android.TransparencyMode import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodChannel @@ -376,6 +378,12 @@ class MainActivity : AudioServiceActivity() { } } + // Must stay surface: media_kit draws video in a native SurfaceView behind Flutter. + // RenderMode.texture (FlutterTextureView) is opaque for that stack — video stays black. + override fun getRenderMode(): RenderMode = RenderMode.surface + + override fun getTransparencyMode(): TransparencyMode = TransparencyMode.transparent + override fun onUserLeaveHint() { super.onUserLeaveHint() requestEnterPiPIfEligible() diff --git a/lib/playback/media_kit_player_backend.dart b/lib/playback/media_kit_player_backend.dart index 8e9acc69..1927e6b9 100644 --- a/lib/playback/media_kit_player_backend.dart +++ b/lib/playback/media_kit_player_backend.dart @@ -1,4 +1,4 @@ -import 'dart:async'; +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -134,10 +134,11 @@ class MediaKitPlayerBackend implements PlayerBackend { Future Function(int handle)? onNativeHandleReady, }) { final hwDecodingEnabled = prefs.get(UserPreferences.hardwareDecoding); + // Android: null → AndroidVideoController getDefaultHwdec() (mediacodec-copy). final String? hwdec = hwDecodingEnabled - ? ((PlatformDetection.isAndroid && PlatformDetection.isTV) - ? 'auto' - : (PlatformDetection.isLinux ? 'auto-safe' : null)) + ? (PlatformDetection.isAndroid + ? null + : (PlatformDetection.isLinux ? 'auto-safe' : null)) : 'no'; final player = Player( diff --git a/lib/ui/screens/home/home_screen.dart b/lib/ui/screens/home/home_screen.dart index 8e1e9ea3..2f5982b9 100644 --- a/lib/ui/screens/home/home_screen.dart +++ b/lib/ui/screens/home/home_screen.dart @@ -886,8 +886,8 @@ class _ContentRowsState extends State<_ContentRows> if (!hwDecodingEnabled) { return 'no'; } - if (PlatformDetection.isAndroid && PlatformDetection.isTV) { - return 'auto'; + if (PlatformDetection.isAndroid) { + return null; } if (PlatformDetection.isLinux) { return 'auto-safe'; diff --git a/lib/ui/widgets/media_bar.dart b/lib/ui/widgets/media_bar.dart index 2e0eb9be..bcff70fe 100644 --- a/lib/ui/widgets/media_bar.dart +++ b/lib/ui/widgets/media_bar.dart @@ -609,8 +609,8 @@ class _MediaBarState extends State if (!widget.prefs.get(UserPreferences.hardwareDecoding)) { return 'no'; } - if (PlatformDetection.isAndroid && PlatformDetection.isTV) { - return 'auto'; + if (PlatformDetection.isAndroid) { + return null; } if (PlatformDetection.isLinux) { return 'auto-safe';