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
+12 -21
View File
@@ -50,36 +50,31 @@ class MpvPlayerCore: MpvPlayerCoreBase {
}
func switchToPipVO(layerPtr: UnsafeMutableRawPointer) -> Bool {
guard let mpv else { return false }
guard mpv != nil else { return false }
print("[MpvPlayerCore] Switching to pip VO for PiP")
metalLayer?.removeFromSuperlayer()
mpv_set_property_string(mpv, "vid", "no")
var pointer = Int64(Int(bitPattern: layerPtr))
mpv_set_property(mpv, "wid", MPV_FORMAT_INT64, &pointer)
mpv_set_property_string(mpv, "vo", "pip")
mpv_set_property_string(mpv, "vid", "auto")
setProperty("vid", value: "no")
setInt64PropertyAsync("wid", value: Int64(Int(bitPattern: layerPtr))) { _ in }
setProperty("vo", value: "pip")
setProperty("vid", value: "auto")
print("[MpvPlayerCore] Switched to pip VO successfully")
return true
}
func switchToGpuNextVO() -> Bool {
guard let mpv, let metalLayer else { return false }
guard mpv != nil, let metalLayer else { return false }
print("[MpvPlayerCore] Switching back to gpu-next VO")
mpv_set_property_string(mpv, "vid", "no")
var layer = metalLayer
mpv_set_property(mpv, "wid", MPV_FORMAT_INT64, &layer)
setProperty("vid", value: "no")
setInt64PropertyAsync("wid", value: Int64(Int(bitPattern: Unmanaged.passUnretained(metalLayer).toOpaque()))) { _ in
}
applyGpuNextOptions()
mpv_set_property_string(mpv, "vid", "auto")
setProperty("vid", value: "auto")
if metalLayer.superlayer == nil, let containerView {
containerView.layer.addSublayer(metalLayer)
@@ -179,9 +174,7 @@ class MpvPlayerCore: MpvPlayerCoreBase {
}
print("[MpvPlayerCore] Entering background - disabling video")
if mpv != nil {
mpv_set_option_string(mpv, "vid", "no")
}
setProperty("vid", value: "no")
}
@objc private func enterForeground() {
@@ -191,8 +184,6 @@ class MpvPlayerCore: MpvPlayerCoreBase {
}
print("[MpvPlayerCore] Entering foreground - enabling video")
if mpv != nil {
mpv_set_option_string(mpv, "vid", "auto")
}
setProperty("vid", value: "auto")
}
}
+20 -10
View File
@@ -230,7 +230,12 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
stopPipTimebaseSync()
pipController?.flushLayer()
let restoredInlineVO = playerCore?.switchToGpuNextVO() ?? false
if pause { playerCore?.setProperty("pause", value: "yes") }
if pause {
playerCore?.setPropertyAsync("pause", value: "yes") { [weak self] _ in
self?.pipController?.invalidatePlaybackState()
self?.syncPipTimebase()
}
}
pendingInlineRestoreAfterPip = restoredInlineVO
if pendingInlineRestoreAfterPip {
if isSceneActive {
@@ -340,14 +345,18 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
return
}
playerCore?.setProperty(name, value: value)
if name == "pause" {
pipController?.invalidatePlaybackState()
if playerCore?.isPipActive == true { syncPipTimebase() }
guard let core = playerCore else {
result(nil)
return
}
result(nil)
core.setPropertyAsync(name, value: value) { [weak self] _ in
if name == "pause" {
self?.pipController?.invalidatePlaybackState()
if core.isPipActive == true { self?.syncPipTimebase() }
}
result(nil)
}
}
// MARK: - Helpers
@@ -400,9 +409,10 @@ extension MpvPlayerPlugin: MpvPipDelegate {
}
func pipSetPlaying(_ playing: Bool) {
playerCore?.setProperty("pause", value: playing ? "no" : "yes")
pipController?.invalidatePlaybackState()
syncPipTimebase()
playerCore?.setPropertyAsync("pause", value: playing ? "no" : "yes") { [weak self] _ in
self?.pipController?.invalidatePlaybackState()
self?.syncPipTimebase()
}
}
func pipSkip(byInterval seconds: Double) {
+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) {
+18 -13
View File
@@ -184,10 +184,8 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
// Get video dimensions for aspect ratio
var aspectRatio = NSSize(width: 16, height: 9) // default
if let w = playerCore.getProperty("width"), let h = playerCore.getProperty("height"),
let width = Double(w), let height = Double(h), width > 0 && height > 0
{
aspectRatio = NSSize(width: width, height: height)
if let videoSize = playerCore.videoSize {
aspectRatio = NSSize(width: videoSize.width, height: videoSize.height)
}
enteredPipViaAuto = !manual
@@ -296,15 +294,19 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
return
}
playerCore?.setProperty(name, value: value)
if name == "pause" {
let isPlaying = value == "no"
pipController?.setPlaying(isPlaying)
playerCore?.setPaused(!isPlaying)
guard let core = playerCore else {
result(nil)
return
}
result(nil)
core.setPropertyAsync(name, value: value) { [weak self] _ in
if name == "pause" {
let isPlaying = value == "no"
self?.pipController?.setPlaying(isPlaying)
core.setPaused(!isPlaying)
}
result(nil)
}
}
// MARK: - Helpers
@@ -366,8 +368,11 @@ extension MpvPlayerPlugin: MpvPipDelegate {
}
func pipSetPlaying(_ playing: Bool) {
playerCore?.setProperty("pause", value: playing ? "no" : "yes")
pipController?.setPlaying(playing)
guard let playerCore else { return }
playerCore.setPropertyAsync("pause", value: playing ? "no" : "yes") { [weak self] _ in
self?.pipController?.setPlaying(playing)
playerCore.setPaused(!playing)
}
}
var isPipPlaying: Bool { !(playerCore?.isPaused ?? true) }
+241 -94
View File
@@ -91,13 +91,34 @@ class MpvPlayerCoreBase: NSObject {
"pause", "eof-reached", "paused-for-cache",
]
private static let internalSigPeakObserverId: UInt64 = UInt64.max - 1
private static let internalWidthObserverId: UInt64 = UInt64.max - 2
private static let internalHeightObserverId: UInt64 = UInt64.max - 3
private static let internalObserverIds: Set<UInt64> = [
internalSigPeakObserverId,
internalWidthObserverId,
internalHeightObserverId,
]
let queue = DispatchQueue(label: "mpv", qos: .userInitiated)
private let queueKey = DispatchSpecificKey<Void>()
private var pendingCommands: [UInt64: (Result<Void, Error>) -> Void] = [:]
private let pendingCommandsLock = NSLock()
private enum PendingRequest {
case void((Result<Void, Error>) -> Void)
case getProperty((Result<String?, Error>) -> Void)
}
private var pendingRequests: [UInt64: PendingRequest] = [:]
private let pendingRequestsLock = NSLock()
private var nextRequestId: UInt64 = 1
private let cacheLock = NSLock()
private var cachedPaused = true
private var cachedDuration = 0.0
private var cachedTimePos = 0.0
private var cachedWidth = 0.0
private var cachedHeight = 0.0
override init() {
super.init()
queue.setSpecific(key: queueKey, value: ())
@@ -122,7 +143,7 @@ class MpvPlayerCoreBase: NSObject {
checkError(mpv_request_log_messages(mpv, "warn"))
#endif
var layer = metalLayer
var layer = Int64(Int(bitPattern: Unmanaged.passUnretained(metalLayer).toOpaque()))
checkError(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &layer))
applySharedMpvOptions()
configurePlatformMpvOptions()
@@ -145,7 +166,9 @@ class MpvPlayerCoreBase: NSObject {
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
)
mpv_observe_property(mpv, 0, "video-params/sig-peak", MPV_FORMAT_DOUBLE)
mpv_observe_property(mpv, Self.internalSigPeakObserverId, "video-params/sig-peak", MPV_FORMAT_DOUBLE)
mpv_observe_property(mpv, Self.internalWidthObserverId, "width", MPV_FORMAT_DOUBLE)
mpv_observe_property(mpv, Self.internalHeightObserverId, "height", MPV_FORMAT_DOUBLE)
return true
}
@@ -155,35 +178,75 @@ class MpvPlayerCoreBase: NSObject {
}
func setProperty(_ name: String, value: String) {
guard mpv != nil else { return }
setPropertyAsync(name, value: value) { _ in }
}
func setPropertyAsync(
_ name: String,
value: String,
completion: @escaping (Result<Void, Error>) -> Void
) {
if name == "pause" {
setCachedPaused(value == "yes" || value == "true" || value == "1")
}
if name == "hdr-enabled" {
let enabled = value == "yes" || value == "true" || value == "1"
setHDREnabled(enabled)
setHDREnabled(enabled, completion: completion)
return
}
mpv_set_property_string(mpv, name, value)
setRawStringPropertyAsync(name, value: value, completion: completion)
}
func setHDREnabled(_ enabled: Bool) {
func setInt64PropertyAsync(
_ name: String,
value: Int64,
completion: @escaping (Result<Void, Error>) -> Void
) {
guard let mpv else {
completion(.success(()))
return
}
let requestId = registerRequest(.void(completion))
var propertyValue = value
let status = name.withCString { namePointer in
mpv_set_property_async(mpv, requestId, namePointer, MPV_FORMAT_INT64, &propertyValue)
}
completeRequestIfSubmissionFailed(requestId: requestId, status: status)
}
func setHDREnabled(_ enabled: Bool, completion: ((Result<Void, Error>) -> Void)? = nil) {
cacheLock.lock()
hdrEnabled = enabled
let sigPeak = lastSigPeak
cacheLock.unlock()
print("[MpvPlayerCore] HDR enabled: \(enabled)")
if mpv != nil {
mpv_set_property_string(mpv, "target-colorspace-hint", enabled ? "yes" : "no")
}
setRawStringPropertyAsync(
"target-colorspace-hint",
value: enabled ? "yes" : "no",
completion: completion ?? { _ in }
)
DispatchQueue.main.async {
self.updateEDRMode(sigPeak: self.lastSigPeak)
self.updateEDRMode(sigPeak: sigPeak)
}
}
func getProperty(_ name: String) -> String? {
guard mpv != nil else { return nil }
let cstr = mpv_get_property_string(mpv, name)
defer { mpv_free(cstr) }
return cstr.map { safeString($0) }
func getPropertyAsync(_ name: String, completion: @escaping (Result<String?, Error>) -> Void) {
guard let mpv else {
completion(.success(nil))
return
}
let requestId = registerRequest(.getProperty(completion))
let status = name.withCString { namePointer in
mpv_get_property_async(mpv, requestId, namePointer, MPV_FORMAT_STRING)
}
completeRequestIfSubmissionFailed(requestId: requestId, status: status)
}
func observeProperty(_ name: String, format: String) {
@@ -207,8 +270,7 @@ class MpvPlayerCoreBase: NSObject {
}
func command(_ args: [String]) {
guard mpv != nil, !args.isEmpty else { return }
command(args[0], args: Array(args.dropFirst()))
commandAsync(args) { _ in }
}
func commandAsync(_ args: [String], completion: @escaping (Result<Void, Error>) -> Void) {
@@ -217,11 +279,7 @@ class MpvPlayerCoreBase: NSObject {
return
}
pendingCommandsLock.lock()
let requestId = nextRequestId
nextRequestId += 1
pendingCommands[requestId] = completion
pendingCommandsLock.unlock()
let requestId = registerRequest(.void(completion))
var cargs: [UnsafeMutablePointer<CChar>?] = args.map { strdup($0) }
cargs.append(nil)
@@ -229,21 +287,7 @@ class MpvPlayerCoreBase: NSObject {
cargs.withUnsafeBufferPointer { buffer in
var constPointers = buffer.map { UnsafePointer($0) }
let result = mpv_command_async(mpv, requestId, &constPointers)
if result < 0 {
pendingCommandsLock.lock()
let pending = pendingCommands.removeValue(forKey: requestId)
pendingCommandsLock.unlock()
guard let pending else { return }
let error = NSError(
domain: "mpv",
code: Int(result),
userInfo: [NSLocalizedDescriptionKey: safeString(mpv_error_string(result))]
)
DispatchQueue.main.async {
pending(.failure(error))
}
}
completeRequestIfSubmissionFailed(requestId: requestId, status: result)
}
for pointer in cargs {
@@ -251,30 +295,54 @@ class MpvPlayerCoreBase: NSObject {
}
}
private func setRawStringPropertyAsync(
_ name: String,
value: String,
completion: @escaping (Result<Void, Error>) -> Void
) {
guard let mpv else {
completion(.success(()))
return
}
let requestId = registerRequest(.void(completion))
let status = name.withCString { namePointer in
value.withCString { valuePointer in
var propertyValue: UnsafePointer<CChar>? = valuePointer
return mpv_set_property_async(mpv, requestId, namePointer, MPV_FORMAT_STRING, &propertyValue)
}
}
completeRequestIfSubmissionFailed(requestId: requestId, status: status)
}
var isPaused: Bool {
guard let mpv else { return true }
var flag: Int32 = 0
mpv_get_property(mpv, "pause", MPV_FORMAT_FLAG, &flag)
return flag != 0
cacheLock.lock()
defer { cacheLock.unlock() }
return cachedPaused
}
var duration: Double {
guard let mpv else { return 0 }
var value: Double = 0
mpv_get_property(mpv, "duration", MPV_FORMAT_DOUBLE, &value)
return value
cacheLock.lock()
defer { cacheLock.unlock() }
return cachedDuration
}
var timePos: Double {
guard let mpv else { return 0 }
var value: Double = 0
mpv_get_property(mpv, "time-pos", MPV_FORMAT_DOUBLE, &value)
return value
cacheLock.lock()
defer { cacheLock.unlock() }
return cachedTimePos
}
var videoSize: CGSize? {
cacheLock.lock()
defer { cacheLock.unlock() }
guard cachedWidth > 0, cachedHeight > 0 else { return nil }
return CGSize(width: cachedWidth, height: cachedHeight)
}
func disposeSharedState(destroySynchronously: Bool) {
isDisposing = true
cancelPendingCommands()
cancelPendingRequests()
let mpvHandle = mpv
mpv = nil
@@ -299,9 +367,9 @@ class MpvPlayerCoreBase: NSObject {
func applyGpuNextOptions() {
guard mpv != nil else { return }
mpv_set_property_string(mpv, "gpu-api", "vulkan")
mpv_set_property_string(mpv, "gpu-context", "moltenvk")
mpv_set_property_string(mpv, "vo", "gpu-next")
setProperty("gpu-api", value: "vulkan")
setProperty("gpu-context", value: "moltenvk")
setProperty("vo", value: "gpu-next")
}
private func applySharedMpvOptions() {
@@ -313,38 +381,99 @@ class MpvPlayerCoreBase: NSObject {
checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes"))
}
private func cancelPendingCommands() {
pendingCommandsLock.lock()
let pending = pendingCommands
pendingCommands.removeAll()
pendingCommandsLock.unlock()
private func cancelPendingRequests() {
pendingRequestsLock.lock()
let pending = pendingRequests
pendingRequests.removeAll()
pendingRequestsLock.unlock()
let error = NSError(
domain: "mpv",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Player disposed"]
)
for (_, completion) in pending {
for (_, request) in pending {
DispatchQueue.main.async {
switch request {
case .void(let completion):
completion(.failure(error))
case .getProperty(let completion):
completion(.failure(error))
}
}
}
}
private func registerRequest(_ request: PendingRequest) -> UInt64 {
pendingRequestsLock.lock()
defer { pendingRequestsLock.unlock() }
let requestId = nextRequestId
nextRequestId += 1
pendingRequests[requestId] = request
return requestId
}
private func takeRequest(_ requestId: UInt64) -> PendingRequest? {
pendingRequestsLock.lock()
defer { pendingRequestsLock.unlock() }
return pendingRequests.removeValue(forKey: requestId)
}
private func mpvError(_ status: CInt) -> NSError {
NSError(
domain: "mpv",
code: Int(status),
userInfo: [NSLocalizedDescriptionKey: safeString(mpv_error_string(status))]
)
}
private func completeRequestIfSubmissionFailed(requestId: UInt64, status: CInt) {
guard status < 0, let request = takeRequest(requestId) else { return }
let error = mpvError(status)
DispatchQueue.main.async {
switch request {
case .void(let completion):
completion(.failure(error))
case .getProperty(let completion):
completion(.failure(error))
}
}
}
private func command(_ command: String, args: [String] = []) {
guard mpv != nil else { return }
private func completeVoidRequest(requestId: UInt64, error status: CInt) {
guard let request = takeRequest(requestId) else { return }
DispatchQueue.main.async {
switch request {
case .void(let completion):
if status < 0 {
completion(.failure(self.mpvError(status)))
} else {
completion(.success(()))
}
case .getProperty:
break
}
}
}
var cargs: [UnsafeMutablePointer<CChar>?] = ([command] + args).map { strdup($0) }
cargs.append(nil)
defer {
for pointer in cargs {
free(pointer)
private func completeGetPropertyRequest(_ event: mpv_event) {
guard let request = takeRequest(event.reply_userdata) else { return }
guard case .getProperty(let completion) = request else { return }
var value: String?
if event.error >= 0,
let propertyPointer = event.data?.assumingMemoryBound(to: mpv_event_property.self)
{
let property = propertyPointer.pointee
if property.format == MPV_FORMAT_STRING, let data = property.data {
let cstring = data.assumingMemoryBound(to: UnsafePointer<CChar>?.self).pointee
value = cstring.map { safeString($0) }
}
}
cargs.withUnsafeBufferPointer { buffer in
var constPointers = buffer.map { UnsafePointer($0) }
_ = mpv_command(mpv, &constPointers)
DispatchQueue.main.async {
completion(.success(value))
}
}
@@ -371,29 +500,16 @@ class MpvPlayerCoreBase: NSObject {
guard let data = event.data else { break }
let property = data.assumingMemoryBound(to: mpv_event_property.self).pointee
let name = safeString(property.name)
handlePropertyChange(name: name, property: property)
handlePropertyChange(name: name, property: property, replyUserdata: event.reply_userdata)
case MPV_EVENT_COMMAND_REPLY:
let requestId = event.reply_userdata
pendingCommandsLock.lock()
let completion = pendingCommands.removeValue(forKey: requestId)
pendingCommandsLock.unlock()
completeVoidRequest(requestId: event.reply_userdata, error: event.error)
guard let completion else { break }
if event.error < 0 {
let error = NSError(
domain: "mpv",
code: Int(event.error),
userInfo: [NSLocalizedDescriptionKey: safeString(mpv_error_string(event.error))]
)
DispatchQueue.main.async {
completion(.failure(error))
}
} else {
DispatchQueue.main.async {
completion(.success(()))
}
}
case MPV_EVENT_SET_PROPERTY_REPLY:
completeVoidRequest(requestId: event.reply_userdata, error: event.error)
case MPV_EVENT_GET_PROPERTY_REPLY:
completeGetPropertyRequest(event)
case MPV_EVENT_FILE_LOADED:
DispatchQueue.main.async {
@@ -446,9 +562,7 @@ class MpvPlayerCoreBase: NSObject {
}
}
private func handlePropertyChange(name: String, property: mpv_event_property) {
if isBackgrounded && !Self.criticalProperties.contains(name) { return }
private func handlePropertyChange(name: String, property: mpv_event_property, replyUserdata: UInt64) {
var value: Any?
switch property.format {
@@ -478,18 +592,51 @@ class MpvPlayerCoreBase: NSObject {
break
}
updateCachedProperty(name: name, value: value)
if name == "video-params/sig-peak", let sigPeak = value as? Double {
cacheLock.lock()
lastSigPeak = sigPeak
cacheLock.unlock()
DispatchQueue.main.async {
self.updateEDRMode(sigPeak: sigPeak)
}
}
if Self.internalObserverIds.contains(replyUserdata) { return }
if isBackgrounded && !Self.criticalProperties.contains(name) { return }
DispatchQueue.main.async {
self.delegate?.onPropertyChange(name: name, value: value)
}
}
private func updateCachedProperty(name: String, value: Any?) {
cacheLock.lock()
defer { cacheLock.unlock() }
switch name {
case "pause":
if let paused = value as? Bool { cachedPaused = paused }
case "duration":
if let duration = value as? Double { cachedDuration = duration }
case "time-pos":
if let timePos = value as? Double { cachedTimePos = timePos }
case "width":
if let width = value as? Double { cachedWidth = width }
case "height":
if let height = value as? Double { cachedHeight = height }
default:
break
}
}
private func setCachedPaused(_ paused: Bool) {
cacheLock.lock()
cachedPaused = paused
cacheLock.unlock()
}
private func convertNode(_ node: mpv_node) -> Any? {
switch node.format {
case MPV_FORMAT_STRING:
@@ -27,7 +27,14 @@ extension MpvPluginShared {
)
return
}
result(coreBase?.getProperty(name))
coreBase?.getPropertyAsync(name) { propertyResult in
switch propertyResult {
case .success(let value):
result(value)
case .failure:
result(nil)
}
} ?? result(nil)
}
func handleObserveProperty(call: FlutterMethodCall, result: @escaping FlutterResult) {
+112 -60
View File
@@ -82,13 +82,25 @@ bool MpvPlayer::Initialize(HWND container, HWND flutter_window) {
void MpvPlayer::Dispose() {
StopEventLoop();
// Cancel pending async commands
// Cancel pending async requests
std::vector<StatusCallback> status_callbacks;
std::vector<GetPropertyCallback> get_callbacks;
{
std::lock_guard<std::mutex> lock(pending_commands_mutex_);
for (auto& pair : pending_commands_) {
if (pair.second) pair.second(-1); // Call with error
std::lock_guard<std::mutex> 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, "");
}
if (mpv_) {
@@ -104,18 +116,7 @@ void MpvPlayer::Dispose() {
observed_properties_.clear();
}
void MpvPlayer::Command(const std::vector<std::string>& args) {
if (!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 (!mpv_) {
@@ -130,50 +131,88 @@ void MpvPlayer::CommandAsync(const std::vector<std::string>& args, CommandCallba
}
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);
}
uint64_t request_id = callback ? RegisterStatusRequest(std::move(callback)) : 0;
// 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);
}
auto cb = TakeStatusRequest(request_id);
if (cb) cb(result);
}
}
void MpvPlayer::SetProperty(const std::string& name, const std::string& value) {
if (!mpv_) return;
SetPropertyAsync(name, value, nullptr);
}
void MpvPlayer::SetPropertyAsync(const std::string& name, const std::string& value, StatusCallback callback) {
if (!mpv_) {
if (callback) callback(0);
return;
}
// Handle custom HDR toggle property (same pattern as iOS/macOS)
if (name == "hdr-enabled") {
bool enabled = (value == "yes" || value == "true" || value == "1");
SetHDREnabled(enabled);
SetHDREnabled(enabled, std::move(callback));
return;
}
mpv_set_property_string(mpv_, name.c_str(), value.c_str());
uint64_t request_id = callback ? RegisterStatusRequest(std::move(callback)) : 0;
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);
}
}
std::string MpvPlayer::GetProperty(const std::string& name) {
if (!mpv_) return "";
void MpvPlayer::GetPropertyAsync(const std::string& name, GetPropertyCallback callback) {
if (!mpv_) {
if (callback) callback(-1, "");
return;
}
char* value = mpv_get_property_string(mpv_, name.c_str());
if (!value) return "";
uint64_t request_id = RegisterGetPropertyRequest(std::move(callback));
std::string result(value);
mpv_free(value);
return result;
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) {
@@ -287,23 +326,31 @@ void MpvPlayer::EventLoop() {
void MpvPlayer::HandleMpvEvent(mpv_event* event) {
switch (event->event_id) {
case MPV_EVENT_COMMAND_REPLY: {
// Handle async command completion
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) {
callback(event->error);
}
break;
}
case MPV_EVENT_GET_PROPERTY_REPLY: {
uint64_t request_id = event->reply_userdata;
GetPropertyCallback callback = TakeGetPropertyRequest(request_id);
if (callback) {
std::string value;
if (event->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);
}
}
callback(event->error, value);
}
break;
}
case MPV_EVENT_LOG_MESSAGE: {
auto* msg = static_cast<mpv_event_log_message*>(event->data);
char log_msg[512];
@@ -356,11 +403,14 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
// to null output (e.g. after sleep/wake or device unplug), re-set
// audio-device to switch back to the real output.
// Mirrors mpv's TOOLS/lua/ao-null-reload.lua for embedded libmpv.
if (strcmp(prop->name, "audio-device-list") == 0 && GetProperty("current-ao") == "null") {
auto device = GetProperty("audio-device");
if (!device.empty()) {
mpv_set_property_string(mpv_, "audio-device", device.c_str());
}
if (strcmp(prop->name, "audio-device-list") == 0) {
GetPropertyAsync("current-ao", [this](int ao_error, const std::string& current_ao) {
if (ao_error < 0 || current_ao != "null") return;
GetPropertyAsync("audio-device", [this](int device_error, const std::string& device) {
if (device_error < 0 || device.empty()) return;
SetProperty("audio-device", device);
});
});
}
SendPropertyChange(prop->name, &node);
@@ -445,11 +495,13 @@ void MpvPlayer::SendEvent(const std::string& name, const flutter::EncodableMap&
}
}
void MpvPlayer::SetHDREnabled(bool enabled) {
void MpvPlayer::SetHDREnabled(bool enabled, StatusCallback callback) {
hdr_enabled_ = enabled;
if (mpv_) {
mpv_set_property_string(mpv_, "target-colorspace-hint", enabled ? "yes" : "no");
SetPropertyAsync("target-colorspace-hint", enabled ? "yes" : "no", std::move(callback));
} else if (callback) {
callback(0);
}
UpdateHDRMode(last_sig_peak_);
+20 -11
View File
@@ -34,21 +34,25 @@ class MpvPlayer {
// Returns true if mpv is initialized.
bool IsInitialized() 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.
// The callback is called on the main thread when the command completes.
void CommandAsync(const std::vector<std::string>& args, CommandCallback callback);
// Sets an mpv property.
// Queues an mpv property update without waiting for completion.
void SetProperty(const std::string& name, const std::string& value);
// Gets an mpv property.
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 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);
@@ -75,6 +79,10 @@ class MpvPlayer {
void HandleMpvEvent(mpv_event* event);
void SendPropertyChange(const char* name, mpv_node* data);
void SendEvent(const std::string& name, const flutter::EncodableMap& data = {});
uint64_t RegisterStatusRequest(StatusCallback callback);
StatusCallback TakeStatusRequest(uint64_t request_id);
uint64_t RegisterGetPropertyRequest(GetPropertyCallback callback);
GetPropertyCallback TakeGetPropertyRequest(uint64_t request_id);
mpv_handle* mpv_ = nullptr;
HWND hwnd_ = nullptr;
@@ -92,16 +100,17 @@ 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_;
// HDR state
bool hdr_enabled_ = true; // User preference
double last_sig_peak_ = 0.0; // Last known sig-peak for HDR content detection
// HDR methods
void SetHDREnabled(bool enabled);
void SetHDREnabled(bool enabled, StatusCallback callback = nullptr);
void UpdateHDRMode(double sigPeak);
};
+72 -17
View File
@@ -18,12 +18,17 @@ void MpvPlayerPluginRegisterWithRegistrar(FlutterDesktopPluginRegistrarRef regis
namespace mpv {
namespace {
constexpr UINT kPlatformTaskMessage = WM_APP + 0x4D50;
}
void MpvPlayerPlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar) {
auto plugin = std::make_unique<MpvPlayerPlugin>(registrar);
registrar->AddPlugin(std::move(plugin));
}
MpvPlayerPlugin::MpvPlayerPlugin(flutter::PluginRegistrarWindows* registrar) : registrar_(registrar) {
MpvPlayerPlugin::MpvPlayerPlugin(flutter::PluginRegistrarWindows* registrar)
: registrar_(registrar), platform_thread_id_(::GetCurrentThreadId()) {
// Create method channel.
method_channel_ = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "com.plezy/mpv_player", &flutter::StandardMethodCodec::GetInstance());
@@ -53,6 +58,8 @@ MpvPlayerPlugin::MpvPlayerPlugin(flutter::PluginRegistrarWindows* registrar) : r
}
MpvPlayerPlugin::~MpvPlayerPlugin() {
DrainPlatformTasks();
// Unregister window proc delegate.
if (proc_id_) {
registrar_->UnregisterTopLevelWindowProcDelegate(proc_id_.value());
@@ -64,6 +71,35 @@ HWND MpvPlayerPlugin::GetChildWindow() { return registrar_->GetView()->GetNative
HWND MpvPlayerPlugin::GetWindow() { return ::GetAncestor(GetChildWindow(), GA_ROOT); }
void MpvPlayerPlugin::PostToPlatformThread(std::function<void()> task) {
if (::GetCurrentThreadId() == platform_thread_id_) {
task();
return;
}
{
std::lock_guard<std::mutex> lock(platform_tasks_mutex_);
platform_tasks_.push(std::move(task));
}
if (flutter_window_) {
::PostMessage(flutter_window_, kPlatformTaskMessage, 0, 0);
}
}
void MpvPlayerPlugin::DrainPlatformTasks() {
std::queue<std::function<void()>> tasks;
{
std::lock_guard<std::mutex> lock(platform_tasks_mutex_);
tasks.swap(platform_tasks_);
}
while (!tasks.empty()) {
tasks.front()();
tasks.pop();
}
}
void MpvPlayerPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
@@ -77,11 +113,17 @@ void MpvPlayerPlugin::HandleMethodCall(
}
HWND flutter_window = GetWindow();
flutter_window_ = flutter_window;
MpvCore::SetInstance(std::make_unique<MpvCore>(flutter_window));
proc_id_ =
registrar_->RegisterTopLevelWindowProcDelegate([](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
registrar_->RegisterTopLevelWindowProcDelegate([this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
if (message == kPlatformTaskMessage) {
DrainPlatformTasks();
return std::optional<HRESULT>(0);
}
auto* core = MpvCore::GetInstance();
if (core) {
return core->WindowProc(hwnd, message, wparam, lparam);
@@ -160,13 +202,15 @@ void MpvPlayerPlugin::HandleMethodCall(
auto result_ptr =
std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
std::string cmd_name = command_args.empty() ? "unknown" : command_args[0];
player_->CommandAsync(command_args, [result_ptr, cmd_name](int error) {
if (error < 0) {
(*result_ptr)
->Error("COMMAND_FAILED", "MPV command failed: " + cmd_name + " (error " + std::to_string(error) + ")");
} else {
(*result_ptr)->Success();
}
player_->CommandAsync(command_args, [this, result_ptr, cmd_name](int error) {
PostToPlatformThread([result_ptr, cmd_name, error]() {
if (error < 0) {
(*result_ptr)
->Error("COMMAND_FAILED", "MPV command failed: " + cmd_name + " (error " + std::to_string(error) + ")");
} else {
(*result_ptr)->Success();
}
});
});
return; // Response will be sent asynchronously
} else if (method == "setProperty") {
@@ -194,8 +238,12 @@ void MpvPlayerPlugin::HandleMethodCall(
return;
}
player_->SetProperty(std::get<std::string>(name_it->second), std::get<std::string>(value_it->second));
result->Success();
auto result_ptr =
std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
player_->SetPropertyAsync(
std::get<std::string>(name_it->second), std::get<std::string>(value_it->second),
[this, result_ptr](int error) { PostToPlatformThread([result_ptr]() { (*result_ptr)->Success(); }); });
return;
} else if (method == "setLogLevel") {
if (!player_ || !player_->IsInitialized()) {
result->Error("NOT_INITIALIZED", "Player not initialized");
@@ -238,12 +286,19 @@ void MpvPlayerPlugin::HandleMethodCall(
return;
}
std::string value = player_->GetProperty(std::get<std::string>(name_it->second));
if (value.empty()) {
result->Success();
} else {
result->Success(flutter::EncodableValue(value));
}
auto result_ptr =
std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
player_->GetPropertyAsync(
std::get<std::string>(name_it->second), [this, result_ptr](int error, const std::string& value) {
PostToPlatformThread([result_ptr, error, value]() {
if (error < 0 || value.empty()) {
(*result_ptr)->Success();
} else {
(*result_ptr)->Success(flutter::EncodableValue(value));
}
});
});
return;
} else if (method == "observeProperty") {
if (!player_ || !player_->IsInitialized()) {
result->Error("NOT_INITIALIZED", "Player not initialized");
+9
View File
@@ -8,8 +8,11 @@
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include "display_mode_manager.h"
#include "mpv_core.h"
@@ -33,11 +36,15 @@ class MpvPlayerPlugin : public flutter::Plugin {
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void SendEvent(const flutter::EncodableValue& event);
void PostToPlatformThread(std::function<void()> task);
void DrainPlatformTasks();
HWND GetWindow();
HWND GetChildWindow();
flutter::PluginRegistrarWindows* registrar_;
DWORD platform_thread_id_;
HWND flutter_window_ = nullptr;
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> method_channel_;
std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> event_channel_;
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> event_sink_;
@@ -45,6 +52,8 @@ class MpvPlayerPlugin : public flutter::Plugin {
std::unique_ptr<MpvPlayer> player_;
DisplayModeManager display_mode_manager_;
std::optional<int32_t> proc_id_;
std::mutex platform_tasks_mutex_;
std::queue<std::function<void()>> platform_tasks_;
};
} // namespace mpv