PrismCore / API reference / Display
ClasstvOS only
DisplayCriteriaController
@MainActor final class DisplayCriteriaControllerDeclared in Sources/PrismCore/Display/DisplayCriteriaController.swift · PrismCore 3.5.4
Programs the HDMI dynamic-range / refresh-rate handshake for a session, via AVDisplayManager — the piece of the playback contract that has to run before the host hands AVPlayer the playlist URL.
let controller = DisplayCriteriaController(window: window)
let session = try PrismCoreSession(url: source, display: .current())
let playlist = try await session.start()
if let choice = await session.displayCriteria {
controller.apply(choice)
await controller.waitForSwitch()
}
player.replaceCurrentItem(with: AVPlayerItem(url: playlist))Hosts using AVPlayerViewController must also set appliesPreferredDisplayCriteriaAutomatically = false: two writers racing the same preferredDisplayCriteria slot produce a double handshake, and AVKit's own write cannot arrive early enough for HLS anyway (see DisplayCriteriaChoice).
On teardown call reset(). It nils the criteria only when this controller actually wrote them, so a host that never applied anything never disturbs whatever else is managing the panel.
Initializers
@MainActor init(window: UIWindow)No documentation comment in the source.
Instance properties
@MainActor var matchingIsEnabled: Bool { get }isDisplayCriteriaMatchingEnabled is a combined flag: true when either Match Dynamic Range or Match Frame Rate is on, with no public way to tell which. That makes it usable in exactly one direction — false proves no switch can happen; true must never be read as "the panel will follow HDR content" (match-rate-on / match-range-off reads true and the panel still refuses PQ).
Instance methods
@discardableResult @MainActor func apply(_ choice: DisplayCriteriaChoice) -> ApplyResultWrite preferredDisplayCriteria for this choice.
The criteria are a synthetic CMVideoFormatDescription handed to AVDisplayCriteria(refreshRate:formatDescription:). Two details carry the whole behavior:
- The fourcc picks the mode family:
dvh1is what makes the panel negotiate Dolby Vision; an HEVC fourcc with the same BT.2020/PQ color extensions lands in HDR10 instead. (The extensions alone can't say "Dolby Vision" — there is no public way to attach advcCto a synthetic description, which is why criteria built without the fourcc kept DV panels in HDR10.) - SDR omits the color extensions entirely: codec + rate only, so Match Frame Rate can engage without Match Dynamic Range being asked to do anything.
@discardableResult @MainActor func waitForSwitch(startGrace: Duration? = nil, settleCap: Duration? = nil) async -> DisplayCriteriaLogic.SettleReportBlock until the HDMI handshake the last apply(_:) started has settled, bounded so a switch the app can't observe never stalls the first frame.
Stage 1 waits (up to startGrace) for a switch to start — the criteria write kicks the negotiation off asynchronously, and loading the asset mid-write is exactly the race the whole exercise exists to avoid. A start is visible as the AVDisplayManager mode-switch-start notification or the in-progress flag. If nothing starts, the panel already satisfied the criteria and there is nothing to wait for.
Stage 2 waits (up to settleCap) for any reliable "done" signal: the mode-switch-end notification, the EDR headroom rising (HDR targets), or the in-progress flag clearing. Some panels complete a Dolby Vision switch without ever reporting it — headroom stays 1.0, the in-progress flag sticks — which is why the cap exists and why hitting it is not an error: the panel is mid re-sync (black) during the handshake anyway, and shows the correct picture once it locks.
nil bounds (the defaults) resolve per what the last apply(_:) wrote — dynamic-range switches get room for a real HDMI renegotiation, rate-only switches keep short bounds. See DisplayCriteriaLogic.settleBounds for the numbers and the race they close; pass explicit values only to override that policy.
The returned report carries what actually happened, with wall-clock times — log its summary, and the settle budgets stop being bets.
@MainActor func currentPanelIsHDR() -> BoolWhether the panel presents this session in HDR — the input a host feeds back into DisplayCapabilities.panelIsCurrentlyHDR for the next session's routing. Meaningful after apply + waitForSwitch have settled; see DisplayCriteriaLogic.panelPresentsHDR for why a quiet headroom is not a "no".
@MainActor func reset()Return the panel to its default criteria — only when this controller wrote (or adopted) them. Nil-ing criteria somebody else manages races their in-flight handshake. Clearing the shared baseline is part of the reset: the panel is heading back to its default mode, so the next apply must really write. panelHasProvenHDR survives — the panel's demonstrated capability doesn't reset with its mode.
EnumerationtvOS only
DisplayCriteriaController.ApplyResult
enum ApplyResultWhat apply(_:) did, which tells the host whether a settle wait is worth anything.
Cases
case willSwitchHDR/DV criteria written; a dynamic-range handshake is expected — call waitForSwitch() before starting playback.
case wroteRate-only criteria written; the switch is sub-second and needs no explicit wait.
case alreadyActiveThe identical criteria are already active; nothing was written and no switch is coming.
case matchingDisabledNothing was written: the user has Match Content fully off (both sub-toggles), so tvOS would ignore the write and no switch can ever start. The session should be routed as if the panel stays in its current mode.
Generated from the public symbols of PrismCore 3.5.4. All types