fix: move mpv operations off UI thread
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user