From d181bd34ae53defbfcc88d4096a935d05e2b4e45 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 3 Sep 2026 08:17:47 +0200 Subject: [PATCH] chore(mpv): share desktop startup options and stop forwarding unused Android events Linux and Windows each wrote the same eleven pre-initialize mpv options (keep-open, idle, input disabling, OSC, ytdl, audio fallback, and the audio-only set); ApplyCommonStartupOptions in mpv_player_common.h owns them once, with the ytdl security rationale kept in one place. Rendering, windowing, HDR, and log-level options stay per platform. The Android JNI event thread forwarded every mpv event to Kotlin, where MpvEvent modelled ten variants but MpvPlayerCore consumes four. The native switch now forwards only START_FILE, FILE_LOADED, and PLAYBACK_RESTART (END_FILE keeps its own path), and the unused variants are gone. --- .../com/edde746/plezy/mpv/MpvPlayerCore.kt | 1 - android/libmpv/src/main/cpp/event.cpp | 7 ++++- .../java/com/edde746/plezy/libmpv/MpvEvent.kt | 16 ++-------- linux/runner/mpv/mpv_player.cc | 29 +++-------------- shared/mpv/mpv_player_common.h | 31 +++++++++++++++++++ windows/runner/mpv/mpv_player.cpp | 30 ++---------------- 6 files changed, 46 insertions(+), 68 deletions(-) diff --git a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt index 9a4d83750..b3db277b6 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/mpv/MpvPlayerCore.kt @@ -537,7 +537,6 @@ class MpvPlayerCore private constructor( delegate?.onEvent("file-loaded", null) } is MpvEvent.PlaybackRestart -> delegate?.onEvent("playback-restart", null) - else -> {} } } } diff --git a/android/libmpv/src/main/cpp/event.cpp b/android/libmpv/src/main/cpp/event.cpp index 6f0e27e94..1562c8efc 100644 --- a/android/libmpv/src/main/cpp/event.cpp +++ b/android/libmpv/src/main/cpp/event.cpp @@ -81,10 +81,15 @@ void* event_thread(void* arg) { case MPV_EVENT_END_FILE: sendEndFileToJava(env, mp_event); break; - default: + case MPV_EVENT_START_FILE: + case MPV_EVENT_FILE_LOADED: + case MPV_EVENT_PLAYBACK_RESTART: ALOGV("event: %s\n", mpv_event_name(mp_event->event_id)); sendEventToJava(env, mp_event->event_id); break; + default: + // Nothing on the Kotlin side consumes the remaining ids (MpvEvent.fromId). + break; } } diff --git a/android/libmpv/src/main/java/com/edde746/plezy/libmpv/MpvEvent.kt b/android/libmpv/src/main/java/com/edde746/plezy/libmpv/MpvEvent.kt index 4bad9ab82..e76e02fc6 100644 --- a/android/libmpv/src/main/java/com/edde746/plezy/libmpv/MpvEvent.kt +++ b/android/libmpv/src/main/java/com/edde746/plezy/libmpv/MpvEvent.kt @@ -1,30 +1,18 @@ package com.edde746.plezy.libmpv sealed interface MpvEvent { - data object Shutdown : MpvEvent data object StartFile : MpvEvent data class EndFile(val reason: EndFileReason?) : MpvEvent data object FileLoaded : MpvEvent - data object VideoReconfig : MpvEvent - data object AudioReconfig : MpvEvent - data object Seek : MpvEvent data object PlaybackRestart : MpvEvent - data object QueueOverflow : MpvEvent - data class Other(val eventId: Int) : MpvEvent companion object { + // Mirrors the ids event.cpp forwards; END_FILE arrives via its own JNI path. internal fun fromId(id: Int): MpvEvent? = when (id) { - 1 -> Shutdown 6 -> StartFile 8 -> FileLoaded - 17 -> VideoReconfig - 18 -> AudioReconfig - 20 -> Seek 21 -> PlaybackRestart - 24 -> QueueOverflow - // 0=NONE, 2=LOG_MESSAGE, 7=END_FILE, 22=PROPERTY_CHANGE handled separately - 0, 2, 7, 22 -> null - else -> Other(id) + else -> null } } } diff --git a/linux/runner/mpv/mpv_player.cc b/linux/runner/mpv/mpv_player.cc index f7933504f..be1507823 100644 --- a/linux/runner/mpv/mpv_player.cc +++ b/linux/runner/mpv/mpv_player.cc @@ -333,23 +333,14 @@ bool MpvPlayer::Initialize() { return false; } - if (audio_only_) { - // Music core: no VO, no video decode. vid=no keeps embedded cover art - // from ever becoming a video track, and force-window/audio-display make - // sure mpv never opens a video output for it either. - mpv_set_option_string(mpv_, "vid", "no"); - mpv_set_option_string(mpv_, "force-window", "no"); - mpv_set_option_string(mpv_, "audio-display", "no"); - mpv_set_option_string(mpv_, "gapless-audio", "weak"); - } else { + plezy::mpv_common::ApplyCommonStartupOptions(mpv_, audio_only_); + mpv_set_option_string(mpv_, "terminal", "no"); + + if (!audio_only_) { // Configure mpv for embedded playback. mpv_set_option_string(mpv_, "vo", "libmpv"); mpv_set_option_string(mpv_, "hwdec", "auto"); - } - mpv_set_option_string(mpv_, "keep-open", "yes"); - mpv_set_option_string(mpv_, "audio-fallback-to-null", "yes"); - if (!audio_only_) { // hdr-compute-peak is nested under the same predicate as the tone-map pass - // it runs exactly when the source's declared peak exceeds target-peak - so it // costs nothing while the compositor owns tone mapping and gives @@ -364,18 +355,6 @@ bool MpvPlayer::Initialize() { // `hdr-enabled` write puts here through SetHDREnabled. mpv_set_option_string(mpv_, "target-colorspace-hint", plezy::mpv_common::TargetColorspaceHint(hdr_enabled_)); } - mpv_set_option_string(mpv_, "idle", "yes"); - mpv_set_option_string(mpv_, "input-default-bindings", "no"); - mpv_set_option_string(mpv_, "input-vo-keyboard", "no"); - mpv_set_option_string(mpv_, "osc", "no"); - mpv_set_option_string(mpv_, "terminal", "no"); - // Every URL Plezy opens is a media-server stream or a local file, never a - // site mpv's bundled ytdl_hook could resolve. Loading it costs an on_load - // hook per open and, on a failed open, spawns yt-dlp with the full stream - // URL — access token included — in its argv, where /proc exposes it. mpv - // gates loading the builtin script on this option at mpv_initialize time, - // so it has to be set here rather than from Dart. - mpv_set_option_string(mpv_, "ytdl", "no"); // Default to info-level logging. The vaapi hwdec probe and the "Using // software decoding" fallback are MSGL_INFO messages, and both are the only diff --git a/shared/mpv/mpv_player_common.h b/shared/mpv/mpv_player_common.h index d1500eba9..c26aff569 100644 --- a/shared/mpv/mpv_player_common.h +++ b/shared/mpv/mpv_player_common.h @@ -370,6 +370,37 @@ inline bool ParseEnabledFlag(const std::string& value) { return value == "yes" | inline const char* TargetColorspaceHint(bool hdr_enabled) { return hdr_enabled ? "auto" : "no"; } +// Startup options shared by every desktop mpv core. Must run between +// mpv_create() and mpv_initialize(); platform-specific options (vo, hwdec, +// wid, HDR/tone-mapping, log level) stay with the caller. +inline void ApplyCommonStartupOptions(mpv_handle* mpv, bool audio_only) { + if (audio_only) { + // Music core: no VO, no video decode. vid=no keeps embedded cover art + // from ever becoming a video track, and force-window/audio-display make + // sure mpv never opens a video output for it either. + mpv_set_option_string(mpv, "vid", "no"); + mpv_set_option_string(mpv, "force-window", "no"); + mpv_set_option_string(mpv, "audio-display", "no"); + mpv_set_option_string(mpv, "gapless-audio", "weak"); + } + mpv_set_option_string(mpv, "keep-open", "yes"); + // When the audio device becomes unavailable (sleep, device unplug), fall + // back to the null audio output instead of permanently dropping the audio + // track. Recovery is handled by the platform event loop. + mpv_set_option_string(mpv, "audio-fallback-to-null", "yes"); + mpv_set_option_string(mpv, "idle", "yes"); + mpv_set_option_string(mpv, "input-default-bindings", "no"); + mpv_set_option_string(mpv, "input-vo-keyboard", "no"); + mpv_set_option_string(mpv, "osc", "no"); + // Every URL Plezy opens is a media-server stream or a local file, never a + // site mpv's bundled ytdl_hook could resolve. Loading it costs an on_load + // hook per open and, on a failed open, spawns yt-dlp with the full stream + // URL — access token included — in its argv, where other processes can read + // it. mpv gates loading the builtin script on this option at mpv_initialize + // time, so it has to be set here rather than from Dart. + mpv_set_option_string(mpv, "ytdl", "no"); +} + enum class AudioReloadReason { kNone, kResume, kNullFallback }; struct AudioReloadAction { diff --git a/windows/runner/mpv/mpv_player.cpp b/windows/runner/mpv/mpv_player.cpp index d1704884a..08099ac85 100644 --- a/windows/runner/mpv/mpv_player.cpp +++ b/windows/runner/mpv/mpv_player.cpp @@ -564,16 +564,9 @@ bool MpvPlayer::Initialize(HWND view) { return false; } - if (audio_only_) { - // Windowless music core: no HWND, no VO, no video decode. vid=no keeps - // embedded cover art from ever becoming a video track, and - // force-window/audio-display make sure mpv never opens a video output - // for it either. - mpv_set_option_string(mpv_, "vid", "no"); - mpv_set_option_string(mpv_, "force-window", "no"); - mpv_set_option_string(mpv_, "audio-display", "no"); - mpv_set_option_string(mpv_, "gapless-audio", "weak"); - } else { + plezy::mpv_common::ApplyCommonStartupOptions(mpv_, audio_only_); + + if (!audio_only_) { // Create a child window for mpv to render into, parented to the Flutter // |view|. The video child then sits in the view's own per-window layer // stack, above the view's (never-painted) layer-1 content and below the @@ -604,21 +597,9 @@ bool MpvPlayer::Initialize(HWND view) { // hwdec is set from Flutter via setProperty based on user preference } - // Configure mpv for embedded playback. - mpv_set_option_string(mpv_, "keep-open", "yes"); - mpv_set_option_string(mpv_, "idle", "yes"); - mpv_set_option_string(mpv_, "input-default-bindings", "no"); - mpv_set_option_string(mpv_, "input-vo-keyboard", "no"); // Hardware media keys are owned by the SMTC integration (os_media_controls); // mpv's default handling would double-handle Play/Pause. mpv_set_option_string(mpv_, "input-media-keys", "no"); - mpv_set_option_string(mpv_, "osc", "no"); - // Never resolve URLs through mpv's bundled ytdl_hook: Plezy only ever opens - // media-server streams and local files, the hook adds a per-open on_load - // round trip, and on a failed open it spawns yt-dlp with the access token in - // its argv. mpv decides whether to load the builtin script during - // mpv_initialize, so this must be an option, not a Dart setProperty. - mpv_set_option_string(mpv_, "ytdl", "no"); if (!audio_only_) { // Let mpv use display/context detection instead of forcing HDR signaling. @@ -639,11 +620,6 @@ bool MpvPlayer::Initialize(HWND view) { } } - // When WASAPI becomes unavailable (sleep, device unplug), fall back to null - // audio output instead of permanently dropping the audio track. Recovery is - // handled by MaybeRunAudioRecovery in the event loop. - mpv_set_option_string(mpv_, "audio-fallback-to-null", "yes"); - // Default to warn-level logging; Dart side can raise to "v" if debug logging is enabled. mpv_request_log_messages(mpv_, "warn");