feat(playback): report Embedded MPV file chapters on the timeline (#1771)

This commit is contained in:
4gray authored and GitHub committed 2026-09-30 22:50:27 +02:00
1 parent d0e366a368
commit 9bc577a4c9
24 files changed
+931 -5

No files matched your search

+2
View File
@@ -487,6 +487,8 @@ Audio tracks are discovered from MPV's `track-list` property. The selected track
Subtitle tracks mirror the audio-track contract: same `track-list` source, same parsing pipeline, but selected through MPV's `sid` property. A `trackId` of `-1` from the renderer is interpreted as "disable subtitles" and translated to `sid=no` at the addon boundary. Playback speed is observed and set through MPV's `speed` property, clamped at the addon to `[0.25, 4.0]`. Aspect override uses MPV's `video-aspect-override` property as a passthrough string ("no", "16:9", "4:3", "21:9", "2.35:1"). All four properties (`sid`, `speed`, `video-aspect-override`, plus `aid`) are observed at session init so renderer state stays in sync with the native side without needing extra round-trips.
File chapters come from MPV's `chapter-list` property, observed as a node next to `track-list` by the macOS and Windows addons and the frame-copy helper, and cleared on every `START_FILE` so a replacement file never shows the previous one's chapters. Each snapshot carries `chapters: [{ timeSeconds, title? }]` (the helper always emits the key, because the adapter merges snapshot events field by field); `EmbeddedMpvNativeService` validates it with `normalizeEmbeddedMpvChapters` into `EmbeddedMpvSession.chapters`, and an older binary without the field yields `[]`. The Linux `mpv --wid` poller does not read chapters. The renderer maps them to timeline segments — see "Timeline segments" in the [player controls contract](player-controls-contract.md).
The renderer learns which features the loaded addon binary supports through the `EmbeddedMpvSupport.capabilities` field returned from `getEmbeddedMpvSupport()`. The service probes `typeof addon.<method> === 'function'` for each optional native export. Older addon binaries with the original audio-only surface return `capabilities: { subtitles: false, playbackSpeed: false, aspectOverride: false, screenshot: false, recording: false }`, and the renderer hides the corresponding controls instead of throwing at runtime. Linux intentionally does not export libmpv-only optional controls while it uses the process-isolated `mpv --wid` backend.
Linux audio-track discovery works differently from macOS/Windows because the hand-rolled JSON IPC reply parser only understands scalar `data` values: the poll loop reads `track-list/count` every tick and walks the scalar `track-list/N/{type,id,title,lang,default,forced}` sub-properties only when the count changes. The selected track is reconciled from the scalar `aid` property on every tick (`aid` reads back non-numeric when audio is disabled, which maps to "no selection"). Track switching still goes through `set_property aid` over the same socket.
+20 -3
View File
@@ -366,7 +366,23 @@ always covers `[0, duration]`. Without segments — live playback, VOD and
series — the row is one untitled segment, which is the plain bar.
`ControlsTimeline` owns the normalized list and the per-segment fill for the
current scrub or playback value; the hover label becomes `Chapter 2 · 12:40`
over a titled segment. mpv's chapter list is not a producer yet.
over a titled segment.
**Chapter producer.** Embedded MPV reports the file's own chapters: the
addons and the frame-copy helper observe mpv `chapter-list` and publish it
as `EmbeddedMpvSession.chapters` (`{ timeSeconds, title? }`, validated in
main by `normalizeEmbeddedMpvChapters`). `buildChapterTimelineSegments`
(`chapter-timeline-segments.ts`) runs each chapter from its start to the
next one's, the last to the duration, orders them and drops repeated or
out-of-range starts; an untitled chapter becomes an untitled segment, and
no chapters or no finite duration yields `null`.
`EmbeddedMpvPlayerComponent.controlsTimelineSegments`
(`embeddedMpvTimelineSegments`) passes the host's segments when it
supplies any and the chapter segments otherwise, so catch-up programmes
always win; `sameTimelineSegments` keeps a re-polled, unchanged list from
counting as a change. The Linux native-view `mpv --wid` backend reads no
chapters, and the web engines report none (streamed HLS/DASH carries no
chapter track), so their VOD and series stay one plain bar.
**Catch-up producer.** Archive playback of a live channel passes the EPG
programmes overlapping its archive window.
@@ -413,8 +429,9 @@ episode: `upNextThresholdSeconds` is 4% of the duration clamped to
`timelineSegments` carry a closing-credits chapter in the last third
(`creditsStartSeconds`: "Credits", "Outro", "Ending", an anime `ED`, and a
few localized names), the card appears when it starts instead, at most
5 min before the end. No engine reports file chapters today, so in practice
the adaptive lead applies. The countdown shows whole minutes, and seconds
5 min before the end. Embedded MPV under the frame-copy engine supplies
file chapters (see the chapter producer above); every other engine, and a
file without a credits chapter, uses the adaptive lead. The countdown shows whole minutes, and seconds
in the last minute. A
still that fails to load falls back to the label tile. A
click emits `nextEpisodeRequested` directly — not through the