MiniPlayer/Sources/VideoPlayerView.swift
yumoqing 0d63414214 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)
2026-06-22 01:06:39 +08:00

86 lines
2.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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