diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 065644ff..8971571f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -8,7 +8,7 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } -val mpvVersion = "v1.0.1" +val mpvVersion = "v1.0.5" val mpvDir = layout.buildDirectory.dir("libmpv").get().asFile val mpvAar = "libmpv-release.aar" @@ -23,7 +23,7 @@ val downloadLibmpv by tasks.registering { } } -val assVersion = "safety" +val assVersion = "safety-2" val assDir = layout.buildDirectory.dir("libass").get().asFile val assAars = listOf("lib_ass-release.aar", "lib_ass_kt-release.aar", "lib_ass_media-release.aar") 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 7a9d3d07..c7851b6a 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 @@ -405,9 +405,6 @@ class MpvPlayerCore(private val activity: Activity) : SurfaceHolder.Callback { audioFocusManager?.release() audioFocusManager = null - // Detach surface before cancelling scope (needs scope.launch for property sets) - detachSurfaceInternal() - // Cancel all coroutines scope.cancel() diff --git a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerPlugin.kt b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerPlugin.kt index 1eff1484..715dc5e4 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerPlugin.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerPlugin.kt @@ -26,6 +26,7 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, private var activity: Activity? = null private var activityBinding: ActivityPluginBinding? = null private val nameToId = mutableMapOf() + private var sessionGeneration = 0 // FlutterPlugin @@ -54,6 +55,7 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, } override fun onDetachedFromActivity() { + ++sessionGeneration playerCore?.dispose() playerCore = null activity = null @@ -122,17 +124,28 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, currentActivity.runOnUiThread { try { - playerCore = MpvPlayerCore(currentActivity).apply { + // Dispose stale core idempotently + playerCore?.dispose() + playerCore = null + + val gen = ++sessionGeneration + val core = MpvPlayerCore(currentActivity).apply { delegate = this@MpvPlayerPlugin } + playerCore = core - playerCore?.initialize { success -> + core.initialize { success -> + if (gen != sessionGeneration || playerCore !== core) { + Log.d(TAG, "Stale init callback (gen=$gen, current=$sessionGeneration)") + result.success(false) + return@initialize + } // Start hidden - now safe because setVisible operates on the container, // not the SurfaceView directly (matching ExoPlayer's approach) - playerCore?.setVisible(false) + core.setVisible(false) Log.d(TAG, "Initialized: $success") result.success(success) - } ?: result.success(false) + } } catch (e: Exception) { Log.e(TAG, "Failed to initialize: ${e.message}", e) result.error("INIT_FAILED", e.message, null) @@ -142,14 +155,14 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, private fun handleDispose(result: MethodChannel.Result) { activity?.runOnUiThread { - playerCore?.dispose { - playerCore = null + val core = playerCore + ++sessionGeneration + playerCore = null + + core?.dispose { Log.d(TAG, "Disposed") result.success(null) - } ?: run { - playerCore = null - result.success(null) - } + } ?: result.success(null) } ?: result.success(null) }