|
|
|
@@ -8,8 +8,14 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
private weak var window: NSWindow?
|
|
|
|
|
private var playbackActivity: NSObjectProtocol?
|
|
|
|
|
private var layerHiddenForOcclusion = false
|
|
|
|
|
private var layerHiddenForScreenSleep = false
|
|
|
|
|
private var isDisposed = false
|
|
|
|
|
|
|
|
|
|
/// True while any reason (occlusion, screen sleep) requires the layer hidden.
|
|
|
|
|
private var hasLayerHideReason: Bool {
|
|
|
|
|
layerHiddenForOcclusion || layerHiddenForScreenSleep
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func initialize(in window: NSWindow) -> Bool {
|
|
|
|
|
guard !isInitialized else {
|
|
|
|
|
print("[MpvPlayerCore] Already initialized")
|
|
|
|
@@ -73,6 +79,24 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
object: window
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Display/system sleep does not reliably change occlusionState, so observe
|
|
|
|
|
// NSWorkspace screen sleep/wake directly to gate presentation (prevents a
|
|
|
|
|
// pinned CPU core after long display sleep). This is a DIFFERENT
|
|
|
|
|
// notification center than NotificationCenter.default used above.
|
|
|
|
|
let workspaceCenter = NSWorkspace.shared.notificationCenter
|
|
|
|
|
workspaceCenter.addObserver(
|
|
|
|
|
self,
|
|
|
|
|
selector: #selector(screensDidSleep),
|
|
|
|
|
name: NSWorkspace.screensDidSleepNotification,
|
|
|
|
|
object: nil
|
|
|
|
|
)
|
|
|
|
|
workspaceCenter.addObserver(
|
|
|
|
|
self,
|
|
|
|
|
selector: #selector(screensDidWake),
|
|
|
|
|
name: NSWorkspace.screensDidWakeNotification,
|
|
|
|
|
object: nil
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
isInitialized = true
|
|
|
|
|
print("[MpvPlayerCore] Initialized successfully with MPV")
|
|
|
|
|
return true
|
|
|
|
@@ -107,8 +131,9 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
|
|
|
|
|
if visible && isVisible && !shouldRestoreOnWindowVisible {
|
|
|
|
|
isBackgrounded = false
|
|
|
|
|
if metalLayer?.isHidden == true {
|
|
|
|
|
if metalLayer?.isHidden == true && !hasLayerHideReason {
|
|
|
|
|
setMetalLayerHidden(false)
|
|
|
|
|
redrawIfPausedAndVisible()
|
|
|
|
|
}
|
|
|
|
|
beginPlaybackActivity()
|
|
|
|
|
print("[MpvPlayerCore] setVisible(true) skipped - already visible")
|
|
|
|
@@ -132,7 +157,10 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
endPlaybackActivity()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setMetalLayerHidden(!visible)
|
|
|
|
|
setMetalLayerHidden(!visible || hasLayerHideReason)
|
|
|
|
|
if visible {
|
|
|
|
|
redrawIfPausedAndVisible()
|
|
|
|
|
}
|
|
|
|
|
print("[MpvPlayerCore] setVisible(\(visible), restoreOnWindowVisible: \(restoreOnWindowVisible))")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -189,6 +217,7 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
|
|
|
|
|
endPlaybackActivity()
|
|
|
|
|
NotificationCenter.default.removeObserver(self)
|
|
|
|
|
NSWorkspace.shared.notificationCenter.removeObserver(self)
|
|
|
|
|
disposeSharedState(destroySynchronously: false)
|
|
|
|
|
|
|
|
|
|
metalLayer?.removeFromSuperlayer()
|
|
|
|
@@ -224,10 +253,13 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
} else if windowVisible && layerHiddenForOcclusion {
|
|
|
|
|
print("[MpvPlayerCore] Window visible - showing Metal layer")
|
|
|
|
|
layerHiddenForOcclusion = false
|
|
|
|
|
if shouldRestoreOnWindowVisible {
|
|
|
|
|
restoreMetalLayerAfterOcclusion()
|
|
|
|
|
} else {
|
|
|
|
|
setMetalLayerHidden(!isVisible)
|
|
|
|
|
if !layerHiddenForScreenSleep {
|
|
|
|
|
if shouldRestoreOnWindowVisible {
|
|
|
|
|
restoreMetalLayerAfterOcclusion()
|
|
|
|
|
} else {
|
|
|
|
|
setMetalLayerHidden(!isVisible)
|
|
|
|
|
}
|
|
|
|
|
redrawIfPausedAndVisible()
|
|
|
|
|
}
|
|
|
|
|
isBackgrounded = false
|
|
|
|
|
if !pausedState {
|
|
|
|
@@ -236,6 +268,54 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc private func screensDidSleep(_ notification: Notification) {
|
|
|
|
|
guard metalLayer != nil, mpv != nil, !layerHiddenForScreenSleep else { return }
|
|
|
|
|
print("[MpvPlayerCore] Screens did sleep - hiding Metal layer")
|
|
|
|
|
layerHiddenForScreenSleep = true
|
|
|
|
|
// Hide even during PiP: nothing is visible while the displays are dark, and
|
|
|
|
|
// the hidden layer is what gates libmpv presentation (MPVKit >= 1.0.10).
|
|
|
|
|
setMetalLayerHidden(true)
|
|
|
|
|
isBackgrounded = true
|
|
|
|
|
endPlaybackActivity()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc private func screensDidWake(_ notification: Notification) {
|
|
|
|
|
guard metalLayer != nil, mpv != nil, layerHiddenForScreenSleep else { return }
|
|
|
|
|
print("[MpvPlayerCore] Screens did wake - restoring Metal layer")
|
|
|
|
|
layerHiddenForScreenSleep = false
|
|
|
|
|
|
|
|
|
|
if isPipActive {
|
|
|
|
|
// Layer is hosted by the PiP window; just unhide it there. Attach/frame
|
|
|
|
|
// logic is owned by the PiP controller.
|
|
|
|
|
setMetalLayerHidden(false)
|
|
|
|
|
isBackgrounded = false
|
|
|
|
|
} else if !layerHiddenForOcclusion {
|
|
|
|
|
if shouldRestoreOnWindowVisible {
|
|
|
|
|
restoreMetalLayerAfterOcclusion()
|
|
|
|
|
} else {
|
|
|
|
|
setMetalLayerHidden(!isVisible)
|
|
|
|
|
}
|
|
|
|
|
isBackgrounded = !isVisible
|
|
|
|
|
}
|
|
|
|
|
// else: window still occluded; windowOcclusionDidChange owns the restore.
|
|
|
|
|
|
|
|
|
|
if !layerHiddenForOcclusion && !pausedState {
|
|
|
|
|
beginPlaybackActivity()
|
|
|
|
|
}
|
|
|
|
|
redrawIfPausedAndVisible()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// With MPVKit >= 1.0.10, frames produced while the layer was hidden are
|
|
|
|
|
/// skipped at the swapchain, so a paused video would otherwise show a stale
|
|
|
|
|
/// frame after unhiding (upstream regression mpv#16693). Playing video
|
|
|
|
|
/// repaints itself on the next frame; only paused needs a forced draw. The
|
|
|
|
|
/// isHidden guard makes multi-path wake ordering safe — only the path that
|
|
|
|
|
/// actually unhides the layer triggers the single redraw.
|
|
|
|
|
private func redrawIfPausedAndVisible() {
|
|
|
|
|
guard pausedState, metalLayer?.isHidden == false else { return }
|
|
|
|
|
forceDraw()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func beginPlaybackActivity() {
|
|
|
|
|
guard playbackActivity == nil else { return }
|
|
|
|
|
playbackActivity = ProcessInfo.processInfo.beginActivity(
|
|
|
|
@@ -267,7 +347,7 @@ class MpvPlayerCore: MpvPlayerCoreBase {
|
|
|
|
|
}
|
|
|
|
|
isVisible = true
|
|
|
|
|
shouldRestoreOnWindowVisible = false
|
|
|
|
|
setMetalLayerHidden(false)
|
|
|
|
|
setMetalLayerHidden(hasLayerHideReason)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func attachMetalLayer(to superlayer: CALayer, frame: CGRect) {
|
|
|
|
|