From 9f8167160342fabbec513bb734ea63485c4d8b69 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:00:29 +0100 Subject: [PATCH] fix: suppress repeated android log spam from MPV surface and stats polling - Deduplicate android-surface-size updates with lastSurfaceSize cache - Short-circuit ensureFlutterOverlayOnTop after initial application - Remove deprecated cache-used property query - Skip unavailable MPV properties on Android (display-fps, HDR metadata) - Gate video-dependent stats queries behind hasVideo check --- .../plezy/exoplayer/ExoPlayerPlugin.kt | 43 ++++---- .../com/edde746/plezy/mpv/MpvPlayerCore.kt | 42 +++++--- .../performance_stats_service.dart | 102 ++++++++++-------- 3 files changed, 112 insertions(+), 75 deletions(-) diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt index 7d82d0cb..0cbb13ca 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt @@ -478,7 +478,9 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, private fun getMpvStats(): Map { val mpv = mpvCore ?: return mapOf("playerType" to "mpv") - return mapOf( + val hasVideo = mpv.getProperty("video-params/w") != null + + val stats = mutableMapOf( "playerType" to "mpv", // Video metrics "video-codec" to mpv.getProperty("video-codec"), @@ -495,27 +497,32 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, "audio-bitrate" to mpv.getProperty("audio-bitrate"), // Performance metrics "total-avsync-change" to mpv.getProperty("total-avsync-change"), - "cache-used" to mpv.getProperty("cache-used"), "cache-speed" to mpv.getProperty("cache-speed"), - "display-fps" to mpv.getProperty("display-fps"), "frame-drop-count" to mpv.getProperty("frame-drop-count"), "decoder-frame-drop-count" to mpv.getProperty("decoder-frame-drop-count"), "demuxer-cache-duration" to mpv.getProperty("demuxer-cache-duration"), - // Color/Format properties - "video-params/pixelformat" to mpv.getProperty("video-params/pixelformat"), - "video-params/hw-pixelformat" to mpv.getProperty("video-params/hw-pixelformat"), - "video-params/colormatrix" to mpv.getProperty("video-params/colormatrix"), - "video-params/primaries" to mpv.getProperty("video-params/primaries"), - "video-params/gamma" to mpv.getProperty("video-params/gamma"), - // HDR metadata - "video-params/max-luma" to mpv.getProperty("video-params/max-luma"), - "video-params/min-luma" to mpv.getProperty("video-params/min-luma"), - "video-params/max-cll" to mpv.getProperty("video-params/max-cll"), - "video-params/max-fall" to mpv.getProperty("video-params/max-fall"), - // Other - "video-params/aspect-name" to mpv.getProperty("video-params/aspect-name"), - "video-params/rotate" to mpv.getProperty("video-params/rotate") ) + + // Only query properties that require an active video track + if (hasVideo) { + stats["display-fps"] = mpv.getProperty("display-fps") + // Color/Format properties + stats["video-params/pixelformat"] = mpv.getProperty("video-params/pixelformat") + stats["video-params/hw-pixelformat"] = mpv.getProperty("video-params/hw-pixelformat") + stats["video-params/colormatrix"] = mpv.getProperty("video-params/colormatrix") + stats["video-params/primaries"] = mpv.getProperty("video-params/primaries") + stats["video-params/gamma"] = mpv.getProperty("video-params/gamma") + // HDR metadata + stats["video-params/max-luma"] = mpv.getProperty("video-params/max-luma") + stats["video-params/min-luma"] = mpv.getProperty("video-params/min-luma") + stats["video-params/max-cll"] = mpv.getProperty("video-params/max-cll") + stats["video-params/max-fall"] = mpv.getProperty("video-params/max-fall") + // Other + stats["video-params/aspect-name"] = mpv.getProperty("video-params/aspect-name") + stats["video-params/rotate"] = mpv.getProperty("video-params/rotate") + } + + return stats } // PiP Mode handling @@ -605,7 +612,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, fallbackInProgress = false // Configure basic MPV properties for Plex playback - mpvCore?.setProperty("hwdec", "mediacodec,mediacodec-copy") + mpvCore?.setProperty("hwdec", "auto") mpvCore?.setProperty("vo", "gpu") mpvCore?.setProperty("ao", "audiotrack") diff --git a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt index cacd17c5..ac7b3fa3 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt @@ -49,10 +49,10 @@ class MpvPlayerCore(private val activity: Activity) : private var surfaceContainer: android.widget.FrameLayout? = null private var overlayLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null - private var voInUse: String = "gpu" @Volatile private var nativeReady: Boolean = false @Volatile private var disposing: Boolean = false private var pendingSurface: Surface? = null + private var lastSurfaceSize: String? = null var delegate: MpvPlayerDelegate? = null var isInitialized: Boolean = false private set @@ -125,7 +125,10 @@ class MpvPlayerCore(private val activity: Activity) : } } + private var flutterOverlayApplied = false + private fun ensureFlutterOverlayOnTop() { + if (flutterOverlayApplied) return val contentView = activity.findViewById(android.R.id.content) contentView.post { if (!isInitialized) return@post @@ -153,6 +156,11 @@ class MpvPlayerCore(private val activity: Activity) : } flutterContainer?.let { container -> + // Skip if Flutter container is already the topmost child + if (contentView.getChildAt(contentView.childCount - 1) == container) { + flutterOverlayApplied = true + return@post + } contentView.bringChildToFront(container) for (j in 0 until container.childCount) { val flutterChild = container.getChildAt(j) @@ -167,6 +175,7 @@ class MpvPlayerCore(private val activity: Activity) : break } } + flutterOverlayApplied = true } } } @@ -244,13 +253,7 @@ class MpvPlayerCore(private val activity: Activity) : ensureFlutterOverlayOnTop() // Re-apply surface size on layout change (orientation transitions) val sv = surfaceView - if (sv != null && nativeReady && !disposing && sv.width > 0 && sv.height > 0) { - try { - MPVLib.setPropertyString("android-surface-size", "${sv.width}x${sv.height}") - } catch (e: Exception) { - Log.w(TAG, "Failed to update surface size on layout change", e) - } - } + if (sv != null) applySurfaceSize(sv.width, sv.height) } contentView.viewTreeObserver.addOnGlobalLayoutListener(overlayLayoutListener) @@ -406,17 +409,13 @@ class MpvPlayerCore(private val activity: Activity) : attachSurfaceInternal(surface) // Reassert overlay order whenever the surface is recreated + flutterOverlayApplied = false ensureFlutterOverlayOnTop() } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { Log.d(TAG, "Surface changed: ${width}x${height}") - if (!nativeReady || disposing) return - try { - MPVLib.setPropertyString("android-surface-size", "${width}x${height}") - } catch (e: Exception) { - Log.w(TAG, "Failed to apply surface size to MPV", e) - } + applySurfaceSize(width, height) } override fun surfaceDestroyed(holder: SurfaceHolder) { @@ -432,13 +431,26 @@ class MpvPlayerCore(private val activity: Activity) : MPVLib.attachSurface(surface) MPVLib.setOptionString("force-window", "yes") // Restore video output after surface is available - MPVLib.setPropertyString("vo", voInUse) + MPVLib.setPropertyString("vo", "gpu") } catch (e: Exception) { Log.w(TAG, "Failed to attach MPV surface", e) } } + private fun applySurfaceSize(width: Int, height: Int) { + if (!nativeReady || disposing || width <= 0 || height <= 0) return + val size = "${width}x${height}" + if (size == lastSurfaceSize) return + lastSurfaceSize = size + try { + MPVLib.setPropertyString("android-surface-size", size) + } catch (e: Exception) { + Log.w(TAG, "Failed to apply surface size to MPV", e) + } + } + private fun detachSurfaceInternal() { + lastSurfaceSize = null if (!nativeReady) return // Disable video output before detaching (like mpv-android) try { diff --git a/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart b/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart index be11cb26..27705b72 100644 --- a/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart +++ b/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:io' show ProcessInfo; +import 'dart:io' show Platform, ProcessInfo; import 'package:flutter/scheduler.dart'; @@ -214,7 +214,7 @@ class PerformanceStatsService { /// Fetch stats from MPV via property queries. Future _fetchMpvStats() async { - // Fetch all properties in parallel for efficiency + // Fetch core properties in parallel final results = await Future.wait([ player.getProperty('video-codec'), // 0 player.getProperty('video-params/w'), // 1 @@ -228,28 +228,49 @@ class PerformanceStatsService { player.getProperty('audio-params/hr-channels'), // 9 player.getProperty('audio-bitrate'), // 10 player.getProperty('total-avsync-change'), // 11 - player.getProperty('cache-used'), // 12 - player.getProperty('cache-speed'), // 13 - player.getProperty('display-fps'), // 14 - player.getProperty('frame-drop-count'), // 15 - player.getProperty('decoder-frame-drop-count'), // 16 - player.getProperty('demuxer-cache-duration'), // 17 - // Color/Format properties - player.getProperty('video-params/pixelformat'), // 18 - player.getProperty('video-params/hw-pixelformat'), // 19 - player.getProperty('video-params/colormatrix'), // 20 - player.getProperty('video-params/primaries'), // 21 - player.getProperty('video-params/gamma'), // 22 - // HDR metadata - player.getProperty('video-params/max-luma'), // 23 - player.getProperty('video-params/min-luma'), // 24 - player.getProperty('video-params/max-cll'), // 25 - player.getProperty('video-params/max-fall'), // 26 - // Other - player.getProperty('video-params/aspect-name'), // 27 - player.getProperty('video-params/rotate'), // 28 + player.getProperty('cache-speed'), // 12 + player.getProperty('frame-drop-count'), // 13 + player.getProperty('decoder-frame-drop-count'), // 14 + player.getProperty('demuxer-cache-duration'), // 15 ]); + final hasVideo = results[1] != null; + + // Only query video-dependent properties when a video track is active. + // On Android, skip properties that are typically unavailable (display-fps, + // hw-pixelformat, HDR metadata) — MPV's native layer logs errors for these + // that we cannot suppress. + List? videoResults; + if (hasVideo) { + final isAndroid = Platform.isAndroid; + videoResults = await Future.wait([ + isAndroid + ? Future.value(null) + : player.getProperty('display-fps'), // 0 + player.getProperty('video-params/pixelformat'), // 1 + isAndroid + ? Future.value(null) + : player.getProperty('video-params/hw-pixelformat'), // 2 + player.getProperty('video-params/colormatrix'), // 3 + player.getProperty('video-params/primaries'), // 4 + player.getProperty('video-params/gamma'), // 5 + isAndroid + ? Future.value(null) + : player.getProperty('video-params/max-luma'), // 6 + isAndroid + ? Future.value(null) + : player.getProperty('video-params/min-luma'), // 7 + isAndroid + ? Future.value(null) + : player.getProperty('video-params/max-cll'), // 8 + isAndroid + ? Future.value(null) + : player.getProperty('video-params/max-fall'), // 9 + player.getProperty('video-params/aspect-name'), // 10 + player.getProperty('video-params/rotate'), // 11 + ]); + } + // Get app memory usage int? appMemory; try { @@ -272,26 +293,23 @@ class PerformanceStatsService { audioChannels: results[9], audioBitrate: _parseInt(results[10]), avsyncChange: _parseDouble(results[11]), - cacheUsed: _parseInt(results[12]), - cacheSpeed: _parseDouble(results[13]), - displayFps: _parseDouble(results[14]), - frameDropCount: _parseInt(results[15]), - decoderFrameDropCount: _parseInt(results[16]), - cacheDuration: _parseDouble(results[17]), - // Color/Format properties - pixelformat: results[18], - hwPixelformat: results[19], - colormatrix: results[20], - primaries: results[21], - gamma: results[22], - // HDR metadata - maxLuma: _parseDouble(results[23]), - minLuma: _parseDouble(results[24]), - maxCll: _parseDouble(results[25]), - maxFall: _parseDouble(results[26]), - // Other - aspectName: results[27], - rotate: _parseInt(results[28]), + cacheSpeed: _parseDouble(results[12]), + frameDropCount: _parseInt(results[13]), + decoderFrameDropCount: _parseInt(results[14]), + cacheDuration: _parseDouble(results[15]), + // Video-dependent properties + displayFps: _parseDouble(videoResults?[0]), + pixelformat: videoResults?[1], + hwPixelformat: videoResults?[2], + colormatrix: videoResults?[3], + primaries: videoResults?[4], + gamma: videoResults?[5], + maxLuma: _parseDouble(videoResults?[6]), + minLuma: _parseDouble(videoResults?[7]), + maxCll: _parseDouble(videoResults?[8]), + maxFall: _parseDouble(videoResults?[9]), + aspectName: videoResults?[10], + rotate: _parseInt(videoResults?[11]), appMemoryBytes: appMemory, uiFps: _currentUiFps, );