我有一个基于URL播放视频的视频播放器视图。当我离开视频时,我会崩溃,并打印下面的错误。在我的deinit中,我试图使观察者无效,但没有成功。我在deinit函数中缺少一个步骤,导致了这个错误。
由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无法从中删除<NSKeyValueObservance 0x2809113b0>键路径“currentItem.videoComposition”的观察者,<AVQueuePlayer 0x28079dfc0>很可能是因为键“currentItem”的值已更改,但未发送相应的KVO通知。请检查AVOfficePlayer类的KVO合规性
这是抛出的代码:
import Foundation
import AVKit
import SwiftUI
import UIKit
import Combine
public class LegacyAVPlayerViewController: AVPlayerViewController {
var onPlayerStatusChange: ((AVPlayer.TimeControlStatus) -> Void)?
var overlayViewController: UIViewController! {
willSet { assert(overlayViewController == nil, "contentViewController should be set only once") }
didSet { attach() }
}
var overlayView: UIView { overlayViewController.view }
private func attach() {
guard
let overlayViewController = overlayViewController,
overlayViewController.parent == nil
else {
return
}
contentOverlayView?.addSubview(overlayView)
overlayView.backgroundColor = .clear
overlayView.sizeToFit()
overlayView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(contentConstraints)
}
private lazy var contentConstraints: [NSLayoutConstraint] = {
guard let overlay = contentOverlayView else { return [] }
return [
overlayView.topAnchor.constraint(equalTo: overlay.topAnchor),
overlayView.leadingAnchor.constraint(equalTo: overlay.leadingAnchor),
overlayView.bottomAnchor.constraint(equalTo: overlay.bottomAnchor),
overlayView.trailingAnchor.constraint(equalTo: overlay.trailingAnchor),
]
}()
private var rateObserver: NSKeyValueObservation?
public override var player: AVPlayer? {
willSet { rateObserver?.invalidate() }
didSet { rateObserver = player?.observe(\AVPlayer.rate, options: [.new], changeHandler: rateHandler(_:change:)) }
}
deinit { rateObserver?.invalidate() }
private func rateHandler(_ player: AVPlayer, change: NSKeyValueObservedChange<Float>) {
guard let item = player.currentItem,
item.currentTime().seconds > 0.5,
player.status == .readyToPlay
else { return }
onPlayerStatusChange?(player.timeControlStatus)
}
}
public struct LegacyVideoPlayer<Overlay: View>: UIViewControllerRepresentable {
var overlay: () -> Overlay
let url: URL
var onTimeControlStatusChange: ((AVPlayer.TimeControlStatus) -> Void)?
@State var isPlaying = true
@State var isLooping = true
@State var showsPlaybackControls = false
public func makeCoordinator() -> CustomPlayerCoordinator<Overlay> {
CustomPlayerCoordinator(customPlayer: self)
}
public func makeUIViewController(context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) -> LegacyAVPlayerViewController {
let controller = LegacyAVPlayerViewController()
controller.delegate = context.coordinator
makeAVPlayer(in: controller, context: context)
playIfNeeded(controller.player)
return controller
}
public func updateUIViewController(_ uiViewController: LegacyAVPlayerViewController, context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) {
makeAVPlayer(in: uiViewController, context: context)
playIfNeeded(uiViewController.player)
updateOverlay(in: uiViewController, context: context)
}
private func updateOverlay(in controller: LegacyAVPlayerViewController, context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) {
guard let hostController = controller.overlayViewController as? UIHostingController<Overlay> else {
let host = UIHostingController(rootView: overlay())
controller.overlayViewController = host
return
}
hostController.rootView = overlay()
}
private func makeAVPlayer(in controller: LegacyAVPlayerViewController, context: UIViewControllerRepresentableContext<LegacyVideoPlayer>) {
if isLooping {
let item = AVPlayerItem(url: url)
let player = AVQueuePlayer(playerItem: item)
let loopingPlayer = AVPlayerLooper(player: player, templateItem: item)
controller.videoGravity = AVLayerVideoGravity.resizeAspectFill
context.coordinator.loopingPlayer = loopingPlayer
controller.player = player
} else {
controller.player = AVPlayer(url: url)
}
controller.showsPlaybackControls = showsPlaybackControls
controller.onPlayerStatusChange = onTimeControlStatusChange
}
private func playIfNeeded(_ player: AVPlayer?) {
if isPlaying { player?.play() }
else { player?.pause() }
}
}
public class CustomPlayerCoordinator<Overlay: View>: NSObject, AVPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate {
let customPlayer: LegacyVideoPlayer<Overlay>
var loopingPlayer: AVPlayerLooper?
public init(customPlayer: LegacyVideoPlayer<Overlay>) {
self.customPlayer = customPlayer
super.init()
}
public func playerViewController(_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
completionHandler(true)
}
}
public extension LegacyVideoPlayer {
func play(_ isPlaying: Bool = true, isLooping: Bool = false) -> LegacyVideoPlayer {
LegacyVideoPlayer(overlay: overlay,
url: url,
onTimeControlStatusChange: onTimeControlStatusChange,
isPlaying: isPlaying,
isLooping: isLooping,
showsPlaybackControls: showsPlaybackControls)
}
func onTimeControlStatusChange(_ onTimeControlStatusChange: @escaping (AVPlayer.TimeControlStatus) -> Void) -> LegacyVideoPlayer {
LegacyVideoPlayer(overlay: overlay,
url: url,
onTimeControlStatusChange: onTimeControlStatusChange,
isPlaying: isPlaying,
isLooping: isLooping,
showsPlaybackControls: showsPlaybackControls)
}
func showingPlaybackControls(_ showsPlaybackControls: Bool = true) -> LegacyVideoPlayer {
LegacyVideoPlayer(overlay: overlay,
url: url,
onTimeControlStatusChange: onTimeControlStatusChange,
isPlaying: isPlaying,
isLooping: isLooping,
showsPlaybackControls: showsPlaybackControls)
}
}
extension LegacyVideoPlayer {
public init(url: URL) where Overlay == EmptyView {
self.init(url: url, overlay: { EmptyView() })
}
public init(url: URL, @ViewBuilder overlay: @escaping () -> Overlay) {
self.url = url
self.overlay = overlay
}
}
字符串
1条答案
按热度按时间ct3nt3jp1#
您的
LegacyAVPlayerViewController
类正在观察AVPlayer
的rate
property,但错误消息表明currentItem.videoComposition
属性存在问题。可能是
AVQueuePlayer
示例的修改方式不符合AVQueuePlayer
Key-Value Observing (KVO),导致了此崩溃。例如,参见Sasha Bondar中的“How to use Swift's Key-Value Observing (KVO)“。
尝试并确保对
player.currentItem
或其属性的任何更改都是以KVO兼容的方式进行的。这对于AVFoundation
classes来说可能很棘手,因为它们并不总是严格遵循KVO约定。另请参阅Jianyuan Chen中的“Key-Value Observing in the age of Swift“。因此,请确保删除
LegacyAVPlayerViewController
的deinit
方法中的所有观察者。由于您已经使rateObserver
无效,因此请确保在代码的其他地方没有添加其他观察者。(有点像“What is wrong with thisAVFoundation
KVO pattern for a video player [ref:AVPlayerLayer
,AVPlayerItem
,AVURLAsset
]?“)在使你的观察者无效之前,检查玩家是否真的在观察那个关键路径。
确保对
player.currentItem
或相关属性的任何修改都是以符合KVO的方式完成的。这可能需要在LegacyVideoPlayer
类中进行自定义处理。你的
deinit
方法应该是:字符串
由于
player?.observationInfo != nil
检查没有解决问题,请尝试检查LegacyAVPlayerViewController
中的KVO设置和拆除过程:包括确保观察者正确添加和删除,以及在视图控制器生命周期的适当时间添加和删除。有时,KVO相关的崩溃会发生,因为观察者被添加多次,但只删除一次。请确保观察者只添加一次。这可以通过设置标志或在添加前检查
rateObserver
是否为nil
来管理。型
在拆除时,在使观察者无效之前将
player
设置为nil
。这可以确保在视图控制器被释放之前停止所有与玩家相关的观察。仔细检查是否有任何其他代码部分(可能在LegacyAVPlayerViewController
之外)可能会向AVPlayer
或其属性添加观察者。确保所有与KVO相关的操作(添加、删除观察者)都在同一个线程上完成,最好是在主线程上,以避免竞态条件。