fix: move mpv operations off UI thread

This commit is contained in:
edde746
2026-03-05 16:55:44 +01:00
parent 819d7b0f8f
commit a4008d3d6b
2 changed files with 105 additions and 27 deletions
@@ -431,6 +431,80 @@ class MpvPlayerCore(private val activity: Activity) :
MPVLib.observeProperty(name, mpvFormat)
}
/**
* Get an MPV property asynchronously off the UI thread.
* This prevents ANR when mpv_get_property blocks waiting for internal locks.
*/
fun getPropertyAsync(name: String, result: MethodChannel.Result) {
if (!isInitialized) {
result.success(null)
return
}
commandExecutor.execute {
try {
val value = MPVLib.getPropertyString(name)
activity.runOnUiThread { result.success(value) }
} catch (e: Exception) {
activity.runOnUiThread { result.success(null) }
}
}
}
/**
* Set an MPV property asynchronously off the UI thread.
* This prevents ANR when mpv_set_property blocks waiting for internal locks.
*/
fun setPropertyAsync(name: String, value: String, result: MethodChannel.Result) {
if (!isInitialized) {
result.success(null)
return
}
commandExecutor.execute {
try {
MPVLib.setPropertyString(name, value)
activity.runOnUiThread { result.success(null) }
} catch (e: Exception) {
Log.e(TAG, "Async setProperty failed: ${e.message}", e)
activity.runOnUiThread { result.success(null) }
}
}
}
/**
* Observe an MPV property asynchronously off the UI thread.
* This prevents ANR when mpv_observe_property blocks waiting for internal locks.
*/
fun observePropertyAsync(name: String, format: String, result: MethodChannel.Result) {
if (!isInitialized) {
result.success(null)
return
}
val mpvFormat = when (format) {
"double" -> MPVLib.MPV_FORMAT_DOUBLE
"flag" -> MPVLib.MPV_FORMAT_FLAG
"string" -> MPVLib.MPV_FORMAT_STRING
"node" -> MPVLib.MPV_FORMAT_NODE
else -> MPVLib.MPV_FORMAT_NONE
}
commandExecutor.execute {
try {
MPVLib.observeProperty(name, mpvFormat)
activity.runOnUiThread { result.success(null) }
} catch (e: Exception) {
Log.e(TAG, "Async observeProperty failed: ${e.message}", e)
activity.runOnUiThread { result.success(null) }
}
}
}
/**
* Run a block on the command executor (background thread).
* Used by the plugin for I/O operations that shouldn't block the main thread.
*/
fun runOnExecutor(block: () -> Unit) {
commandExecutor.execute(block)
}
fun command(args: Array<String>) {
if (!isInitialized || args.isEmpty()) return
MPVLib.command(args)
@@ -162,8 +162,8 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
return
}
playerCore?.setProperty(name, value)
result.success(null)
playerCore?.setPropertyAsync(name, value, result)
?: result.success(null)
}
private fun handleGetProperty(call: MethodCall, result: MethodChannel.Result) {
@@ -174,8 +174,8 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
return
}
val value = playerCore?.getProperty(name)
result.success(value)
playerCore?.getPropertyAsync(name, result)
?: result.success(null)
}
private fun handleObserveProperty(call: MethodCall, result: MethodChannel.Result) {
@@ -189,8 +189,8 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
nameToId[name] = id
playerCore?.observeProperty(name, format)
result.success(null)
playerCore?.observePropertyAsync(name, format, result)
?: result.success(null)
}
private fun handleCommand(call: MethodCall, result: MethodChannel.Result) {
@@ -253,28 +253,32 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
return
}
try {
val uri = Uri.parse(uriString)
val contentResolver = activity?.contentResolver
if (contentResolver == null) {
result.error("NO_ACTIVITY", "Activity not available", null)
return
}
val pfd = contentResolver.openFileDescriptor(uri, "r")
if (pfd == null) {
result.error("OPEN_FAILED", "Failed to open file descriptor for $uriString", null)
return
}
// detachFd() transfers ownership of the FD to the caller (MPV via fdclose://)
val fd = pfd.detachFd()
Log.d(TAG, "Opened content FD $fd for $uriString")
result.success(fd)
} catch (e: Exception) {
Log.e(TAG, "Failed to open content FD: ${e.message}", e)
result.error("OPEN_FAILED", e.message, null)
val contentResolver = activity?.contentResolver
if (contentResolver == null) {
result.error("NO_ACTIVITY", "Activity not available", null)
return
}
// Open file descriptor off UI thread to prevent ANR on slow storage
playerCore?.runOnExecutor {
try {
val uri = Uri.parse(uriString)
val pfd = contentResolver.openFileDescriptor(uri, "r")
if (pfd == null) {
activity?.runOnUiThread {
result.error("OPEN_FAILED", "Failed to open file descriptor for $uriString", null)
}
return@runOnExecutor
}
val fd = pfd.detachFd()
Log.d(TAG, "Opened content FD $fd for $uriString")
activity?.runOnUiThread { result.success(fd) }
} catch (e: Exception) {
Log.e(TAG, "Failed to open content FD: ${e.message}", e)
activity?.runOnUiThread { result.error("OPEN_FAILED", e.message, null) }
}
} ?: result.error("NO_PLAYER", "Player not initialized", null)
}
// MpvPlayerDelegate