From 8ad1cb988f8a96ec5cd407293205e355d0e6c195 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Wed, 25 Mar 2026 07:24:52 +0100 Subject: [PATCH] fix: detach mpv surface before view removal in dispose --- .../com/edde746/plezy/mpv/MpvPlayerCore.kt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) 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 ccd43f9a..3a15388a 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 @@ -410,6 +410,17 @@ class MpvPlayerCore(private val activity: Activity) : SurfaceHolder.Callback { // Cancel all coroutines scope.cancel() + // Detach surface from MPV BEFORE removing views to prevent GPU mutex contention + val p = player + if (p != null) { + try { + runBlocking(Dispatchers.IO) { p.setProperty("vo", "null") } + p.detachSurface() + } catch (e: Exception) { + Log.w(TAG, "Failed to detach surface during dispose", e) + } + } + // Capture locals for deferred cleanup val sv = surfaceView val container = surfaceContainer @@ -427,16 +438,7 @@ class MpvPlayerCore(private val activity: Activity) : SurfaceHolder.Callback { pendingSurface = null isInitialized = false - // Deferred view removal - Handler(Looper.getMainLooper()).postAtFrontOfQueue { - sv?.holder?.removeCallback(this) - if (container?.parent != null) { - contentView.removeView(container) - } - } - - // Close player on background thread - val p = player + // Close player on background thread, then remove views if (p != null) { Thread { try { @@ -446,9 +448,22 @@ class MpvPlayerCore(private val activity: Activity) : SurfaceHolder.Callback { } player = null Log.d(TAG, "Disposed (native)") - Handler(Looper.getMainLooper()).post { onComplete?.invoke() } + Handler(Looper.getMainLooper()).post { + sv?.holder?.removeCallback(this) + if (container?.parent != null) { + contentView.removeView(container) + } + onComplete?.invoke() + } }.start() } else { + // No player — safe to remove views immediately + Handler(Looper.getMainLooper()).postAtFrontOfQueue { + sv?.holder?.removeCallback(this) + if (container?.parent != null) { + contentView.removeView(container) + } + } onComplete?.invoke() }