fix(mpv): avoid blocking runtime calls

This commit is contained in:
edde746
2026-05-03 08:23:44 +02:00
parent f5901389e0
commit 5010833da9
12 changed files with 662 additions and 292 deletions
+109 -49
View File
@@ -202,13 +202,25 @@ void MpvPlayer::Dispose() {
event_callback_ = nullptr;
}
// 4. Cancel pending async commands
// 4. Cancel pending async requests
std::vector<StatusCallback> status_callbacks;
std::vector<GetPropertyCallback> get_callbacks;
{
std::lock_guard<std::mutex> cmd_lock(pending_commands_mutex_);
for (auto& pair : pending_commands_) {
if (pair.second) pair.second(-1);
std::lock_guard<std::mutex> request_lock(pending_requests_mutex_);
for (auto& pair : pending_status_requests_) {
if (pair.second) status_callbacks.push_back(std::move(pair.second));
}
pending_commands_.clear();
for (auto& pair : pending_get_property_requests_) {
if (pair.second) get_callbacks.push_back(std::move(pair.second));
}
pending_status_requests_.clear();
pending_get_property_requests_.clear();
}
for (auto& callback : status_callbacks) {
callback(-1);
}
for (auto& callback : get_callbacks) {
callback(-1, "");
}
// 5. Remove pending idle callbacks
@@ -271,18 +283,7 @@ void MpvPlayer::Render(int width, int height, int fbo) {
mpv_render_context_render(mpv_gl_, params);
}
void MpvPlayer::Command(const std::vector<std::string>& args) {
if (disposed_ || !mpv_) 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);
mpv_command(mpv_, c_args.data());
}
void MpvPlayer::Command(const std::vector<std::string>& args) { CommandAsync(args, nullptr); }
void MpvPlayer::CommandAsync(const std::vector<std::string>& args, CommandCallback callback) {
if (disposed_ || !mpv_) {
@@ -297,39 +298,80 @@ void MpvPlayer::CommandAsync(const std::vector<std::string>& args, CommandCallba
}
c_args.push_back(nullptr);
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);
}
uint64_t request_id = callback ? RegisterStatusRequest(std::move(callback)) : 0;
int result = mpv_command_async(mpv_, request_id, c_args.data());
if (result < 0) {
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);
}
auto cb = TakeStatusRequest(request_id);
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());
SetPropertyAsync(name, value, nullptr);
}
std::string MpvPlayer::GetProperty(const std::string& name) {
if (disposed_ || !mpv_) return "";
void MpvPlayer::SetPropertyAsync(const std::string& name, const std::string& value, StatusCallback callback) {
if (disposed_ || !mpv_) {
if (callback) callback(0);
return;
}
char* value = mpv_get_property_string(mpv_, name.c_str());
if (!value) return "";
uint64_t request_id = callback ? RegisterStatusRequest(std::move(callback)) : 0;
std::string result(value);
mpv_free(value);
return result;
char* property_value = const_cast<char*>(value.c_str());
int result = mpv_set_property_async(mpv_, request_id, name.c_str(), MPV_FORMAT_STRING, &property_value);
if (result < 0) {
auto cb = TakeStatusRequest(request_id);
if (cb) cb(result);
}
}
void MpvPlayer::GetPropertyAsync(const std::string& name, GetPropertyCallback callback) {
if (disposed_ || !mpv_) {
if (callback) callback(-1, "");
return;
}
uint64_t request_id = RegisterGetPropertyRequest(std::move(callback));
int result = mpv_get_property_async(mpv_, request_id, name.c_str(), MPV_FORMAT_STRING);
if (result < 0) {
auto cb = TakeGetPropertyRequest(request_id);
if (cb) cb(result, "");
}
}
uint64_t MpvPlayer::RegisterStatusRequest(StatusCallback callback) {
std::lock_guard<std::mutex> lock(pending_requests_mutex_);
uint64_t request_id = next_reply_userdata_++;
pending_status_requests_[request_id] = std::move(callback);
return request_id;
}
MpvPlayer::StatusCallback MpvPlayer::TakeStatusRequest(uint64_t request_id) {
std::lock_guard<std::mutex> lock(pending_requests_mutex_);
auto it = pending_status_requests_.find(request_id);
if (it == pending_status_requests_.end()) return nullptr;
auto callback = std::move(it->second);
pending_status_requests_.erase(it);
return callback;
}
uint64_t MpvPlayer::RegisterGetPropertyRequest(GetPropertyCallback callback) {
std::lock_guard<std::mutex> lock(pending_requests_mutex_);
uint64_t request_id = next_reply_userdata_++;
pending_get_property_requests_[request_id] = std::move(callback);
return request_id;
}
MpvPlayer::GetPropertyCallback MpvPlayer::TakeGetPropertyRequest(uint64_t request_id) {
std::lock_guard<std::mutex> lock(pending_requests_mutex_);
auto it = pending_get_property_requests_.find(request_id);
if (it == pending_get_property_requests_.end()) return nullptr;
auto callback = std::move(it->second);
pending_get_property_requests_.erase(it);
return callback;
}
void MpvPlayer::ObserveProperty(const std::string& name, const std::string& format, int id) {
@@ -446,17 +488,10 @@ bool MpvPlayer::ProcessEvents() {
void MpvPlayer::HandleMpvEvent(mpv_event* event) {
switch (event->event_id) {
case MPV_EVENT_COMMAND_REPLY: {
case MPV_EVENT_COMMAND_REPLY:
case MPV_EVENT_SET_PROPERTY_REPLY: {
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);
}
}
StatusCallback callback = TakeStatusRequest(request_id);
if (callback) {
int error = event->error;
g_idle_add(
@@ -470,6 +505,31 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
}
break;
}
case MPV_EVENT_GET_PROPERTY_REPLY: {
uint64_t request_id = event->reply_userdata;
GetPropertyCallback callback = TakeGetPropertyRequest(request_id);
if (callback) {
int error = event->error;
std::string value;
if (error >= 0) {
auto* prop = static_cast<mpv_event_property*>(event->data);
if (prop && prop->format == MPV_FORMAT_STRING && prop->data) {
auto c_value = *static_cast<char**>(prop->data);
if (c_value) value = SanitizeUtf8(c_value);
}
}
g_idle_add(
[](gpointer data) -> gboolean {
auto* tuple = static_cast<std::tuple<GetPropertyCallback, int, std::string>*>(data);
const auto& callback = std::get<0>(*tuple);
if (callback) callback(std::get<1>(*tuple), std::get<2>(*tuple));
delete tuple;
return G_SOURCE_REMOVE;
},
new std::tuple<GetPropertyCallback, int, std::string>(std::move(callback), error, std::move(value)));
}
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);
+20 -8
View File
@@ -15,6 +15,7 @@
#include <mutex>
#include <string>
#include <thread>
#include <tuple>
#include <vector>
// Forward declaration for Flutter types
@@ -68,11 +69,13 @@ class MpvPlayer {
/// Returns true if mpv handle exists (even without render context).
bool HasMpvHandle() const { return mpv_ != nullptr; }
/// Executes an mpv command.
/// Queues an mpv command without waiting for completion.
void Command(const std::vector<std::string>& args);
/// Callback type for async command completion.
using CommandCallback = std::function<void(int error)>;
/// Callback types for async mpv requests.
using StatusCallback = std::function<void(int error)>;
using CommandCallback = StatusCallback;
using GetPropertyCallback = std::function<void(int error, const std::string& value)>;
/// Executes an mpv command asynchronously to prevent UI blocking.
void CommandAsync(const std::vector<std::string>& args, CommandCallback callback);
@@ -80,8 +83,11 @@ class MpvPlayer {
/// Sets an mpv property by name.
void SetProperty(const std::string& name, const std::string& value);
/// Gets an mpv property value by name.
std::string GetProperty(const std::string& name);
/// Sets an mpv property asynchronously.
void SetPropertyAsync(const std::string& name, const std::string& value, StatusCallback callback);
/// Gets an mpv property value asynchronously.
void GetPropertyAsync(const std::string& name, GetPropertyCallback callback);
/// Observes an mpv property for changes.
void ObserveProperty(const std::string& name, const std::string& format, int id);
@@ -126,6 +132,11 @@ class MpvPlayer {
/// Sends an event notification.
void SendEvent(const std::string& name, ::_FlValue* data = nullptr);
uint64_t RegisterStatusRequest(StatusCallback callback);
StatusCallback TakeStatusRequest(uint64_t request_id);
uint64_t RegisterGetPropertyRequest(GetPropertyCallback callback);
GetPropertyCallback TakeGetPropertyRequest(uint64_t request_id);
/// Helper to convert mpv_node to FlValue.
::_FlValue* NodeToFlValue(mpv_node* node);
@@ -146,9 +157,10 @@ class MpvPlayer {
std::map<std::string, uint64_t> observed_properties_;
std::map<std::string, int> name_to_id_;
// Pending async commands: request_id -> callback
std::map<uint64_t, CommandCallback> pending_commands_;
std::mutex pending_commands_mutex_;
// Pending async requests: request_id -> callback
std::map<uint64_t, StatusCallback> pending_status_requests_;
std::map<uint64_t, GetPropertyCallback> pending_get_property_requests_;
std::mutex pending_requests_mutex_;
// GSource for processing events on main thread
guint event_source_id_ = 0;
+21 -8
View File
@@ -203,8 +203,14 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, FlMethodCall
} else if (value_value == nullptr || fl_value_get_type(value_value) != FL_VALUE_TYPE_STRING) {
response = FL_METHOD_RESPONSE(fl_method_error_response_new("INVALID_ARGS", "Missing 'value'", nullptr));
} else {
self->player->SetProperty(fl_value_get_string(name_value), fl_value_get_string(value_value));
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
g_object_ref(method_call);
self->player->SetPropertyAsync(
fl_value_get_string(name_value), fl_value_get_string(value_value), [method_call](int error) {
g_autoptr(FlMethodResponse) 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 sent asynchronously
}
}
} else if (strcmp(method, "setLogLevel") == 0) {
@@ -229,12 +235,19 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, FlMethodCall
if (name_value == nullptr || fl_value_get_type(name_value) != FL_VALUE_TYPE_STRING) {
response = FL_METHOD_RESPONSE(fl_method_error_response_new("INVALID_ARGS", "Missing 'name'", nullptr));
} else {
std::string value = self->player->GetProperty(fl_value_get_string(name_value));
if (value.empty()) {
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} else {
response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_string(value.c_str())));
}
g_object_ref(method_call);
self->player->GetPropertyAsync(
fl_value_get_string(name_value), [method_call](int error, const std::string& value) {
g_autoptr(FlMethodResponse) async_response = nullptr;
if (error < 0 || value.empty()) {
async_response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} else {
async_response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_string(value.c_str())));
}
fl_method_call_respond(method_call, async_response, nullptr);
g_object_unref(method_call);
});
return; // Response sent asynchronously
}
}
} else if (strcmp(method, "observeProperty") == 0) {