Skip to content

AetherEngine 1.4.4

Choose a tag to compare

@superuser404notfound superuser404notfound released this 26 May 09:08
· 428 commits to main since this release

Highlight

Fixes HDR / DV playback failure on tvOS 26.5 (AVFoundationErrorDomain -11868 / AVErrorNoCompatibleAlternatesForExternalDisplay). SDR playback was unaffected throughout.

Root cause is HDR-variant-specific: tvOS 26.5 enforces Apple Tech Talk 503's documented ordering ("perform this switch BEFORE assigning the AVPlayerItem to the AVPlayer object") synchronously at HLS variant validation, but only for variants whose VIDEO-RANGE requires a panel mode switch (HDR10 / HLG / DV). SDR variants are unaffected since SDR is a universally supported panel mode and no switch is needed. Earlier tvOS versions deferred this check even for HDR variants.

The symptom signature for HDR sources was distinctive: master + media playlists fetched OK, item.status goes .failed immediately, errorLog().events.count == 0, accessLog().events.count == 0, item.tracks.count == 0, item.duration indefinite, no GET /init.mp4 ever reaches the loopback server.

AVKit-auto (appliesPreferredDisplayCriteriaAutomatically = true) cannot satisfy the criteria-first contract for HLS multivariant HDR sources. AVKit reads criteria from AVAsset.preferredDisplayCriteria, which is synthesized from the chosen variant's CMVideoFormatDescription. For HLS that format description only exists after init.mp4 is parsed, and init.mp4 is only fetched after the variant passes the validator. Chicken-and-egg, no way around it via auto-criteria.

Engine-driven sole-writer is the only working pattern. Hosts should pass LoadOptions(suppressDisplayCriteria: false) and set appliesPreferredDisplayCriteriaAutomatically = false on their AVPlayerViewController. Sodalite reverted to that host config in 1b6bbae1.

Fixes

Cache-Control: no-store → no-cache reverted (prophylactic, not proven necessary)

caf33ee (1.4.x) flipped the loopback HLS server's response Cache-Control from no-cache to no-store as a speculative AVPlayer URLCache leak mitigation. The revert in this release is prophylactic: single-variable testing of no-store under engine-driven display criteria wasn't done, so whether no-store was contributing to the failure is unproven. SDR variants kept working with no-store throughout the broken window, which suggests it's not a general HLS killer. The actual memory leak in that timeframe was on the source-fetcher (URLSession task pool, fixed separately via per-request session); the loopback-response no-store was layered on top without measurement that it helped.

If a later DV 8.1 long-session memory-leak test shows URLCache footprint matters, re-trying no-store under engine-driven mode is the right next experiment before going to bigger guns (custom URLProtocol interception or AVAssetResourceLoaderDelegate with a custom URL scheme).

DV 8.1 signaling: hvc1 sample entry + SUPPLEMENTAL-CODECS dvh1.08.XX/db1p on DV panels

The prior P8.1 emission branched on panel state: bare dvh1 sample entry + dvh1.08.XX CODECS on DV panels, hvc1 on non-DV panels, both without SUPPLEMENTAL-CODECS. Per Apple's HLS Authoring Spec appendix on SUPPLEMENTAL-CODECS (post-WWDC22), backward-compatible DV ships with hvc1 sample entry + hvcC + dvvC boxes, primary CODECS hvc1.2.4.LXX, and SUPPLEMENTAL-CODECS dvh1.08.XX/db1p. The /db1p brand identifier marks the supplemental as DV with HDR10 base for AVPlayer's profile-matching; without it the variant is treated as plain HDR10 and the DV pipeline never engages. Mirrors the working P8.4 pattern.

DV 8.1 / 8.4 on non-DV panels: strip DV side data

For backward-compatible DV routed to HDR10-only panels: strip AV_PKT_DATA_DOVI_CONF from the codecpar before mp4 muxer init so init.mp4 has a clean hvc1 + hvcC sample entry with no dvvC box. Mirrors P7's existing strategy. Removes a class of "init.mp4 sample entry confuses tvOS 26 codec filter" risk on non-DV displays.

DisplayCriteriaController.reset() gated on whether apply() actually wrote

reset() now tracks whether apply() wrote preferredDisplayCriteria during the session and no-ops otherwise. AVKit-sole-writer hosts that pass LoadOptions.suppressDisplayCriteria = true now get ZERO engine writes against avDisplayManager for the entire session lifecycle. The prior unconditional nil-write on every stopInternal() (audio-track-switch reloads, source switch, etc.) raced AVKit's in-flight criteria negotiation under that host mode.

HDCP-LEVEL=TYPE-1 no longer emitted from master playlist

Apple Tech Talk 501 recommends TYPE-1 for 4K HDR / DV variants in CDN distribution for DRM enforcement. Our local loopback server has no DRM scope: the source file is already in the user's possession. The attribute caused AVPlayer's variant filter to reject the variant on edge-case HDMI HDCP-negotiation states. Removed.

P8.4 mirror of P8.1 branched emission

P8.4 now matches P8.1's two-branch emission: DV-capable panel gets hvc1 + SUPPLEMENTAL-CODECS dvh1.08.XX/db4h with DV preserved; non-DV panel gets plain hvc1 with DV stripped.

Diagnostics

Richer instrumentation on item.failed, evergreen value:

  • errorLog().events full dump (was: only post-registration notifications via newErrorLogEntryNotification; missed entries logged during replaceCurrentItem)
  • accessLog().events full dump
  • Item-level state: item.tracks with FourCC per track, presentationSize, seekableTimeRanges.count, loadedTimeRanges.count, duration, appliesPerFrameHDRDisplayMetadata
  • HLSLocalServer logs first request headers per session (AVPlayer's User-Agent, X-Playback-Session-Id, Accept-Encoding, etc.)
  • HLSLocalServer logs media.m3u8 TAIL as well as head

Acknowledgements

@DrHurt for the testing patience across multiple builds in #4 and for independently reaching the "go back to engine-driven manual display criteria" conclusion at 2026-05-26 07:59 UTC. That architectural call closed the bug.


Full changelog: 1.4.2...1.4.4