You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(engine): rank subtitle-language pick by disposition, surface forced/SDH/commentary on TrackInfo (#73)
LoadOptions.preferredSubtitleLanguages (4.4.0) picked the first track in a matching language. It now picks
the best track within the first matching preference: full subtitles rank over SDH / forced / commentary
(from container dispositions) and text over bitmap. New TrackInfo.isForced / isHearingImpaired /
isCommentary surface those dispositions (read alongside the existing isDefault) so hosts can rank or filter
subtitleTracks the same way; pure selectSubtitleIndex + subtitlePickRank + isBitmapSubtitleCodec are exposed
and unit-tested.
Also fixes a pre-existing bitmap-classification bug in the native-subtitle rendition (#55): two sites
matched TrackInfo.codec (the libavcodec DECODER name, e.g. pgssub) against an exact-match Set of DESCRIPTOR
names (hdmv_pgs_subtitle, ...), so PGS / DVB / DVD bitmap tracks leaked into the mov_text trak table and the
store-attach set (only xsub matched by luck), producing phantom native-menu entries and a store/reader index
mismatch when prepareNativeSubtitles was set. Both sites now use the shared decoder-name isBitmapSubtitleCodec
classifier, matching the reader's enum classifier.
193 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B4hBXf5yHPUNxQsDavBKGk
/// Container disposition `COMMENT` (director / cast commentary tracks). Applies to audio and subtitle.
287
+
publicletisCommentary:Bool
279
288
/// EAC3 with JOC profile (Dolby Atmos). Lets the UI surface "Atmos" instead of the bed channel count (typically 5.1).
280
289
publicletisAtmos:Bool
281
290
282
291
/// ASS / SSA tracks only: `[Script Info]` + `[V4+ Styles]` + `[Events]` format line from codec extradata. Hosts rendering ASS styling themselves (see `LoadOptions.preserveASSMarkup`) need it to resolve style references. nil for all other track kinds.
Copy file name to clipboardExpand all lines: docs/formats.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -70,6 +70,15 @@ Subtitle packets are routed through the same demux loop as audio and video. No s
70
70
-**Bitmap codecs** (PGS / HDMV PGS / DVB / DVD) → `.image(SubtitleImage)`. The indexed pixel plane is walked through its palette, premultiplied against alpha, and wrapped as a `CGImage`. Position is normalised in `[0..1]` against the source video frame so the host scales to any on-screen rect.
71
71
-**Sidecar files** (a separate `.srt` / `.ass` / `.vtt` URL) → `selectSidecarSubtitle(url:httpHeaders:)` opens its own short-lived `AVFormatContext`, decodes the whole file once, atomically swaps the result into `subtitleCues`. The fetch forwards the session's `LoadOptions.httpHeaders` by default (WebDAV auth and friends); pass the call's own `httpHeaders` to override per fetch.
72
72
73
+
### Track selection by language preference
74
+
75
+
`LoadOptions` can seed the initial audio and subtitle tracks from an ordered language preference, resolved from the engine's single probe so a host honors a saved preference without a separate pre-probe or a post-load reload:
76
+
77
+
-**`preferredAudioLanguages`** (ordered ISO 639-1 / 639-2 codes or English names, e.g. `["en", "de"]`) picks the first-frame audio track: an explicit `audioSourceStreamIndex` wins, else the first track matching a preference in order, else the container default. The pick is muxed into the loopback HLS, so it is correct on the first frame with no `selectAudioTrack` reload.
78
+
-**`preferredSubtitleLanguages`** activates a subtitle at the end of load. Within the first preference that has a match, it picks the *best* track by container disposition: full subtitles rank over SDH (`HEARING_IMPAIRED`), forced, and commentary (`COMMENT`), and text over bitmap. No match leaves subtitles off. It drives the host-overlay path, so unlike audio it needs no reload regardless; it only spares a host from language-matching `subtitleTracks` itself. The native menu (below) keeps its own host-driven default selection via `setNativeSubtitleSelected(track:)`.
79
+
80
+
Matching is case-insensitive across ISO 639-1, 639-2/B, 639-2/T, and English names (`en` == `eng` == `english`); preference order dominates, so an earlier preference on a later track still wins. The resolved tracks are published on `player.activeAudioTrackIndex` / `player.activeSubtitleTrackIndex` (both match `TrackInfo.id`), and every `TrackInfo` carries `isDefault` / `isForced` / `isHearingImpaired` / `isCommentary` (from container dispositions) so a host can rank or filter the track lists the same way.
81
+
73
82
### Second simultaneous subtitle track (bilingual)
74
83
75
84
A second subtitle channel can run alongside the primary for bilingual playback / language learning: `selectSecondarySubtitleTrack(index:)` for an embedded track and `selectSecondarySidecarSubtitle(url:httpHeaders:)` for a sidecar file, mirroring the primary API. Its cues land in a separate `@Published secondarySubtitleCues` list (so the host can render the two channels independently, e.g. top vs bottom), with `isSecondarySubtitleActive` and `isLoadingSecondarySubtitles` for UI state; `clearSecondarySubtitle()` tears it down. The secondary channel decodes through the same demux loop and PTS rules as the primary.
0 commit comments