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");