diff --git a/ios/Runner/MpvPlayer/MpvPlayerCore.swift b/ios/Runner/MpvPlayer/MpvPlayerCore.swift index 9abea1e3..28db1942 100644 --- a/ios/Runner/MpvPlayer/MpvPlayerCore.swift +++ b/ios/Runner/MpvPlayer/MpvPlayerCore.swift @@ -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") } } diff --git a/ios/Runner/MpvPlayer/MpvPlayerPlugin.swift b/ios/Runner/MpvPlayer/MpvPlayerPlugin.swift index 62eb0d60..7f1e621c 100644 --- a/ios/Runner/MpvPlayer/MpvPlayerPlugin.swift +++ b/ios/Runner/MpvPlayer/MpvPlayerPlugin.swift @@ -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) { diff --git a/linux/runner/mpv/mpv_player.cc b/linux/runner/mpv/mpv_player.cc index b51a0001..2c7da7cd 100644 --- a/linux/runner/mpv/mpv_player.cc +++ b/linux/runner/mpv/mpv_player.cc @@ -202,13 +202,25 @@ void MpvPlayer::Dispose() { event_callback_ = nullptr; } - // 4. Cancel pending async commands + // 4. Cancel pending async requests + std::vector status_callbacks; + std::vector get_callbacks; { - std::lock_guard cmd_lock(pending_commands_mutex_); - for (auto& pair : pending_commands_) { - if (pair.second) pair.second(-1); + std::lock_guard 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& args) { - if (disposed_ || !mpv_) return; - - std::vector 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& args) { CommandAsync(args, nullptr); } void MpvPlayer::CommandAsync(const std::vector& args, CommandCallback callback) { if (disposed_ || !mpv_) { @@ -297,39 +298,80 @@ void MpvPlayer::CommandAsync(const std::vector& args, CommandCallba } c_args.push_back(nullptr); - uint64_t request_id; - { - std::lock_guard 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 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(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 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 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 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 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 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(event->data); + if (prop && prop->format == MPV_FORMAT_STRING && prop->data) { + auto c_value = *static_cast(prop->data); + if (c_value) value = SanitizeUtf8(c_value); + } + } + g_idle_add( + [](gpointer data) -> gboolean { + auto* tuple = static_cast*>(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(std::move(callback), error, std::move(value))); + } + break; + } case MPV_EVENT_LOG_MESSAGE: { auto* msg = static_cast(event->data); g_message("MPV [%s] %s: %s", msg->level, msg->prefix, msg->text); diff --git a/linux/runner/mpv/mpv_player.h b/linux/runner/mpv/mpv_player.h index efd5e9e3..cadf715d 100644 --- a/linux/runner/mpv/mpv_player.h +++ b/linux/runner/mpv/mpv_player.h @@ -15,6 +15,7 @@ #include #include #include +#include #include // 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& args); - /// Callback type for async command completion. - using CommandCallback = std::function; + /// Callback types for async mpv requests. + using StatusCallback = std::function; + using CommandCallback = StatusCallback; + using GetPropertyCallback = std::function; /// Executes an mpv command asynchronously to prevent UI blocking. void CommandAsync(const std::vector& 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 observed_properties_; std::map name_to_id_; - // Pending async commands: request_id -> callback - std::map pending_commands_; - std::mutex pending_commands_mutex_; + // Pending async requests: request_id -> callback + std::map pending_status_requests_; + std::map pending_get_property_requests_; + std::mutex pending_requests_mutex_; // GSource for processing events on main thread guint event_source_id_ = 0; diff --git a/linux/runner/mpv/mpv_plugin.cc b/linux/runner/mpv/mpv_plugin.cc index 02f319ba..98406c4c 100644 --- a/linux/runner/mpv/mpv_plugin.cc +++ b/linux/runner/mpv/mpv_plugin.cc @@ -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) { diff --git a/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift b/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift index a3abbd2e..76668435 100644 --- a/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift +++ b/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift @@ -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) } diff --git a/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift b/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift index d3dbe981..12fb2d13 100644 --- a/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift +++ b/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift @@ -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 = [ + internalSigPeakObserverId, + internalWidthObserverId, + internalHeightObserverId, + ] + let queue = DispatchQueue(label: "mpv", qos: .userInitiated) private let queueKey = DispatchSpecificKey() - private var pendingCommands: [UInt64: (Result) -> Void] = [:] - private let pendingCommandsLock = NSLock() + private enum PendingRequest { + case void((Result) -> Void) + case getProperty((Result) -> 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 + ) { + 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 + ) { + 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)? = 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) -> 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) { @@ -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?] = 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 + ) { + 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? = 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?] = ([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?.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: diff --git a/shared/apple/MpvPlayer/MpvPlayerPluginShared.swift b/shared/apple/MpvPlayer/MpvPlayerPluginShared.swift index 24155692..3baf381c 100644 --- a/shared/apple/MpvPlayer/MpvPlayerPluginShared.swift +++ b/shared/apple/MpvPlayer/MpvPlayerPluginShared.swift @@ -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) { diff --git a/windows/runner/mpv/mpv_player.cpp b/windows/runner/mpv/mpv_player.cpp index 0064c4cc..7c91f4da 100644 --- a/windows/runner/mpv/mpv_player.cpp +++ b/windows/runner/mpv/mpv_player.cpp @@ -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 status_callbacks; + std::vector get_callbacks; { - std::lock_guard lock(pending_commands_mutex_); - for (auto& pair : pending_commands_) { - if (pair.second) pair.second(-1); // Call with error + std::lock_guard 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& args) { - if (!mpv_) return; - - std::vector 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& args) { CommandAsync(args, nullptr); } void MpvPlayer::CommandAsync(const std::vector& args, CommandCallback callback) { if (!mpv_) { @@ -130,50 +131,88 @@ void MpvPlayer::CommandAsync(const std::vector& args, CommandCallba } c_args.push_back(nullptr); - // Generate unique request ID and store callback - uint64_t request_id; - { - std::lock_guard 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 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(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 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 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 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 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 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(event->data); + if (prop && prop->format == MPV_FORMAT_STRING && prop->data) { + auto c_value = *static_cast(prop->data); + if (c_value) value = SanitizeUtf8(c_value); + } + } + callback(event->error, value); + } + break; + } case MPV_EVENT_LOG_MESSAGE: { auto* msg = static_cast(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_); diff --git a/windows/runner/mpv/mpv_player.h b/windows/runner/mpv/mpv_player.h index 142ef5c9..f8d20d94 100644 --- a/windows/runner/mpv/mpv_player.h +++ b/windows/runner/mpv/mpv_player.h @@ -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& args); - // Callback type for async command completion. - using CommandCallback = std::function; + // Callback types for async mpv requests. + using StatusCallback = std::function; + using CommandCallback = StatusCallback; + using GetPropertyCallback = std::function; // 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& 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 observed_properties_; std::map name_to_id_; - // Pending async commands: request_id -> callback - std::map pending_commands_; - std::mutex pending_commands_mutex_; + // Pending async requests: request_id -> callback + std::map pending_status_requests_; + std::map 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); }; diff --git a/windows/runner/mpv/mpv_plugin.cpp b/windows/runner/mpv/mpv_plugin.cpp index 6d6de516..8236431e 100644 --- a/windows/runner/mpv/mpv_plugin.cpp +++ b/windows/runner/mpv/mpv_plugin.cpp @@ -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(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>( 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 task) { + if (::GetCurrentThreadId() == platform_thread_id_) { + task(); + return; + } + + { + std::lock_guard lock(platform_tasks_mutex_); + platform_tasks_.push(std::move(task)); + } + + if (flutter_window_) { + ::PostMessage(flutter_window_, kPlatformTaskMessage, 0, 0); + } +} + +void MpvPlayerPlugin::DrainPlatformTasks() { + std::queue> tasks; + { + std::lock_guard lock(platform_tasks_mutex_); + tasks.swap(platform_tasks_); + } + + while (!tasks.empty()) { + tasks.front()(); + tasks.pop(); + } +} + void MpvPlayerPlugin::HandleMethodCall( const flutter::MethodCall& method_call, std::unique_ptr> result) { @@ -77,11 +113,17 @@ void MpvPlayerPlugin::HandleMethodCall( } HWND flutter_window = GetWindow(); + flutter_window_ = flutter_window; MpvCore::SetInstance(std::make_unique(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(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::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(name_it->second), std::get(value_it->second)); - result->Success(); + auto result_ptr = + std::make_shared>>(std::move(result)); + player_->SetPropertyAsync( + std::get(name_it->second), std::get(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(name_it->second)); - if (value.empty()) { - result->Success(); - } else { - result->Success(flutter::EncodableValue(value)); - } + auto result_ptr = + std::make_shared>>(std::move(result)); + player_->GetPropertyAsync( + std::get(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"); diff --git a/windows/runner/mpv/mpv_plugin.h b/windows/runner/mpv/mpv_plugin.h index 57fd31fd..c610c082 100644 --- a/windows/runner/mpv/mpv_plugin.h +++ b/windows/runner/mpv/mpv_plugin.h @@ -8,8 +8,11 @@ #include #include +#include #include +#include #include +#include #include "display_mode_manager.h" #include "mpv_core.h" @@ -33,11 +36,15 @@ class MpvPlayerPlugin : public flutter::Plugin { std::unique_ptr> result); void SendEvent(const flutter::EncodableValue& event); + void PostToPlatformThread(std::function task); + void DrainPlatformTasks(); HWND GetWindow(); HWND GetChildWindow(); flutter::PluginRegistrarWindows* registrar_; + DWORD platform_thread_id_; + HWND flutter_window_ = nullptr; std::unique_ptr> method_channel_; std::unique_ptr> event_channel_; std::unique_ptr> event_sink_; @@ -45,6 +52,8 @@ class MpvPlayerPlugin : public flutter::Plugin { std::unique_ptr player_; DisplayModeManager display_mode_manager_; std::optional proc_id_; + std::mutex platform_tasks_mutex_; + std::queue> platform_tasks_; }; } // namespace mpv