diff --git a/lib/mpv/player/player.dart b/lib/mpv/player/player.dart index 64f51521..8bd629f8 100644 --- a/lib/mpv/player/player.dart +++ b/lib/mpv/player/player.dart @@ -142,6 +142,12 @@ abstract class Player { /// Get an MPV property value by name. Future getProperty(String name); + /// Set the native MPV log message level (e.g., "warn", "v", "debug"). + /// + /// This controls the volume of log messages sent from the native player + /// over the event channel. Use "warn" in production and "v" for debugging. + Future setLogLevel(String level); + /// Execute a raw MPV command. /// /// [args] - Command and arguments as a list of strings. diff --git a/lib/mpv/player/player_base.dart b/lib/mpv/player/player_base.dart index a27efe3c..1fff43fa 100644 --- a/lib/mpv/player/player_base.dart +++ b/lib/mpv/player/player_base.dart @@ -436,6 +436,10 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { // ignore: no-empty-block - base no-op, overridden by platform subclasses Future setAudioPassthrough(bool enabled) async {} + @override + // ignore: no-empty-block - base no-op, overridden by platform subclasses + Future setLogLevel(String level) async {} + // ============================================ // Lifecycle // ============================================ diff --git a/lib/mpv/player/player_native.dart b/lib/mpv/player/player_native.dart index b46cecdb..5c570c9f 100644 --- a/lib/mpv/player/player_native.dart +++ b/lib/mpv/player/player_native.dart @@ -230,6 +230,17 @@ class PlayerNative extends PlayerBase { await methodChannel.invokeMethod('command', {'args': args}); } + // ============================================ + // Log Level + // ============================================ + + @override + Future setLogLevel(String level) async { + checkDisposed(); + await _ensureInitialized(); + await methodChannel.invokeMethod('setLogLevel', {'level': level}); + } + // ============================================ // Passthrough // ============================================ diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index ebdeec15..22850fc0 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -387,6 +387,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin await player!.setProperty('sub-ass', 'yes'); // Enable libass await player!.setProperty('demuxer-max-bytes', bufferSizeBytes.toString()); await player!.setProperty('msg-level', debugLoggingEnabled ? 'all=debug' : 'all=error'); + await player!.setLogLevel(debugLoggingEnabled ? 'v' : 'warn'); await player!.setProperty('hwdec', _getHwdecValue(enableHardwareDecoding)); // Subtitle styling diff --git a/linux/runner/mpv/mpv_player.cc b/linux/runner/mpv/mpv_player.cc index 2e4f4980..e76c1383 100644 --- a/linux/runner/mpv/mpv_player.cc +++ b/linux/runner/mpv/mpv_player.cc @@ -81,8 +81,8 @@ bool MpvPlayer::Initialize(GtkGLArea* gl_area) { mpv_set_option_string(mpv_, "osc", "no"); mpv_set_option_string(mpv_, "terminal", "no"); - // Enable verbose logging for debugging. - mpv_request_log_messages(mpv_, "v"); + // Default to warn-level logging; Dart side can raise to "v" if debug logging is enabled. + mpv_request_log_messages(mpv_, "warn"); // Initialize mpv. int err = mpv_initialize(mpv_); @@ -315,9 +315,13 @@ void MpvPlayer::SetEventCallback(EventCallback callback) { void MpvPlayer::RequestRedraw() { if (disposed_) return; - needs_redraw_.store(true); + // Only queue one idle handler at a time + bool expected = false; + if (!needs_redraw_.compare_exchange_strong(expected, true)) { + return; // Already have a pending redraw + } + if (gl_area_) { - // Queue redraw on main thread GtkGLArea* area = gl_area_; g_idle_add_full( GDK_PRIORITY_REDRAW, @@ -333,6 +337,11 @@ void MpvPlayer::RequestRedraw() { } } +void MpvPlayer::SetLogLevel(const std::string& level) { + if (disposed_ || !mpv_) return; + mpv_request_log_messages(mpv_, level.c_str()); +} + void MpvPlayer::OnMpvWakeup(void* ctx) { auto* player = static_cast(ctx); diff --git a/linux/runner/mpv/mpv_player.h b/linux/runner/mpv/mpv_player.h index 0b6ca69a..a314c359 100644 --- a/linux/runner/mpv/mpv_player.h +++ b/linux/runner/mpv/mpv_player.h @@ -101,6 +101,9 @@ class MpvPlayer { /// Request a redraw. void RequestRedraw(); + /// Sets the MPV log message level (e.g., "warn", "v", "debug"). + void SetLogLevel(const std::string& level); + private: /// MPV event wakeup callback (called from mpv thread). static void OnMpvWakeup(void* ctx); diff --git a/linux/runner/mpv/mpv_plugin.cc b/linux/runner/mpv/mpv_plugin.cc index 0145b876..3fd4d73d 100644 --- a/linux/runner/mpv/mpv_plugin.cc +++ b/linux/runner/mpv/mpv_plugin.cc @@ -181,8 +181,8 @@ static gboolean on_gl_render(GtkGLArea* area, // Set viewport and render the video frame. glViewport(0, 0, width, height); - self->player->Render(width, height, fbo); self->player->ClearRedrawFlag(); + self->player->Render(width, height, fbo); // Restore GL state after MPV render to prevent Flutter corruption. glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]); @@ -381,6 +381,22 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } } + } else if (strcmp(method, "setLogLevel") == 0) { + if (!self->player || !self->initialized) { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "NOT_INITIALIZED", "Player not initialized", nullptr)); + } else { + FlValue* level_value = fl_value_lookup_string(args, "level"); + + if (level_value == nullptr || + fl_value_get_type(level_value) != FL_VALUE_TYPE_STRING) { + response = FL_METHOD_RESPONSE(fl_method_error_response_new( + "INVALID_ARGS", "Missing 'level'", nullptr)); + } else { + self->player->SetLogLevel(fl_value_get_string(level_value)); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } + } } else if (strcmp(method, "getProperty") == 0) { if (!self->player || !self->initialized) { response = FL_METHOD_RESPONSE(fl_method_error_response_new( diff --git a/windows/runner/mpv/mpv_player.cpp b/windows/runner/mpv/mpv_player.cpp index 5e1d6a34..b2f6862e 100644 --- a/windows/runner/mpv/mpv_player.cpp +++ b/windows/runner/mpv/mpv_player.cpp @@ -84,8 +84,8 @@ bool MpvPlayer::Initialize(HWND container, HWND flutter_window) { mpv_set_option_string(mpv_, "tone-mapping", "auto"); mpv_set_option_string(mpv_, "hdr-compute-peak", "auto"); - // Enable logging - mpv_request_log_messages(mpv_, "v"); + // Default to warn-level logging; Dart side can raise to "v" if debug logging is enabled. + mpv_request_log_messages(mpv_, "warn"); // Initialize mpv. LogToFile("MpvPlayer::Initialize - calling mpv_initialize()"); @@ -282,6 +282,11 @@ void MpvPlayer::SetVisible(bool visible) { } } +void MpvPlayer::SetLogLevel(const std::string& level) { + if (!mpv_) return; + mpv_request_log_messages(mpv_, level.c_str()); +} + void MpvPlayer::SetEventCallback(EventCallback callback) { std::lock_guard lock(callback_mutex_); event_callback_ = std::move(callback); diff --git a/windows/runner/mpv/mpv_player.h b/windows/runner/mpv/mpv_player.h index 98707f16..f144fa86 100644 --- a/windows/runner/mpv/mpv_player.h +++ b/windows/runner/mpv/mpv_player.h @@ -65,6 +65,9 @@ class MpvPlayer { // Shows or hides the video window. void SetVisible(bool visible); + // Sets the MPV log message level (e.g., "warn", "v", "debug"). + void SetLogLevel(const std::string& level); + // Sets the event callback for property changes and events. void SetEventCallback(EventCallback callback); diff --git a/windows/runner/mpv/mpv_plugin.cpp b/windows/runner/mpv/mpv_plugin.cpp index ea511309..e28327af 100644 --- a/windows/runner/mpv/mpv_plugin.cpp +++ b/windows/runner/mpv/mpv_plugin.cpp @@ -248,6 +248,29 @@ void MpvPlayerPlugin::HandleMethodCall( player_->SetProperty(std::get(name_it->second), std::get(value_it->second)); result->Success(); + } else if (method == "setLogLevel") { + if (!player_ || !player_->IsInitialized()) { + result->Error("NOT_INITIALIZED", "Player not initialized"); + return; + } + + const auto* args = method_call.arguments(); + if (!args || !std::holds_alternative(*args)) { + result->Error("INVALID_ARGS", "Expected map argument"); + return; + } + + const auto& map = std::get(*args); + auto level_it = map.find(flutter::EncodableValue("level")); + + if (level_it == map.end() || + !std::holds_alternative(level_it->second)) { + result->Error("INVALID_ARGS", "Missing 'level'"); + return; + } + + player_->SetLogLevel(std::get(level_it->second)); + result->Success(); } else if (method == "getProperty") { if (!player_ || !player_->IsInitialized()) { result->Error("NOT_INITIALIZED", "Player not initialized");