fix: async MPV commands to prevent ANR
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user