fix: replace CoreMedia periodic time observer with pure Swift Timer to eliminate autorelease pool race
Root cause: addPeriodicTimeObserver's internal FigNotificationCenter weak
listener mechanism races with autorelease pool drain on main thread, causing
double-free of weak reference wrappers (KERN_INVALID_ADDRESS).
Changes:
- Replace addPeriodicTimeObserver with Timer.scheduledTimer (bypasses CoreMedia
weak listener infrastructure entirely)
- Remove ALL Task { @MainActor in } from observer callbacks — these created
unstructured tasks whose weak ref wrappers conflicted with CoreMedia internals
- Use DispatchQueue.main.async for KVO callbacks (may fire from non-main thread)
- Direct calls for queue: .main callbacks (NotificationCenter, end observer)
- Add isTearingDown flag to prevent callbacks firing during cleanup
- Fix cleanup() order: timer → KVO → notifications → replaceCurrentItem(nil)
- Fix FullscreenPlayerView: use addSublayer instead of replacing backing layer
- Add .onDisappear { bridge.cleanup() } to ensure cleanup before dealloc
- Remove Combine import (no longer needed)
This commit is contained in:
parent
b2e8dfe812
commit
0d63414214
@ -11,6 +11,7 @@ struct MiniPlayerApp: App {
|
||||
ContentView(bridge: bridge)
|
||||
.frame(minWidth: 900, minHeight: 600)
|
||||
.onAppear { bridge.setup() }
|
||||
.onDisappear { bridge.cleanup() }
|
||||
}
|
||||
#if os(macOS)
|
||||
.commands {
|
||||
|
||||
@ -425,7 +425,10 @@ final class PlayerBridge: ObservableObject {
|
||||
import AppKit
|
||||
|
||||
class FullscreenPlayerView: NSView {
|
||||
private let playerLayer = AVPlayerLayer()
|
||||
// 使用 makeBackingLayer 让 AppKit 管理 AVPlayerLayer
|
||||
override func makeBackingLayer() -> CALayer { AVPlayerLayer() }
|
||||
|
||||
private var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
|
||||
|
||||
var player: AVPlayer? {
|
||||
get { playerLayer.player }
|
||||
@ -442,14 +445,6 @@ class FullscreenPlayerView: NSView {
|
||||
|
||||
override var acceptsFirstResponder: Bool { true }
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
if playerLayer.superlayer !== layer {
|
||||
layer?.addSublayer(playerLayer)
|
||||
}
|
||||
playerLayer.frame = bounds
|
||||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
if event.clickCount == 2 {
|
||||
NotificationCenter.default.post(name: .init("ExitFullscreen"), object: nil)
|
||||
|
||||
@ -42,19 +42,32 @@ import AppKit
|
||||
struct VideoPlayerRepresentable: NSViewRepresentable {
|
||||
let player: AVPlayer
|
||||
|
||||
func makeCoordinator() -> Coordinator { Coordinator() }
|
||||
|
||||
class Coordinator {
|
||||
weak var view: PlayerNSView?
|
||||
}
|
||||
|
||||
func makeNSView(context: Context) -> PlayerNSView {
|
||||
let view = PlayerNSView()
|
||||
view.player = player
|
||||
context.coordinator.view = view
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: PlayerNSView, context: Context) {
|
||||
nsView.player = player
|
||||
// 只在 player 引用变化时设置(Coordinator 确保不重复设置)
|
||||
if nsView.player !== player {
|
||||
nsView.player = player
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerNSView: NSView {
|
||||
private let playerLayer = AVPlayerLayer()
|
||||
// 使用 makeBackingLayer 让 AppKit 管理 AVPlayerLayer 作为 backing layer
|
||||
override func makeBackingLayer() -> CALayer { AVPlayerLayer() }
|
||||
|
||||
private var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: frame)
|
||||
@ -64,16 +77,6 @@ class PlayerNSView: NSView {
|
||||
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
// 确保 playerLayer 作为 sublayer 存在(不要在 init 中 addSublayer,
|
||||
// 因为 wantsLayer=true 后 layer 可能还没创建)
|
||||
if playerLayer.superlayer !== layer {
|
||||
layer?.addSublayer(playerLayer)
|
||||
}
|
||||
playerLayer.frame = bounds
|
||||
}
|
||||
|
||||
var player: AVPlayer? {
|
||||
get { playerLayer.player }
|
||||
set { playerLayer.player = newValue }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user