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
This commit is contained in:
@@ -478,7 +478,9 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private fun getMpvStats(): Map<String, Any?> {
|
||||
val mpv = mpvCore ?: return mapOf("playerType" to "mpv")
|
||||
|
||||
return mapOf(
|
||||
val hasVideo = mpv.getProperty("video-params/w") != null
|
||||
|
||||
val stats = mutableMapOf<String, Any?>(
|
||||
"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")
|
||||
|
||||
|
||||
@@ -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<ViewGroup>(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 {
|
||||
|
||||
+60
-42
@@ -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<void> _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<String?>? 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,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user