Files
plezy/ios/Runner/MpvPlayer/ExternalDisplayManager.swift

173 lines
4.8 KiB
Swift

import UIKit
final class ExternalDisplayManager {
static let shared = ExternalDisplayManager()
private weak var activeCore: MpvPlayerCore?
private var externalScene: UIWindowScene?
private var externalWindow: UIWindow?
private var externalRootViewController: UIViewController?
private init() {}
static func isExternalDisplayRole(_ role: UISceneSession.Role) -> Bool {
role.rawValue == "UIWindowSceneSessionRoleExternalDisplay"
|| role.rawValue == "UIWindowSceneSessionRoleExternalDisplayNonInteractive"
}
static var hasActiveApplicationScene: Bool {
applicationWindowScenes.contains { $0.activationState == .foregroundActive }
}
static func mainApplicationWindow() -> UIWindow? {
let scenes = applicationWindowScenes.sorted {
scenePriority($0.activationState) < scenePriority($1.activationState)
}
for scene in scenes {
if let window = scene.windows.first(where: { $0.isKeyWindow }) {
return window
}
}
for scene in scenes {
if let window = scene.windows.first(where: { !$0.isHidden }) {
return window
}
}
return scenes.first?.windows.first
}
private static var applicationWindowScenes: [UIWindowScene] {
UIApplication.shared.connectedScenes.compactMap { scene in
guard let windowScene = scene as? UIWindowScene,
!isExternalDisplayRole(windowScene.session.role)
else { return nil }
return windowScene
}
}
private static func scenePriority(_ state: UIScene.ActivationState) -> Int {
switch state {
case .foregroundActive:
return 0
case .foregroundInactive:
return 1
case .background:
return 2
case .unattached:
return 3
@unknown default:
return 4
}
}
var videoSuperview: UIView? {
externalRootViewController?.view
}
func attach(core: MpvPlayerCore) {
activeCore = core
discoverExistingExternalScene()
activeCore?.externalDisplayDidChange()
}
func detach(core: MpvPlayerCore) {
if activeCore === core {
activeCore = nil
}
}
@discardableResult
func connect(scene: UIWindowScene) -> UIWindow? {
guard Self.isExternalDisplayRole(scene.session.role) else { return nil }
externalScene = scene
let window = ensureWindow(for: scene)
activeCore?.externalDisplayDidChange()
print("[ExternalDisplayManager] External display connected")
return window
}
func disconnect(scene: UIScene) {
guard scene === externalScene else { return }
externalWindow?.isHidden = true
externalWindow = nil
externalRootViewController = nil
externalScene = nil
activeCore?.externalDisplayDidChange()
print("[ExternalDisplayManager] External display disconnected")
}
func update(scene: UIWindowScene) {
guard scene === externalScene else { return }
externalRootViewController?.view.frame = scene.coordinateSpace.bounds
activeCore?.externalDisplayDidChange()
}
private func discoverExistingExternalScene() {
guard externalScene == nil else { return }
for scene in UIApplication.shared.connectedScenes {
guard let windowScene = scene as? UIWindowScene,
Self.isExternalDisplayRole(windowScene.session.role)
else { continue }
_ = connect(scene: windowScene)
return
}
}
private func ensureWindow(for scene: UIWindowScene) -> UIWindow {
if let existing = externalWindow, existing.windowScene === scene {
return existing
}
externalWindow?.isHidden = true
let rootViewController = UIViewController()
rootViewController.view.frame = scene.coordinateSpace.bounds
rootViewController.view.backgroundColor = .black
let window = UIWindow(windowScene: scene)
window.rootViewController = rootViewController
window.backgroundColor = .black
window.isHidden = false
externalRootViewController = rootViewController
externalWindow = window
return window
}
}
class ExternalDisplaySceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene,
ExternalDisplayManager.isExternalDisplayRole(session.role)
else { return }
window = ExternalDisplayManager.shared.connect(scene: windowScene)
}
func sceneDidDisconnect(_ scene: UIScene) {
ExternalDisplayManager.shared.disconnect(scene: scene)
window = nil
}
func windowScene(
_ windowScene: UIWindowScene,
didUpdate previousCoordinateSpace: UICoordinateSpace,
interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation,
traitCollection previousTraitCollection: UITraitCollection
) {
ExternalDisplayManager.shared.update(scene: windowScene)
}
}