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)
86 lines
2.2 KiB
Swift
86 lines
2.2 KiB
Swift
import SwiftUI
|
||
import AVFoundation
|
||
|
||
// MARK: - 跨平台AVPlayer渲染
|
||
|
||
#if os(iOS)
|
||
import UIKit
|
||
|
||
struct VideoPlayerRepresentable: UIViewRepresentable {
|
||
let player: AVPlayer
|
||
|
||
func makeUIView(context: Context) -> PlayerUIView {
|
||
let view = PlayerUIView()
|
||
view.player = player
|
||
return view
|
||
}
|
||
|
||
func updateUIView(_ uiView: PlayerUIView, context: Context) {
|
||
uiView.player = player
|
||
}
|
||
}
|
||
|
||
class PlayerUIView: UIView {
|
||
override static var layerClass: AnyClass { AVPlayerLayer.self }
|
||
|
||
var player: AVPlayer? {
|
||
get { (layer as? AVPlayerLayer)?.player }
|
||
set { (layer as? AVPlayerLayer)?.player = newValue }
|
||
}
|
||
|
||
override var contentMode: UIView.ContentMode {
|
||
get { (layer as? AVPlayerLayer)?.videoGravity == .resizeAspectFill ? .scaleAspectFill : .scaleAspectFit }
|
||
set {
|
||
(layer as? AVPlayerLayer)?.videoGravity = newValue == .scaleAspectFill ? .resizeAspectFill : .resizeAspect
|
||
}
|
||
}
|
||
}
|
||
|
||
#elseif os(macOS)
|
||
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) {
|
||
// 只在 player 引用变化时设置(Coordinator 确保不重复设置)
|
||
if nsView.player !== player {
|
||
nsView.player = player
|
||
}
|
||
}
|
||
}
|
||
|
||
class PlayerNSView: NSView {
|
||
// 使用 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)
|
||
wantsLayer = true
|
||
playerLayer.videoGravity = .resizeAspect
|
||
}
|
||
|
||
required init?(coder: NSCoder) { fatalError() }
|
||
|
||
var player: AVPlayer? {
|
||
get { playerLayer.player }
|
||
set { playerLayer.player = newValue }
|
||
}
|
||
}
|
||
#endif
|