From 3d36ecf094b5faf2228776bb2aa15fec7f8ddcd0 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 12 Jul 2026 05:10:12 +0200 Subject: [PATCH] fix(mpv): harden native initialization and logging --- ios/Runner.xcodeproj/project.pbxproj | 1 + .../apple/MpvPlayer/MpvPlayerCoreBase.swift | 10 ++-- test/mpv/player_native_bridge_test.dart | 59 +++++++++++++++++++ tvos/Runner.xcodeproj/project.pbxproj | 1 + windows/runner/mpv/mpv_plugin.cpp | 8 +++ 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9c59afa3..aa76c415 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -727,6 +727,7 @@ MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; diff --git a/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift b/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift index b2bb5cbf..a9f495b6 100644 --- a/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift +++ b/shared/apple/MpvPlayer/MpvPlayerCoreBase.swift @@ -387,10 +387,12 @@ class MpvPlayerCoreBase: NSObject { return false } - // TEMP: always "v" (tvOS builds in release) so the avfoundation VO's - // osd-perf timing lines show up — restore the DEBUG/warn split after the - // subtitle-timing investigation. - checkError(mpv_request_log_messages(mpv, "v")) + #if DEBUG + let defaultLogLevel = "v" + #else + let defaultLogLevel = "warn" + #endif + checkError(mpv_request_log_messages(mpv, defaultLogLevel)) configure() diff --git a/test/mpv/player_native_bridge_test.dart b/test/mpv/player_native_bridge_test.dart index a6415c42..d37bcbaf 100644 --- a/test/mpv/player_native_bridge_test.dart +++ b/test/mpv/player_native_bridge_test.dart @@ -1,3 +1,5 @@ +import 'dart:async' show Completer; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/mpv/models.dart'; @@ -16,6 +18,41 @@ void main() { await SettingsService.getInstance(); }); + test('MPV coalesces concurrent Dart initialization requests', () async { + final initialize = Completer(); + final calls = []; + + await withMockPlayerChannels( + methodChannelName: 'com.plezy/mpv_player', + eventChannelName: 'com.plezy/mpv_player/events', + methodHandler: (call) { + calls.add(call); + if (call.method == 'initialize') return initialize.future; + return Future.value(null); + }, + testBody: () async { + final player = PlayerNative(); + try { + final logLevel = player.setLogLevel('warn'); + final command = player.command(['stop']); + await Future.delayed(Duration.zero); + + expect(calls.where((call) => call.method == 'initialize'), hasLength(1)); + + initialize.complete(true); + await Future.wait([logLevel, command]); + + expect(calls.where((call) => call.method == 'initialize'), hasLength(1)); + expect(calls.where((call) => call.method == 'setLogLevel'), hasLength(1)); + expect(calls.where((call) => call.method == 'command'), hasLength(1)); + } finally { + if (!initialize.isCompleted) initialize.complete(true); + await player.dispose(); + } + }, + ); + }); + test('Android command failure reaches seek recovery', () async { await withMockPlayerChannels( methodChannelName: 'com.plezy/mpv_player', @@ -64,6 +101,28 @@ void main() { ); }); + test('audio setLogLevel uses the dedicated native channel', () async { + MethodCall? logLevelCall; + await withMockPlayerChannels( + methodChannelName: 'com.plezy/mpv_audio_player', + eventChannelName: 'com.plezy/mpv_audio_player/events', + methodHandler: (call) async { + if (call.method == 'initialize') return true; + if (call.method == 'setLogLevel') logLevelCall = call; + return null; + }, + testBody: () async { + final player = PlayerNative.audio(); + try { + await player.setLogLevel('v'); + expect(logLevelCall?.arguments, {'level': 'v'}); + } finally { + await player.dispose(); + } + }, + ); + }); + test('Android mpv end-file error preserves native diagnostic message', () async { await withMockPlayerChannels( methodChannelName: 'com.plezy/mpv_player', diff --git a/tvos/Runner.xcodeproj/project.pbxproj b/tvos/Runner.xcodeproj/project.pbxproj index 850f7a56..18fd6930 100644 --- a/tvos/Runner.xcodeproj/project.pbxproj +++ b/tvos/Runner.xcodeproj/project.pbxproj @@ -862,6 +862,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; diff --git a/windows/runner/mpv/mpv_plugin.cpp b/windows/runner/mpv/mpv_plugin.cpp index 139ab001..ed38ee51 100644 --- a/windows/runner/mpv/mpv_plugin.cpp +++ b/windows/runner/mpv/mpv_plugin.cpp @@ -132,6 +132,14 @@ void MpvPlayerPlugin::HandleMethodCall( const auto& method = method_call.method_name(); if (method == "initialize") { + // Method-channel calls are serialized on the platform thread. Keep an + // already-live core instead of replacing it (which would synchronously + // join its event thread before MpvPlayer::Initialize can see its guard). + if (player_ && player_->IsInitialized()) { + result->Success(flutter::EncodableValue(true)); + return; + } + if (proc_id_) { registrar_->UnregisterTopLevelWindowProcDelegate(proc_id_.value()); proc_id_ = std::nullopt;