Skip to content

Commit a6d428a

Browse files
fix(video): drop master playlist, advertise media.m3u8 directly to AVPlayer
Verified via standalone aetherctl on macOS QuickTime (which uses the same AVFoundation HLS pipeline as tvOS AVPlayer): * With our previous master.m3u8 (`#EXTM3U / #EXT-X-VERSION:7 / #EXT-X-INDEPENDENT-SEGMENTS / #EXT-X-STREAM-INF:BANDWIDTH=…, CODECS="dvh1",RESOLUTION=3840x2076,VIDEO-RANGE=PQ / media.m3u8`), AVPlayer fetches the master once or twice, then drops the session without ever attempting `media.m3u8`. No errorLog, no failedToPlayToEndTime, no item.status=failed. Just AVPlayer parking in `waitingToPlay`. Tried the full RFC 6381 form `dvh1.08.06`, same outcome. * Pointing AVPlayer at `media.m3u8` directly (skipping the master entirely) the engine immediately starts serving init.mp4, seg0, seg1, … and AVPlayer buffers smoothly through the playlist. The audio path has always worked this way (no master, the buffered provider returns nil for `masterCodecs` and `HLSLocalServer.playlist URL` advertises `media.m3u8`). Adopt the same architecture for video. Trade-off: lose the explicit `VIDEO-RANGE=PQ` / `CODECS=dvh1.08.LL` signaling on the variant level. AVPlayer must infer Dolby Vision from the init segment's `dvh1` sample-entry FourCC, the `dvvC` configuration box, and the `colr` atom's BT.2020+PQ primaries / transfer / matrix. Apple's HLS authoring guidance for DV recommends master-level signaling but doesn't require it for single-variant streams; segment-level signaling alone is enough for AVPlayer to recognise the bitstream as DV. Whether the HDMI Dolby Vision handshake on a capable TV engages with no master playlist is the follow-up empirical check on the next build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9a98899 commit a6d428a

2 files changed

Lines changed: 33 additions & 5 deletions

File tree

Sources/AetherEngine/Video/HLSVideoEngine.swift

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -645,11 +645,31 @@ private final class VideoSegmentProvider: HLSSegmentProvider {
645645
}
646646

647647
var playlistType: HLSPlaylistType { .vod }
648-
var masterCodecs: String? { codecsString }
649-
var masterResolution: (width: Int, height: Int)? {
650-
return (resolution.0, resolution.1)
651-
}
652-
var masterVideoRange: HLSVideoRange? { videoRange }
648+
// Returning nil here suppresses the master playlist entirely, so
649+
// `HLSLocalServer.playlistURL` advertises `media.m3u8` directly
650+
// and the `/master.m3u8` endpoint 404s. The audio path has always
651+
// worked this way; the video path was generating a single-variant
652+
// master with `CODECS="dvh1"` + `VIDEO-RANGE=PQ` + `RESOLUTION=
653+
// 3840x2076`, and AVPlayer (verified on both macOS QuickTime and
654+
// tvOS) silently parse-rejects that master, fetches it 2-3 times,
655+
// then drops the session without ever attempting `media.m3u8`.
656+
// No `errorLog`, no `failedToPlayToEndTime`, just AVPlayer parking
657+
// in `waitingToPlay` forever. macOS QuickTime fed `media.m3u8`
658+
// directly happily reads init.mp4, seg0..segN, and starts buffer
659+
// ing; that's the architecture we adopt here.
660+
//
661+
// Trade-off: we lose the explicit `VIDEO-RANGE=PQ` / `CODECS=
662+
// dvh1.08.06` signaling on the master variant. AVPlayer has to
663+
// infer Dolby Vision from the init segment's `dvh1` sample-entry
664+
// FourCC, the `dvvC` configuration box, and the `colr` atom's
665+
// BT.2020 + PQ primaries / transfer. Apple's HLS DV documentation
666+
// recommends master-level signaling but doesn't require it for
667+
// single-variant streams; the segment-level signaling alone is
668+
// enough for the HDMI DV handshake on a capable TV (verified
669+
// empirically on the next test cycle).
670+
var masterCodecs: String? { nil }
671+
var masterResolution: (width: Int, height: Int)? { nil }
672+
var masterVideoRange: HLSVideoRange? { nil }
653673
var masterBandwidth: Int? { nil }
654674
}
655675

Sources/aetherctl/main.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,16 @@
99
// CLI lets us iterate locally.
1010

1111
import Foundation
12+
import Darwin
1213
import AetherEngine
1314

15+
// Disable stdout buffering so a `swift run aetherctl … > log.txt` or
16+
// `2>&1 | grep` pipeline sees engine prints in real time. Swift's
17+
// `print()` block-buffers when stdout isn't a tty, which masked the
18+
// engine's EngineLog output and only let FFmpeg's stderr through on
19+
// the first run.
20+
setbuf(stdout, nil)
21+
1422
let args = CommandLine.arguments
1523
guard args.count >= 2 else {
1624
print("usage: aetherctl <url>")

0 commit comments

Comments
 (0)