AetherEngine 1.1.0
AetherEngine 1.1.0
Bug-fix release on top of 1.0.0. Three days of public-beta feedback in Sodalite drove a producer-side A/V sync overhaul, HDR / Dolby Vision routing fixes, a dynamic subtitle-clock model, plus a handful of new public API hooks. Tag retargeted on 2026-05-16 to include the tvOS/iOS packaging fix (#5).
Playback pipeline (producer + muxer)
A/V sync after restart and from frame one. The producer's video gate is now unconditional on AV_PKT_FLAG_KEY, initial-start as well as restart sessions. The audio gate always waits for the video gate, so both streams' first kept sample comes from the same source-time. Previously, MKV remuxes whose first decode-order packet wasn't a sync sample (some Bluey BD remuxes) either stalled AVPlayer with -12860 or played the first few seconds with audio anchored at the file start and video at a later IDR.
Matroska seek imprecision tolerated end-to-end. Producer restarts scan forward from the matroska seek result to the next true IDR, then apply a per-stream dynamic PTS shift so the fragment tfdt lands at the playlist's cumulative-EXTINF origin. NOPTS dts repair (lastValidDts + 1) keeps B-frame-heavy MKVs from stalling the muxer. Per-stream PTS shift now uses each stream's own start_time rather than the format-level value, since broadcast remuxes can ship different per-stream offsets.
HEVC open-GOP CRA support. Producers now drop pre-keyframe leading B-frames (HEVC RASL) when their display-order pts lands before the CRA that opened the segment stream. Without the drop, AVPlayer's HEVC decoder fails on the first display sample and stalls in waitingToPlay forever (repro: Bombige Magenverstimmung, open-GOP HEVC with firstKeyframePts=88 and two leading B-frames at pts=88 and pts=131).
Per-frame fallback duration. When the source MKV doesn't ship DefaultDuration (HandBrake / web-rip pipelines often drop it), the producer backfills pkt->duration per stream via a look-behind. Stops the mp4 sub-muxer from writing trun.last.duration = 0, which broke seekability on some downstream consumers.
HDR / Dolby Vision routing
Match Content master-toggle aware HDR routing. HDR HEVC on a non-DV display now routes through the master playlist when the user's Match Content master toggle is on, and through the media playlist when it's off. On panels with the toggle off, the master's VIDEO-RANGE=PQ was failing AVPlayer with Cannot Open (AVFoundationErrorDomain -11848) since the panel is SDR-locked.
Dolby Vision P8.1 / P8.4 cross-compat tags. P8.1 and P8.4 now emit bare dvh1.<profile>.<dvLevel> on DV-capable displays for the direct DV engagement, and fall back to hvc1.2.4.LXX with SUPPLEMENTAL dvh1.08.LL/db4h for cross-player compatibility on P8.4's HLG-HEVC base layer.
SDR rate-only display criteria. DisplayCriteriaController.apply() used to early-return for .sdr sources, which also skipped the preferredDisplayCriteria assignment. SDR sessions now program a rate-only criteria so Match Frame Rate can engage independently of Match Dynamic Range. tvOS internally honours whichever Match Content sub-toggle the user has enabled.
HDR10+ runtime detection. videoFormat flips from .hdr10 to .hdr10Plus on first T.35 SEI detection in a packet's payload. Debounced once per session so producer restarts on scrub don't re-fire.
Effective format clamping. Published videoFormat is clamped to panel capabilities. A DV asset on a non-DV TV is reported as .hdr10 (the panel's actual experience) rather than the source's claimed format.
Subtitles
Cue timestamps mapped through the active producer's shift. Subtitle cues come from an independent side-demuxer in raw source PTS, but AVPlayer's HLS clock sits at source_pts - producer.videoShiftPts, and the shift varies per producer session (matroska seek imprecision means the shift can be ~4 s on restart sessions for the same source). The producer now reports its shift via onVideoShiftKnown; the engine forwards it through HLSVideoEngine.onPlaylistShiftChanged and publishes a derived sourceTime (= currentTime + shift). Hosts read sourceTime for cue lookup and side-demuxer seeking, while currentTime stays the AVPlayer-clock for transport / scrub / resume.
Public API additions
AetherEngine.currentAVPlayer(@Published AVPlayer?): exposes the active AVPlayer for MPNowPlayingSession hosting. Re-emitted on every reload so hosts that rebindMPNowPlayingSession.playerstay current.setExternalMetadata(_:): pushAVMetadataItemcollection into the engine for the next native load. Stashed beforeload()and replayed onto the freshly-createdNativeAVPlayerHostwhen the item is created, so hosts can drive the tvOS info panel without reaching into AVPlayer.LoadOptions.httpHeaders: custom headers (auth tokens, User-Agent overrides) attached to every demuxer + segment fetch, replayed across producer restarts and side-demuxer sessions.LoadOptions.keepDvh1TagWithoutDV: experimental, keep thedvh1codec tag on non-DV displays. Off by default; tooling lever for AVKit auto-criteria behaviour.LoadOptions.matchContentEnabled: mirror the host's tvOS Match Content master toggle so the engine can gate HDR HEVC master-playlist routing accordingly.engine.reloadAtCurrentPosition()preserves the originalLoadOptions(httpHeaders + matchContentEnabled + keepDvh1TagWithoutDV) across the rebuild. Same for the internal audio-switch reload path.
Packaging
aetherctl is no longer exposed as an SPM product (#5). The target uses Foundation.Process, which is unavailable on tvOS / iOS, so exposing it forced SPM consumers on those platforms to compile it and fail. The aetherctl target itself stays, so swift build on macOS still produces the CLI for upstream development.
aetherctl
aetherctl reorganised under probe / serve / validate subcommands. serve exposes the engine's loopback HLS server for AVPlayer-side debugging on macOS. A fixtures.sh script populates a small media-zoo for repro testing.
Internals
- HDR badge consolidated into the
videoFormatsubscription path, single source of truth. - EngineLog double-emit fixed (handler and stdout are now mutually exclusive so Xcode console no longer renders every line twice).
- Network: BSD-sockets HLSLocalServer refactor attempted and reverted; NWConnection-based server remains the shipping implementation.
- Cleanup: dead VP9 capability probe removed; dead symbols dropped; stale docstrings refreshed.
Engine pin
For Sodalite hosts: bump Package.resolved to 424b88e (or use the 1.1.0 tag).