173 lines
4.8 KiB
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)
|
|
}
|
|
}
|