Skip to content

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

Closed

Description

@superuser404notfound

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:

  1. 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.
  2. 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.
  3. 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:

public enum DisplayCriteriaOwner: 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
}
public var displayCriteriaOwner: DisplayCriteriaOwner = .engine

.host:

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

  1. superuser404notfound commented on Oct 19, 2026

    @superuser404notfound
    OwnerAuthor

    Released in 7.42.0 (58303c74, PR #843).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions