fix(mpv): avoid blocking runtime calls
This commit is contained in:
+109
-49
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user