Live-TV clock generations were matched to mpv sources first-in-first-out: every
start-file popped the oldest registered open, on the assumption of exactly one
start-file per open in dispatch order. Opens without a generation on the same
player, an Android loadfile rejected silently by nativeCommand, and the
independent delivery of the command ack and the start-file event all broke
that, so a seek that reopened the stream could calibrate against the wrong
source.
The loadfile reply now carries mpv's playlist_entry_id on Android, Apple, Linux
and Windows, PlayerNative.open resolves with it, and the live session binds
each generation to that id explicitly. Source events that land before the
reply are buffered per id and replayed on binding; a rejected or unreachable
load fails its generation instead of leaving a phantom; opens with no
generation are invisible to clock binding. Android now reports a rejected mpv
command as COMMAND_FAILED like the other cores.
Audio-only mpv core on every platform (dedicated
com.plezy/mpv_audio_player channels): parameterized android/windows/
linux mpv plugins and a new apple MpvAudioPlayerCore, all skipping
video/window paths (vid=no, audio-display=no, gapless-audio=weak).
MusicPlaybackService drives an in-memory queue with shuffle/repeat,
file-loaded-event gapless arming (property edges coalesce and the
android bridge drops them), per-track progress reporting, OS media
controls, audio focus, sleep timer, and error auto-skip.
PlaybackCoordinator enforces one live native player: starting video
disposes the audio core first.
The two prior attempts at #783 failed for lack of a reliable trigger:
re-setting audio-device to its own value is a no-op, and gating
recovery on audio-device-list changes while current-ao is null never
fires in the real flows (a paused player only discovers the dead
WASAPI session at unpause, when the device list is already stable;
a zombie session that consumes samples silently never goes null).
Replace the single gate with a recovery scheduler on the mpv event
thread, ticked every wait-event timeout:
- WM_POWERBROADCAST resume (both PBT variants, via the plugin's
existing window-proc delegate) requests unconditional ao-reloads at
+1.5s and +6s after wake — the only trigger that reaches zombie
sessions; the second shot covers a first reload landing while the
audio stack is still restoring. The handoff is a single atomic
flag: no mpv calls or timers from the platform thread, so there is
nothing to clean up on dispose.
- current-ao falling to null arms clock-driven retries (budget 5,
x2 backoff capped at 8s); retries must be clock-driven because a
failed ao-reload falls back to null without a property change
event. A device-list change while null refreshes the budget, and
audio-device-list is now observed natively so recovery does not
depend on the Dart side.
Every trigger, attempt, and outcome logs as [mpv:audio-recovery] at
warn level so exported logs from the reporter are actionable if this
still misses. Verified with synthetic PBT_APMRESUMEAUTOMATIC while
playing and while paused: full WASAPI reinit both times, audio
restarts on unpause.
Ref #783
close#1453
Events were delivered to the Flutter event sink directly on the mpv event
thread while method replies were already marshaled; concurrent messenger
use from two threads intermittently crashed guests during the event burst
of an in-place media switch. Also joins the event thread before draining
platform tasks in the plugin destructor (reverse member destruction order
would otherwise let the event thread enqueue into a destroyed queue) and
coalesces the wakeup PostMessage behind a retry-safe flag.
- 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