fix: resolve UI freezes during video playback on Steam Deck
- Default MPV native log level to "warn" instead of "v" (Linux/Windows), add setLogLevel method channel to raise it when debug logging is enabled - Coalesce MPV render requests via atomic compare_exchange on needs_redraw_ - Clear redraw flag before render so new redraws queue during current frame
This commit is contained in:
@@ -142,6 +142,12 @@ abstract class Player {
|
||||
/// Get an MPV property value by name.
|
||||
Future<String?> 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<void> setLogLevel(String level);
|
||||
|
||||
/// Execute a raw MPV command.
|
||||
///
|
||||
/// [args] - Command and arguments as a list of strings.
|
||||
|
||||
@@ -436,6 +436,10 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
|
||||
// ignore: no-empty-block - base no-op, overridden by platform subclasses
|
||||
Future<void> setAudioPassthrough(bool enabled) async {}
|
||||
|
||||
@override
|
||||
// ignore: no-empty-block - base no-op, overridden by platform subclasses
|
||||
Future<void> setLogLevel(String level) async {}
|
||||
|
||||
// ============================================
|
||||
// Lifecycle
|
||||
// ============================================
|
||||
|
||||
@@ -230,6 +230,17 @@ class PlayerNative extends PlayerBase {
|
||||
await methodChannel.invokeMethod('command', {'args': args});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Log Level
|
||||
// ============================================
|
||||
|
||||
@override
|
||||
Future<void> setLogLevel(String level) async {
|
||||
checkDisposed();
|
||||
await _ensureInitialized();
|
||||
await methodChannel.invokeMethod('setLogLevel', {'level': level});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Passthrough
|
||||
// ============================================
|
||||
|
||||
@@ -387,6 +387,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> 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
|
||||
|
||||
@@ -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<MpvPlayer*>(ctx);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<std::mutex> lock(callback_mutex_);
|
||||
event_callback_ = std::move(callback);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -248,6 +248,29 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
player_->SetProperty(std::get<std::string>(name_it->second),
|
||||
std::get<std::string>(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<flutter::EncodableMap>(*args)) {
|
||||
result->Error("INVALID_ARGS", "Expected map argument");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& map = std::get<flutter::EncodableMap>(*args);
|
||||
auto level_it = map.find(flutter::EncodableValue("level"));
|
||||
|
||||
if (level_it == map.end() ||
|
||||
!std::holds_alternative<std::string>(level_it->second)) {
|
||||
result->Error("INVALID_ARGS", "Missing 'level'");
|
||||
return;
|
||||
}
|
||||
|
||||
player_->SetLogLevel(std::get<std::string>(level_it->second));
|
||||
result->Success();
|
||||
} else if (method == "getProperty") {
|
||||
if (!player_ || !player_->IsInitialized()) {
|
||||
result->Error("NOT_INITIALIZED", "Player not initialized");
|
||||
|
||||
Reference in New Issue
Block a user