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
docs: correct DV routing, CLI flags, and source map against 4.6.3 (#84)
Audited README + docs/{architecture,formats,cli}.md against the actual
sources at HEAD (4.6.3) and fixed the drift found:
formats.md (Dolby Vision signaling was the biggest miss):
- DV routing table no longer collapses P5/P8.1/P8.4 under "dvh1/dvhe".
P5 emits a bare dvh1 primary; P8.1/P8.4/P7 emit hvc1 primary + dvvC
with dvh1.08/db1p or /db4h in SUPPLEMENTAL-CODECS on DV panels, plain
HDR10/HLG base elsewhere. dvhe is never emitted.
- AV1+DV corrected: P10.0/P10.1 are bare dav1 with no supplemental,
only P10.4 carries the dav1/db4h supplemental.
- Documented the SDR-compatible-base profiles (HEVC P8.2, AV1 P10.2)
that strip the DV config and play the Rec.709 base on every panel.
cli.md:
- "Fifteen subcommands" was off by one (sixteen).
- hlslive --segments is a required comma-separated list of .ts paths,
not a numeric sizing flag; clarified --seconds/--segment-seconds/--disc.
- serve --native-subs: the index value is legacy/ignored; the flag drives
requestNativeSubtitleTrack + attachAllNativeSubtitleStores, not
LoadOptions.prepareNativeSubtitles.
- Added the undocumented flags: live --serve-only/--rewind-test/
--gen-highbitrate-seed and customio --audio-only.
architecture.md source map:
- Added the omitted files: DiscRecognitionCache, HTTPDiscIOReader,
MovTextSampleBuilder, NativeSubtitleCueStore, DoviRpuConverter+Probe,
Issue65LivelockBreakers.
- Added the AetherEngineSMB target subtree and a pointer to cli.md for
the aetherctl target.
README.md:
- Audio bridge row now lists DTS-HD MA.
- Documented LoadOptions.nativeRemoteHLS for AVPlayer-native remote HLS.
Claude-Session: https://claude.ai/code/session_01XZTEfmztPE8hAdjHdBr9BH
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| Audio | AAC, AC3, EAC3, FLAC, ALAC stream-copy lossless; TrueHD / DTS / MP3 / Opus bridge to EAC3 5.1 (default) or lossless FLAC |
45
+
| Audio | AAC, AC3, EAC3, FLAC, ALAC stream-copy lossless; TrueHD / DTS / DTS-HD MA / MP3 / Opus bridge to EAC3 5.1 (default) or lossless FLAC |
46
46
| Dolby Atmos | EAC3+JOC stream-copied on every route (HDMI MAT 2.0, AirPods spatial, BT downmix) |
47
47
| Surround | 5.1 / 7.1 with correct `AudioChannelLayout`|
48
48
| Audio-only |`LoadOptions.audioOnly`: lean pipeline, no video machinery, system Now-Playing on tvOS / iOS |
@@ -219,6 +219,8 @@ try await player.load(
219
219
220
220
Direct ingest covers MPEG-TS with demuxed-audio and packed-audio renditions, in-line AES-128 clear-key decryption, and SSAI ad-pod direct play (versioned init segments, audio re-anchoring, no-cut watchdog). Unsupported encryption / fMP4 playlists surface a typed `HLSIngestError` so the host can fall back. Details in [docs/formats.md › Live ingest](docs/formats.md#live-ingest-aes-128-ssai).
221
221
222
+
For an upstream AVPlayer can play natively (a standard remote `master.m3u8`, e.g. a Jellyfin live channel), `LoadOptions.nativeRemoteHLS` skips the demuxer probe and the loopback server entirely and hands the URL straight to AVPlayer, which manages the live edge and reconnect itself. Pair it with `isLive: true`.
│ ├── BDTitleSelector.swift Enumerates Blu-ray playlists as selectable titles (longest first; short menu / decoy playlists filtered)
134
+
│ ├── DiscRecognitionCache.swift Memoises `DiscReader.wrap` per URL + title index so disc recognition does not re-run on every subtitle / track switch (load-bearing for remote-ISO track switches, #76)
134
135
│ └── DiscInspector.swift Diagnostic mirror of `DiscReader.wrap` for `aetherctl disc-inspect` (titles, chapters, recognition stages)
└── AetherPlayerView.swift Polymorphic surface: hosts either AVPlayerLayer (native) or AVSampleBufferDisplayLayer (SW)
188
194
```
189
195
196
+
The `AetherEngineSMB` product is a separate, opt-in target so its AMSMB2 (LGPL-2.1) dependency never enters the core engine binary. Hosts that need LAN-share playback link it and hand the engine an `smb://` source via the standard `IOReader` seam:
197
+
198
+
```
199
+
Sources/AetherEngineSMB/
200
+
├── AetherEngineSMB.swift Product entry point: opt-in SMB2/3 byte source, depends on AMSMB2 (libsmb2); never linked by the core engine
201
+
├── SMBURL.swift Parses smb://[user[:password]@]host[:port]/share/path URLs (missing credentials default to guest)
202
+
├── SMBConnection.swift Read-only SMB2/3 byte source over one share + path via AMSMB2 (per-read open/seek/close, no persistent handle)
203
+
├── ByteRangeSource.swift Random-access read-only byte-source protocol, isolates the network backend from cursor / seek logic for testability
204
+
└── SMBIOReader.swift Bridges a ByteRangeSource into the engine's IOReader (blocking read via a happens-before semaphore edge)
205
+
```
206
+
207
+
The `aetherctl` CLI target (`Sources/aetherctl/`) is documented separately in [docs/cli.md](cli.md).
Copy file name to clipboardExpand all lines: docs/cli.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,7 @@ swift run aetherctl smbtest <smb-url> # play a file off an SMB2/3 share via t
21
21
swift run aetherctl <url># alias for serve (backwards compat)
22
22
```
23
23
24
-
Fifteen subcommands plus the bare-URL `serve` alias.
24
+
Sixteen subcommands plus the bare-URL `serve` alias.
25
25
26
26
## probe
27
27
@@ -42,7 +42,7 @@ open 'http://127.0.0.1:<port>/master.m3u8' # macOS QuickTime
42
42
43
43
`--no-dv` forces the SDR / HDR10 route even for a Dolby Vision source (compare the two playlists).
44
44
45
-
`--native-subs <index>`enables `LoadOptions.prepareNativeSubtitles`, which causes the engine to mux ALL text subtitle tracks as separate language-tagged `mov_text` (tx3g) traks in the init segment (all `disposition:default=0`, none auto-displayed). The `<index>`argument (zero-based among the subtitle tracks reported by `probe`) is passed to `setNativeSubtitleSelected(track:)` after the session starts, so the diagnostic selects that track via the host API. The init segment will carry one `mov_text` trak per text subtitle stream; inspect with `mp4dump` or open the playlist in QuickTime to verify the legible `AVMediaSelection` group enumerates every language. Omit the flag to reproduce the default behavior (no native subtitle traks, muxer output byte-identical to before).
45
+
`--native-subs <index>`turns on the native mov_text subtitle path: the engine calls `requestNativeSubtitleTrack()` before `start()` (so the init `moov` declares the tracks), then `attachAllNativeSubtitleStores()` after start. ALL text subtitle tracks are muxed as separate language-tagged `mov_text` (tx3g) traks (all `disposition:default=0`, none auto-displayed). The `<index>`value is legacy and now ignored, kept only for CLI compatibility: every non-bitmap track is always declared, and actual track selection happens via the host API in a full session, not from this flag. Inspect the init segment with `mp4dump` or open the playlist in QuickTime to verify the legible `AVMediaSelection` group enumerates every language. Omit the flag to reproduce the default behavior (no native subtitle traks, muxer output byte-identical to before).
46
46
47
47
## validate
48
48
@@ -84,7 +84,7 @@ Plays a source through the audio-only pipeline (default ten seconds, `--seconds
84
84
85
85
## customio
86
86
87
-
Wraps a local file in a custom `IOReader` and plays it through `load(source:)`. `--memory` reads via `DataIOReader`, `--forward-only` drops the seek capability, and `--reload` / `--switch-audio` / `--select-subs` / `--extract` exercise the optional capabilities (background reload, audio-track switch, embedded subtitles, scrub preview) end-to-end.
87
+
Wraps a local file in a custom `IOReader` and plays it through `load(source:)`. `--memory` reads via `DataIOReader`, `--forward-only` drops the seek capability, `--audio-only` routes through the audio-only pipeline, and `--reload` / `--switch-audio` / `--select-subs` / `--extract` exercise the optional capabilities (background reload, audio-track switch, embedded subtitles, scrub preview) end-to-end.
88
88
89
89
## disc-inspect
90
90
@@ -96,7 +96,7 @@ Activates two subtitle tracks simultaneously on one source (primary + secondary)
96
96
97
97
## live
98
98
99
-
Runs a live MPEG-TS session against a built-in fixture that serves an endless broadcast by looping a seed `.ts` with rewritten timestamps. Flags simulate the failure modes the live path hardens against: `--drop-after N` (mid-stream connection drop + reconnect), `--discontinuity-at N` (program-boundary PTS / PCR jump), `--realtime` (1x wall-clock pacing), `--dvr-window N` (timeshift), `--measure-rss` (sliding-window retention), `--reload-test` (live rejoin end to end, including the full-backlog replay shape some origins serve on reconnect). `--seed <ts>` overrides the seed clip, `--sw` forces the software live path, `--report-cache-bytes` tracks on-disk DVR footprint.
99
+
Runs a live MPEG-TS session against a built-in fixture that serves an endless broadcast by looping a seed `.ts` with rewritten timestamps. Flags simulate the failure modes the live path hardens against: `--drop-after N` (mid-stream connection drop + reconnect), `--discontinuity-at N` (program-boundary PTS / PCR jump), `--realtime` (1x wall-clock pacing), `--dvr-window N` (timeshift), `--measure-rss` (sliding-window retention), `--reload-test` (live rejoin end to end, including the full-backlog replay shape some origins serve on reconnect). `--seed <ts>` overrides the seed clip, `--sw` forces the software live path, `--report-cache-bytes` tracks on-disk DVR footprint, `--serve-only` parks the fixture without attaching an engine (raw `curl` / `ffprobe` inspection), `--rewind-test` runs the DVR rewind-and-return matrix variant, and `--gen-highbitrate-seed` generates a ~22 Mbps 1080p H.264 MPEG-TS seed (for RSS-retention measurement) then exits.
100
100
101
101
## dvr
102
102
@@ -112,7 +112,7 @@ Drives a real AVPlayer (native loopback-HLS path) through a burst of rapid seeks
112
112
113
113
## hlslive
114
114
115
-
Replays a synthetic SSAI ad-pod feed through the live-direct-play path to repro the FAST-channel ad-break handling (program-switch detection, muxer rotation with versioned `#EXT-X-MAP`, audio re-anchor, no-cut watchdog). `--segments N`, `--seconds N`, `--segment-seconds N` size the run; `--disc` injects discontinuities at the ad boundaries.
115
+
Replays a synthetic SSAI ad-pod feed through the live-direct-play path to repro the FAST-channel ad-break handling (program-switch detection, muxer rotation with versioned `#EXT-X-MAP`, audio re-anchor, no-cut watchdog). `--segments a.ts,b.ts,c.ts` is required: a comma-separated list of real `.ts` segment files served in order (content / ad / content) without timestamp rewriting. `--seconds N` (default 40) and `--segment-seconds N`(default 5) size the run; `--disc i,j` marks which segment indices carry a leading `#EXT-X-DISCONTINUITY` (default: auto-detected on every file change).
Copy file name to clipboardExpand all lines: docs/formats.md
+10-2Lines changed: 10 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,18 +23,26 @@ Interlaced sources (DVD-rip MPEG-2, SD broadcast) are deinterlaced through a per
23
23
| H.264, HEVC (SDR) | BT.709 |
24
24
| HEVC Main10 (HDR10) | BT.2020 / PQ |
25
25
| HEVC Main10 (HDR10+) | BT.2020 / PQ + per-frame ST 2094-40 SEI stream-copied |
26
-
| HEVC Main10 (DV P5 / P8.1 / P8.4) |`dvh1` / `dvhe` track type with the source's `dvcC` box preserved |
26
+
| HEVC Main10 (DV P5) |`dvh1` track type (DV-only, IPT-PQ base; forced even on SDR panels) |
27
+
| HEVC Main10 (DV P8.1 / P8.4 / P7) |`hvc1` primary + `dvvC` box, DV engaged via `SUPPLEMENTAL-CODECS` on DV panels, plain HDR10 / HLG base elsewhere |
27
28
| HEVC Main10 (HLG) | BT.2020 / HLG |
28
29
| AV1 HDR | BT.2020 / PQ |
29
30
30
31
HDR-to-SDR mapping is handled by AVPlayer and the system compositor according to the connected display. AetherEngine doesn't tonemap on the host; it tells the system "this is BT.2020 PQ" (or DV, or HLG) via the HLS-fMP4 sample description and lets tvOS / iOS pick the right path. `DisplayCriteriaController` issues the HDMI content-frame-rate and dynamic-range hint via `AVDisplayManager` before the first segment is fetched, so the receiver-side handshake is in flight by the time `AVPlayer` is ready to render. (For why this ordering is mandatory on tvOS, see the README's "Host setup on tvOS" section.)
31
32
32
33
### Dolby Vision signaling
33
34
34
-
For DV streams the demuxer surfaces the source's `AVDOVIDecoderConfigurationRecord`. On DV-capable displays, `HLSVideoEngine` writes the matching ISO BMFF `dvcC` box into the HLS-fMP4 sample description and emits a bare `dvh1.<profile>.<dvLevel>` codec tag for Profile 5, 8.1, and 8.4 so AVKit's auto-criteria reads `dvh1` from the sample entries and engages DV mode directly. On non-DV displays the engine downgrades to plain `hvc1`: Profile 5 is unplayable there (no HDR10 base), and Profiles 8.1 / 8.4 fall back to their HDR10 / HLG base layer with AVPlayer's tone-mapping path. AV1+DV (Profile 10.0 / 10.1 / 10.4) uses the parallel `dav1` / `av01` track type plus `dvvC` box on hardware-AV1 hosts.
35
+
For DV streams the demuxer surfaces the source's `AVDOVIDecoderConfigurationRecord`, and the route depends on the profile's base-layer compatibility:
36
+
37
+
-**Profile 5** (DV-only, IPT-PQ, no base layer) emits a bare `dvh1.05.<dvLevel>` codec tag in the primary `CODECS` attribute with the `dvcC` box preserved. `dvh1` is forced even on non-DV panels (AVPlayer's system DV decoder tonemaps IPT-PQ internally; without `dvh1` the IPT chroma reads as YCbCr and shows a green / purple cast), so P5-on-non-DV is routed through a media playlist to dodge the `-11868` variant rejection.
38
+
-**Profiles 8.1 / 8.4** (HDR10- / HLG-compatible base) emit `hvc1.2.4.L<level>` as the primary `CODECS` tag. On a DV-capable display the muxer writes the `dvvC` box and the variant carries `dvh1.08.<dvLevel>/db1p` (8.1) or `/db4h` (8.4) in `SUPPLEMENTAL-CODECS`, which is what makes AVKit engage DV. On a non-DV display the `dvvC` is stripped (a lone `hvc1` + `dvvC` still trips `-11868`) and the stream plays as its plain HDR10 / HLG base with AVPlayer's tone-mapping.
39
+
40
+
AV1+DV emits a bare `dav1.10.<dvLevel>` primary for Profile 10.0 (DV-only) and Profile 10.1 (HDR10-compat base) with no supplemental entry, and an `av01...` primary plus `dav1.10.<dvLevel>/db4h` in `SUPPLEMENTAL-CODECS` for Profile 10.4 (HLG-compat base), on hardware-AV1 hosts.
35
41
36
42
**Profile 7** (dual-layer, the common UHD-Blu-ray remux profile) has no decoder on any Apple platform, so the engine converts it to single-layer **Profile 8.1** live during muxing: the RPU of each video packet is rewritten with [libdovi](https://github.com/superuser404notfound/LibDovi) (`dovi_convert_rpu_with_mode`, mode 2, the same transform as `dovi_tool -m 2`), the enhancement-layer NALs are dropped, and the container `dvvC` is set to Profile 8.1. On a DV-capable display this means real Dolby Vision (`dvh1.08/db1p` supplemental) instead of the plain HDR10 base; on a non-DV display Profile 7 still falls back to its HDR10 base, unchanged. The conversion is loss-free relative to what Apple could show before (the enhancement layer was never decodable on Apple hardware), and any per-packet conversion failure falls back to the HDR10 strip. MEL and FEL sources are both handled.
37
43
44
+
**SDR-compatible-base profiles** (HEVC **Profile 8.2**, AV1 **Profile 10.2**) carry a Rec.709 base layer that no Apple platform has a DV decoder for. The engine strips the `dvcC` / DV config and plays the base as plain `hvc1` / `av01` (logging `not DV-routable, playing Rec.709 base`); there is no Dolby Vision on any display for these, on DV panels and SDR panels alike.
45
+
38
46
### HDR10+ dynamic metadata
39
47
40
48
ST 2094-40 metadata stays attached to the HEVC bitstream as user-data-registered ITU-T T.35 SEI NALs. The HLS-fMP4 stream-copy preserves the SEI through to `AVPlayer`, which forwards it to the system compositor. HDR10+-capable TVs apply the per-scene tone-mapping curves; HDR10-only TVs fall back to the static HDR10 base.
0 commit comments