Add tvOS display frame rate and dynamic range matching

Lets the Apple TV switch its HDMI output to match the playing video's
frame rate and dynamic range via AVDisplayManager.preferredDisplayCriteria,
driven from MPV's container-fps and video-params/gamma. Two opt-in toggles
(default off) live under Playback → Display on tvOS; both are no-ops on
other platforms. Anchor an AVKit class symbol so the linker keeps AVKit
linked — Swift only autolinks AVFoundation here, and without AVKit the
UIWindow.avDisplayManager category isn't loaded at runtime.
This commit is contained in:
Arkadiusz Fal
2026-05-10 01:30:55 +02:00
parent 100e762d4b
commit 6a343311ea
8 changed files with 333 additions and 0 deletions

View File

@@ -133,6 +133,9 @@ final class MPVBackend: PlayerBackend {
private var videoCodec: String = ""
private var hwdecCurrent: String = ""
private var hwdecInterop: String = ""
#if os(tvOS)
private var videoGamma: String = ""
#endif
// Async initialization tracking
private var setupTask: Task<Void, Error>?
@@ -753,6 +756,10 @@ final class MPVBackend: PlayerBackend {
duration = 0
bufferedTime = 0
currentStream = nil
#if os(tvOS)
videoGamma = ""
clearTVDisplayCriteria()
#endif
delegate?.backend(self, didChangeState: .idle)
}
@@ -1631,8 +1638,19 @@ extension MPVBackend: MPVClientDelegate {
if let fps = value as? Double, fps > 0 {
containerFps = fps
updateRenderViewFPS()
#if os(tvOS)
applyTVDisplayCriteria()
#endif
}
#if os(tvOS)
case "video-params/gamma":
if let gamma = value as? String {
videoGamma = gamma
applyTVDisplayCriteria()
}
#endif
case "paused-for-cache":
if let isPausedForCache = value as? Bool {
pausedForCache = isPausedForCache
@@ -1706,6 +1724,25 @@ extension MPVBackend: MPVClientDelegate {
checkAndMarkReadyIfVideoAvailable()
}
#if os(tvOS)
/// Apply tvOS display-mode criteria (frame rate / dynamic range) based on the
/// currently-cached MPV video parameters. Safe to call repeatedly as more info
/// arrives (fps may land before gamma or vice versa).
private func applyTVDisplayCriteria() {
let fps = containerFps > 0 ? containerFps : nil
let gamma = videoGamma.isEmpty ? nil : videoGamma
Task { @MainActor in
TVDisplayModeManager.shared.apply(fps: fps, gamma: gamma)
}
}
private func clearTVDisplayCriteria() {
Task { @MainActor in
TVDisplayModeManager.shared.clear()
}
}
#endif
/// Update render view's video FPS for display link frame rate matching
private func updateRenderViewFPS() {
// Use cached container-fps (set via property observation to avoid sync fetch on main thread)
@@ -1771,6 +1808,7 @@ extension MPVBackend: MPVClientDelegate {
let hwdec = hwdecCurrent.isEmpty ? "none" : hwdecCurrent
let interop = hwdecInterop.isEmpty ? "none" : hwdecInterop
LoggingService.shared.debug("MPV: Video codec: \(codec), hwdec-current: \(hwdec), hwdec-interop: \(interop)", category: .mpv)
applyTVDisplayCriteria()
#endif
#if os(iOS) || os(tvOS)
reactivateAudioSession()