feat(macos): add picture-in-picture support
This commit is contained in:
@@ -13,7 +13,8 @@ private class MetalLayer: CAMetalLayer {
|
||||
override var drawableSize: CGSize {
|
||||
get { return super.drawableSize }
|
||||
set {
|
||||
if Int(newValue.width) > 1 && Int(newValue.height) > 1 {
|
||||
// Allow .zero (auto-derive from bounds) or valid sizes > 1x1
|
||||
if newValue == .zero || (Int(newValue.width) > 1 && Int(newValue.height) > 1) {
|
||||
super.drawableSize = newValue
|
||||
}
|
||||
}
|
||||
@@ -49,6 +50,9 @@ class MpvPlayerCore: NSObject {
|
||||
|
||||
private(set) var isInitialized = false
|
||||
|
||||
// PiP state
|
||||
var isPipActive = false
|
||||
|
||||
// HDR settings
|
||||
private var hdrEnabled = true // User preference for HDR
|
||||
private var lastSigPeak: Double = 0.0 // Last known sig-peak for re-evaluation
|
||||
@@ -119,25 +123,25 @@ class MpvPlayerCore: NSObject {
|
||||
// MARK: - Fullscreen Transition Handling
|
||||
|
||||
@objc private func windowWillEnterFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
guard mpv != nil, !isPipActive else { return }
|
||||
print("[MpvPlayerCore] willEnterFullScreen — disabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "no")
|
||||
}
|
||||
|
||||
@objc private func windowDidEnterFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
guard mpv != nil, !isPipActive else { return }
|
||||
print("[MpvPlayerCore] didEnterFullScreen — re-enabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "auto")
|
||||
}
|
||||
|
||||
@objc private func windowWillExitFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
guard mpv != nil, !isPipActive else { return }
|
||||
print("[MpvPlayerCore] willExitFullScreen — disabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "no")
|
||||
}
|
||||
|
||||
@objc private func windowDidExitFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
guard mpv != nil, !isPipActive else { return }
|
||||
print("[MpvPlayerCore] didExitFullScreen — re-enabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "auto")
|
||||
}
|
||||
@@ -300,10 +304,64 @@ class MpvPlayerCore: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PiP Support
|
||||
|
||||
/// The Metal layer used for video rendering, exposed for PiP.
|
||||
/// PiP moves this layer to its own window; mpv continues rendering to it.
|
||||
var videoLayer: CAMetalLayer? { metalLayer }
|
||||
|
||||
/// Re-attach the Metal layer to the main window after PiP exits.
|
||||
func reattachMetalLayer() {
|
||||
guard let metalLayer = metalLayer, let contentView = window?.contentView else { return }
|
||||
if metalLayer.superlayer == nil {
|
||||
contentView.wantsLayer = true
|
||||
contentView.layer?.insertSublayer(metalLayer, at: 0)
|
||||
metalLayer.frame = contentView.bounds
|
||||
if let screen = window?.screen ?? NSScreen.main {
|
||||
metalLayer.contentsScale = screen.backingScaleFactor
|
||||
metalLayer.drawableSize = CGSize(
|
||||
width: contentView.bounds.width * screen.backingScaleFactor,
|
||||
height: contentView.bounds.height * screen.backingScaleFactor
|
||||
)
|
||||
}
|
||||
}
|
||||
print("[MpvPlayerCore] Metal layer reattached to window")
|
||||
}
|
||||
|
||||
/// Force a redraw (useful after PiP exit when paused).
|
||||
/// Uses a seek to the current position to trigger a frame render.
|
||||
func forceDraw() {
|
||||
command(["seek", "0", "relative+exact"])
|
||||
}
|
||||
|
||||
/// Whether mpv is currently paused
|
||||
var isPaused: Bool {
|
||||
guard let mpv = mpv else { return true }
|
||||
var flag: Int32 = 0
|
||||
mpv_get_property(mpv, "pause", MPV_FORMAT_FLAG, &flag)
|
||||
return flag != 0
|
||||
}
|
||||
|
||||
/// Current playback duration in seconds
|
||||
var duration: Double {
|
||||
guard let mpv = mpv else { return 0 }
|
||||
var value: Double = 0
|
||||
mpv_get_property(mpv, "duration", MPV_FORMAT_DOUBLE, &value)
|
||||
return value
|
||||
}
|
||||
|
||||
/// Current playback time in seconds
|
||||
var timePos: Double {
|
||||
guard let mpv = mpv else { return 0 }
|
||||
var value: Double = 0
|
||||
mpv_get_property(mpv, "time-pos", MPV_FORMAT_DOUBLE, &value)
|
||||
return value
|
||||
}
|
||||
|
||||
// MARK: - Visibility
|
||||
|
||||
func setVisible(_ visible: Bool) {
|
||||
guard let layer = metalLayer else { return }
|
||||
guard let layer = metalLayer, !isPipActive else { return }
|
||||
|
||||
if visible {
|
||||
// Re-insert after background layer but before Flutter control views
|
||||
@@ -321,7 +379,7 @@ class MpvPlayerCore: NSObject {
|
||||
}
|
||||
|
||||
func updateFrame(_ frame: CGRect? = nil) {
|
||||
guard let metalLayer = metalLayer else { return }
|
||||
guard let metalLayer = metalLayer, !isPipActive else { return }
|
||||
|
||||
if let frame = frame {
|
||||
metalLayer.frame = frame
|
||||
|
||||
Reference in New Issue
Block a user