fix: nil AVPlayerLayer.player before closing fullscreen window

Root cause: when FullscreenPlayerView deallocates during window close,
AVPlayerLayer.player reference is released via autorelease pool drain
on main thread. CoreMedia background threads simultaneously access
sFigNotificationCenterWeakListenerLinks dictionary (weak listener
cleanup), causing use-after-free race condition.

Fix:
- toggleFullscreen(): set playerView.player = nil BEFORE fw.close()
- FullscreenPlayerView.viewWillMove(toWindow: nil): safety net to
  nil out player when view is removed from window by any means

This ensures the AVPlayer reference is released synchronously on the
main thread, before any autorelease pool drain can race with CoreMedia
internal cleanup threads.
This commit is contained in:
yumoqing 2026-06-22 01:11:40 +08:00
parent 0d63414214
commit 6bbd86006a

View File

@ -279,6 +279,11 @@ final class PlayerBridge: ObservableObject {
func toggleFullscreen() { func toggleFullscreen() {
#if os(macOS) #if os(macOS)
if let fw = fullscreenWindow { if let fw = fullscreenWindow {
// 先断开 AVPlayerLayer → player 引用,避免 autorelease pool drain 时
// 与 CoreMedia 后台线程竞争 sFigNotificationCenterWeakListenerLinks
if let playerView = fw.contentView as? FullscreenPlayerView {
playerView.player = nil
}
fw.close() fw.close()
fullscreenWindow = nil fullscreenWindow = nil
isFullscreen = false isFullscreen = false
@ -445,6 +450,15 @@ class FullscreenPlayerView: NSView {
override var acceptsFirstResponder: Bool { true } override var acceptsFirstResponder: Bool { true }
// 安全网:视图从窗口移除时主动断开 player 引用
// 避免 CoreMedia 后台线程与 autorelease pool drain 竞争
override func viewWillMove(toWindow newWindow: NSWindow?) {
if newWindow == nil {
playerLayer.player = nil
}
super.viewWillMove(toWindow: newWindow)
}
override func mouseDown(with event: NSEvent) { override func mouseDown(with event: NSEvent) {
if event.clickCount == 2 { if event.clickCount == 2 {
NotificationCenter.default.post(name: .init("ExitFullscreen"), object: nil) NotificationCenter.default.post(name: .init("ExitFullscreen"), object: nil)