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
{{ message }}
Repository navigation
A supported host-owned display-criteria mode: suppressDisplayCriteria means "AVKit writes", and a host that keeps the panel in its system format pays for that #842
Raised by the StreamX integrator (twice now): their Dynamic Range setting has a System position (keep the panel in the output format the user chose in tvOS Settings) and a Content position (match the source). The only way to get System today is LoadOptions.suppressDisplayCriteria = true, which they rightly call a hack.
What suppressDisplayCriteria actually means today
Its doc says "Skip display-criteria handshake entirely. For previews and aetherctl where no panel exists." The code treats it as something narrower: AVKit is the sole writer (AVPlayerViewController.appliesPreferredDisplayCriteriaAutomatically) and writes the criteria from the AVPlayerItem somewhere inside the load. Every branch is built around that inbound write:
Play gate waits for it.playGateGrace(engineIsCriteriaWriter: false, ...) returns .full (1000 ms Stage 1) for every non-SDR source, at load and again on every native reload (AetherEngine.swift play gate, AetherEngine+Loading.swift reload gate). With nobody writing, that is a second of dead startup per HDR / DV title that buys nothing.
The panel readout is switched off.criteriaPanelReadout = options.suppressDisplayCriteria ? nil : displayCriteria.currentPanelIsHDR(), because a readout taken before AVKit's write would be stale. A host that wrote its own criteria before load() (or wrote none) loses the engine's half of the HDR-panel answer and routes on panelIsInHDRMode alone.
Observation is armed for a write that is not coming. Harmless, but it is the same misreading.
So a host that owns the criteria, rather than delegating them to AVKit, has no correct setting: false makes the engine write range + rate on every load (Content, not System), true makes it wait for AVKit.
Proposal
An explicit owner instead of a Bool that means two things:
publicenumDisplayCriteriaOwner:Sendable,Equatable{case engine // default: the engine writes range + rate and waits for the switch
case avKit // AVKit writes from the item; today's suppressDisplayCriteria = true
case host // the host writes before load(), or nobody does
}publicvardisplayCriteriaOwner:DisplayCriteriaOwner=.engine
the play gate takes the same .brief start grace an engine write gets, because the write happened before load(); a switch the engine observes running is still waited out (.awaitObservedEnd on VOD);
the panel readout is taken as on the engine path;
observation is armed at load, so a host write that lands during the load is seen.
suppressDisplayCriteria = true stays as the existing spelling of .avKit (no break), with its doc corrected. An explicit non-.engine owner wins over the Bool.
Open question for StreamX
In the System position, does StreamX write any criteria itself (for example rate-only, so Match Frame Rate still engages), or nobody? .host covers both; the answer decides whether .host also wants a hook for the engine to learn the rate it should match, which this issue does not add.
Activity
superuser404notfound commentedon Oct 19, 2026
Released in 7.42.0 (
58303c74, PR #843).feat(display): a host-owned display-criteria mode (superuser404notfou…