fix: async MPV commands to prevent ANR

This commit is contained in:
edde746
2026-01-20 01:14:45 +01:00
parent e18a9457dd
commit 9721e6bf3f
12 changed files with 397 additions and 10 deletions
+71
View File
@@ -144,6 +144,15 @@ void MpvPlayer::Dispose() {
return; // Already disposed
}
// Cancel pending async commands
{
std::lock_guard<std::mutex> cmd_lock(pending_commands_mutex_);
for (auto& pair : pending_commands_) {
if (pair.second) pair.second(-1); // Call with error
}
pending_commands_.clear();
}
// Clear mpv callbacks BEFORE freeing to prevent new callbacks being scheduled
if (mpv_gl_) {
mpv_render_context_set_update_callback(mpv_gl_, nullptr, nullptr);
@@ -187,6 +196,42 @@ void MpvPlayer::Command(const std::vector<std::string>& args) {
mpv_command(mpv_, c_args.data());
}
void MpvPlayer::CommandAsync(const std::vector<std::string>& args,
CommandCallback callback) {
if (disposed_ || !mpv_) {
if (callback) callback(0);
return;
}
std::vector<const char*> c_args;
c_args.reserve(args.size() + 1);
for (const auto& arg : args) {
c_args.push_back(arg.c_str());
}
c_args.push_back(nullptr);
// Generate unique request ID and store callback
uint64_t request_id;
{
std::lock_guard<std::mutex> lock(pending_commands_mutex_);
request_id = next_reply_userdata_++;
pending_commands_[request_id] = std::move(callback);
}
// mpv_command_async returns immediately
int result = mpv_command_async(mpv_, request_id, c_args.data());
if (result < 0) {
// Submission failed, complete immediately with error
std::lock_guard<std::mutex> lock(pending_commands_mutex_);
auto it = pending_commands_.find(request_id);
if (it != pending_commands_.end()) {
auto cb = std::move(it->second);
pending_commands_.erase(it);
if (cb) cb(result);
}
}
}
void MpvPlayer::SetProperty(const std::string& name, const std::string& value) {
if (disposed_ || !mpv_) return;
mpv_set_property_string(mpv_, name.c_str(), value.c_str());
@@ -329,6 +374,32 @@ bool MpvPlayer::ProcessEvents() {
void MpvPlayer::HandleMpvEvent(mpv_event* event) {
switch (event->event_id) {
case MPV_EVENT_COMMAND_REPLY: {
// Handle async command completion
uint64_t request_id = event->reply_userdata;
CommandCallback callback;
{
std::lock_guard<std::mutex> lock(pending_commands_mutex_);
auto it = pending_commands_.find(request_id);
if (it != pending_commands_.end()) {
callback = std::move(it->second);
pending_commands_.erase(it);
}
}
if (callback) {
// Call callback on main thread
int error = event->error;
g_idle_add(
[](gpointer data) -> gboolean {
auto* pair = static_cast<std::pair<CommandCallback, int>*>(data);
if (pair->first) pair->first(pair->second);
delete pair;
return G_SOURCE_REMOVE;
},
new std::pair<CommandCallback, int>(std::move(callback), error));
}
break;
}
case MPV_EVENT_LOG_MESSAGE: {
auto* msg = static_cast<mpv_event_log_message*>(event->data);
g_message("MPV [%s] %s: %s", msg->level, msg->prefix, msg->text);
+13
View File
@@ -48,6 +48,15 @@ class MpvPlayer {
/// @param args Command arguments (e.g., ["loadfile", "url", "replace"]).
void Command(const std::vector<std::string>& args);
/// Callback type for async command completion.
using CommandCallback = std::function<void(int error)>;
/// Executes an mpv command asynchronously to prevent UI blocking.
/// The callback is called on the main thread when the command completes.
/// @param args Command arguments.
/// @param callback Callback called with error code (0 = success).
void CommandAsync(const std::vector<std::string>& args, CommandCallback callback);
/// Sets an mpv property by name.
/// @param name Property name.
/// @param value Property value as string.
@@ -125,6 +134,10 @@ class MpvPlayer {
uint64_t next_reply_userdata_ = 1;
std::map<std::string, uint64_t> observed_properties_;
// Pending async commands: request_id -> callback
std::map<uint64_t, CommandCallback> pending_commands_;
std::mutex pending_commands_mutex_;
// GSource for processing events on main thread
guint event_source_id_ = 0;
};
+15 -2
View File
@@ -342,8 +342,21 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel,
command_args.push_back(fl_value_get_string(item));
}
}
self->player->Command(command_args);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
// Use async command to prevent UI blocking during network operations
// Take ownership of method_call to respond asynchronously
g_object_ref(method_call);
self->player->CommandAsync(command_args, [method_call](int error) {
g_autoptr(FlMethodResponse) async_response = nullptr;
if (error < 0) {
async_response = FL_METHOD_RESPONSE(fl_method_error_response_new(
"COMMAND_FAILED", "MPV command failed", nullptr));
} else {
async_response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
fl_method_call_respond(method_call, async_response, nullptr);
g_object_unref(method_call);
});
return; // Response will be sent asynchronously
}
}
} else if (strcmp(method, "setProperty") == 0) {