192 lines
5.5 KiB
Swift
192 lines
5.5 KiB
Swift
import Libmpv
|
|
import UIKit
|
|
|
|
/// Core MPV player using Metal rendering for iOS.
|
|
class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
private var containerView: UIView?
|
|
private weak var window: UIWindow?
|
|
|
|
var isPipStarting = false
|
|
|
|
func initialize(in window: UIWindow) -> Bool {
|
|
guard !isInitialized else {
|
|
print("[MpvPlayerCore] Already initialized")
|
|
return true
|
|
}
|
|
|
|
self.window = window
|
|
|
|
let container = UIView(frame: window.bounds)
|
|
container.backgroundColor = .clear
|
|
container.isUserInteractionEnabled = false
|
|
|
|
let layer = MpvMetalLayer()
|
|
layer.frame = container.bounds
|
|
layer.contentsScale = UIScreen.main.nativeScale
|
|
layer.framebufferOnly = true
|
|
layer.backgroundColor = UIColor.black.cgColor
|
|
|
|
container.layer.addSublayer(layer)
|
|
containerView = container
|
|
metalLayer = layer
|
|
|
|
window.insertSubview(container, at: 0)
|
|
|
|
guard setupMpv() else {
|
|
print("[MpvPlayerCore] Failed to setup MPV")
|
|
layer.removeFromSuperlayer()
|
|
container.removeFromSuperview()
|
|
metalLayer = nil
|
|
containerView = nil
|
|
return false
|
|
}
|
|
|
|
setupNotifications()
|
|
|
|
isInitialized = true
|
|
print("[MpvPlayerCore] Initialized successfully with MPV")
|
|
return true
|
|
}
|
|
|
|
func switchToPipVO(layerPtr: UnsafeMutableRawPointer) -> Bool {
|
|
guard let mpv else { return false }
|
|
|
|
print("[MpvPlayerCore] Switching to pip VO for PiP")
|
|
|
|
metalLayer?.removeFromSuperlayer()
|
|
|
|
mpv_set_property_string(mpv, "vid", "no")
|
|
|
|
var pointer = Int64(Int(bitPattern: layerPtr))
|
|
mpv_set_property(mpv, "wid", MPV_FORMAT_INT64, &pointer)
|
|
|
|
mpv_set_property_string(mpv, "vo", "pip")
|
|
mpv_set_property_string(mpv, "vid", "auto")
|
|
|
|
print("[MpvPlayerCore] Switched to pip VO successfully")
|
|
return true
|
|
}
|
|
|
|
func switchToGpuNextVO() -> Bool {
|
|
guard let mpv, let metalLayer else { return false }
|
|
|
|
print("[MpvPlayerCore] Switching back to gpu-next VO")
|
|
|
|
mpv_set_property_string(mpv, "vid", "no")
|
|
|
|
var layer = metalLayer
|
|
mpv_set_property(mpv, "wid", MPV_FORMAT_INT64, &layer)
|
|
|
|
applyGpuNextOptions()
|
|
mpv_set_property_string(mpv, "vid", "auto")
|
|
|
|
if metalLayer.superlayer == nil, let containerView {
|
|
containerView.layer.addSublayer(metalLayer)
|
|
}
|
|
|
|
print("[MpvPlayerCore] Switched back to gpu-next VO successfully")
|
|
return true
|
|
}
|
|
|
|
func setVisible(_ visible: Bool) {
|
|
guard let containerView else { return }
|
|
|
|
if visible {
|
|
containerView.removeFromSuperview()
|
|
window?.insertSubview(containerView, at: 0)
|
|
}
|
|
|
|
containerView.isHidden = !visible
|
|
}
|
|
|
|
func updateFrame(_ frame: CGRect? = nil) {
|
|
guard let metalLayer, let containerView else { return }
|
|
|
|
if let frame {
|
|
containerView.frame = frame
|
|
metalLayer.frame = containerView.bounds
|
|
} else if let window {
|
|
containerView.frame = window.bounds
|
|
metalLayer.frame = containerView.bounds
|
|
}
|
|
|
|
let scale = UIScreen.main.nativeScale
|
|
metalLayer.drawableSize = CGSize(
|
|
width: metalLayer.frame.width * scale,
|
|
height: metalLayer.frame.height * scale
|
|
)
|
|
}
|
|
|
|
override func updateEDRMode(sigPeak: Double) {
|
|
guard let metalLayer else { return }
|
|
|
|
var edrHeadroom: CGFloat = 1.0
|
|
if #available(iOS 16.0, *) {
|
|
edrHeadroom = containerView?.window?.screen.potentialEDRHeadroom ?? 1.0
|
|
metalLayer.wantsExtendedDynamicRangeContent =
|
|
hdrEnabled && sigPeak > 1.0 && edrHeadroom > 1.0
|
|
}
|
|
|
|
let shouldEnableEDR = hdrEnabled && sigPeak > 1.0 && edrHeadroom > 1.0
|
|
print(
|
|
"[MpvPlayerCore] EDR mode: \(shouldEnableEDR) (hdrEnabled: \(hdrEnabled), sigPeak: \(sigPeak), headroom: \(edrHeadroom))"
|
|
)
|
|
}
|
|
|
|
func dispose() {
|
|
NotificationCenter.default.removeObserver(self)
|
|
disposeSharedState(destroySynchronously: false)
|
|
|
|
metalLayer?.removeFromSuperlayer()
|
|
metalLayer = nil
|
|
containerView?.removeFromSuperview()
|
|
containerView = nil
|
|
isInitialized = false
|
|
print("[MpvPlayerCore] Disposed")
|
|
}
|
|
|
|
deinit {
|
|
dispose()
|
|
}
|
|
|
|
private func setupNotifications() {
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(enterBackground),
|
|
name: UIApplication.didEnterBackgroundNotification,
|
|
object: nil
|
|
)
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(enterForeground),
|
|
name: UIApplication.willEnterForegroundNotification,
|
|
object: nil
|
|
)
|
|
}
|
|
|
|
@objc private func enterBackground() {
|
|
if isPipActive || isPipStarting {
|
|
print("[MpvPlayerCore] Entering background - PiP active/starting, keeping video")
|
|
return
|
|
}
|
|
|
|
print("[MpvPlayerCore] Entering background - disabling video")
|
|
if mpv != nil {
|
|
mpv_set_option_string(mpv, "vid", "no")
|
|
}
|
|
}
|
|
|
|
@objc private func enterForeground() {
|
|
if isPipActive {
|
|
print("[MpvPlayerCore] Entering foreground - PiP active, skipping vid restore")
|
|
return
|
|
}
|
|
|
|
print("[MpvPlayerCore] Entering foreground - enabling video")
|
|
if mpv != nil {
|
|
mpv_set_option_string(mpv, "vid", "auto")
|
|
}
|
|
}
|
|
}
|