PrismCore / Concepts
How it works
The remux pipeline from source URL to AVPlayer, the two production modes, and the design decisions that keep Atmos and Dolby Vision intact.
The pipeline
┌─► video ──► fMP4 segments + index.m3u8
Source URL ──► libavformat demux ───┼─► audio 0 ─► audio0/{init.mp4, seg*.m4s, index.m3u8}
├─► audio N ─► audioN/…
└─► subs N ─► subsN/seg*.vtt
master.m3u8
│
▼
LoopbackHTTPServer (127.0.0.1) ──► AVPlayer- Demux, don't decode. libavformat opens the container and separates its streams. Video and copyable audio are never decoded.
- Segment ourselves. The FFmpeg build PrismCore uses has no
hlsmuxer, soFMP4SegmentWriterdrives themp4muxer with custom fragmentation and cuts where PrismCore says — at the first video keyframe at least 6 s after the segment's start (2 s for the first segment) — andMediaPlaylistWriterturns the fragments into playlists. - Serve locally. A loopback HTTP server hands the master playlist, the media playlists, init segments,
.m4sfragments and.vttcues toAVPlayer. Payloads come from a segment provider rather than straight off disk, so a fetch can wait for, or trigger, production. - Apple does the rest. Hardware decode, Atmos passthrough, the Dolby Vision handshake, track menus, PiP and AirPlay are
AVPlayer's, because from its point of view it is playing ordinary HLS.
Everything is produced into a per-session work directory under the system temporary directory and deleted by stop().
Planned and sequential production
Planned (demand-driven) is the default for seekable on-demand sources. SegmentPlan maps every segment upfront from the container's keyframe index, and complete EXT-X-PLAYLIST-TYPE:VOD playlists are published before the first packet is read. A fetch outside the producer's window re-anchors it: av_seek_frame to the anchor keyframe and fresh muxers with absolute tfdt, so the produced segment sits exactly where the playlist promised. After end of file the producer parks and keeps answering demand for segments a re-anchor skipped.
Sequential is an EVENT playlist growing from head to end of file (or, opted in, a sliding window). It is used when no trustworthy plan exists: a live source, an unknown duration, an index that is missing, untrustworthy, or did not load in time.
Seeking and segment plans covers both in detail.
The software path
For video AVPlayer cannot decode at all, SoftwarePlaybackPipeline decodes with libavcodec — zero-copy into CVPixelBuffers where VideoToolbox has a hardware decoder — and renders into an AVSampleBufferDisplayLayer and an AVSampleBufferAudioRenderer under one synchronizer. It owns its clock, so the host gets a layer and transport calls (play, pause, seek(to:), track selection) instead of a URL. PiP, AirPlay and the system player UI are not available on this path.
Design notes
The decisions that were expensive to learn.
Atmos needs a box FFmpeg drops
The CHANNELS="16/JOC" playlist attribute is necessary, but it is not what engages Atmos. AVFoundation takes the Dolby route only when the dec3 box in the init segment carries the TS 103 420 type-A extension, and FFmpeg's mp4 muxer drops that extension on a stream copy. EAC3Syncframe reads complexity_index_type_a from the bitstream's addbsi, and EAC3Configuration appends it to the produced init segment, re-framing every enclosing box's size. The field walk follows libavcodec's ac3_parser.c: the JOC signal can sit in a dependent substream (Blu-ray-style DD+ puts it behind an AC-3 core frame), and a walk that is off by a bit does not fail — it returns a confident wrong number.
hvc1 is a promise the record has to keep
An hvc1 sample entry asserts that every parameter set lives in the decoder configuration record, and Matroska CodecPrivate routinely says otherwise. HVCCNormalizer rewrites the record — once on the input, and again on the produced init segment, because the muxer rebuilds the record and clears the completeness flag while still naming the entry hvc1.
Dolby Vision is three claims that must agree
The playlist's CODECS, the sample entry's fourcc and the dvcC/dvvC box all describe the same track, and AVPlayer checks them against each other. Profile 5 gets dvh1 — its picture is IPT-PQ-c2, not YCbCr, and an hvc1 entry over Profile 5 renders green and purple. Profile 8 keeps hvc1, because its base layer really is plain HEVC and the dvvC box is what upgrades it; claiming dvh1 there would deny the fallback that makes 8.1 worth having.
WebVTT timing states a fact, not a convention
HLS ties cue times to the media timeline with X-TIMESTAMP-MAP. PrismCore's fMP4 segments carry the source's own timestamps, so the media timeline starts at the first video PTS — there is no MPEG-TS 10 s offset to honour. Cue times are written relative to the presentation origin, and every segment repeats MPEGTS:round(origin × 90000),LOCAL:00:00:00.000.
The display handshake comes first on tvOS
tvOS validates an HDR variant's VIDEO-RANGE against the panel's current mode before it will switch modes, so AVKit's automatic display criteria cannot help: they are derived from a format description that only exists after the variant passes the very check the switch must precede. DisplayCriteriaController programs and settles the panel before the item is loaded — the quick start shows the order.
Stability
PrismCore follows Semantic Versioning. Every public declaration in Sources/PrismCore/ — everything in the API reference — is the stability contract; internal and package symbols are not. Since 1.0.0 breaking changes bump the major version, features the minor and fixes the patch. Every release is in the changelog.