我有一个视频播放器视图,可以根据URL播放视频.当我离开视频时,我会崩溃,并打印下面的错误.在我的deinit我试图无效的观察者,但没有工作.我错过了我的deinit函数的一个步骤,导致这个错误.

由于未捕获的异常"NSInternalInconsistencyException"而终止应用程序,原因:"无法从AVQuePlayer 0x28079dfc 0中删除键路径"currentItem.videoComposition"的观察者NSKeyValueObservance 0x2809113b0,很可能是因为键"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
   }
}

推荐答案

您的LegacyAVPlayerViewController类正在观察AVPlayerrate property,但错误消息表明currentItem.videoComposition属性有问题.

It is possible that the AVQueuePlayer instance is being modified in a way that is not compliant with AVQueuePlayer Key-Value Observing (KVO), leading to this crash.
See for instance "How to use Swift's Key-Value Observing (KVO)" from Sasha Bondar.

try 并确保对player.currentItem或其属性的任何更改都是以符合KVO的方式进行的.对于AVFoundation classes人来说,这可能是棘手的,因为他们并不总是严格遵守KVO的约定.参见Jianyuan Chen中的"Key-Value Observing in the age of Swift".

因此,请确保删除LegacyAVPlayerViewControllerdeinit方法中的所有观察者.由于您已经使rateObserver无效,请确保没有其他您可能已经在代码中的其他位置添加的观察者.(有点像"What is wrong with this AVFoundation KVO pattern for a video player [ref: AVPlayerLayer, AVPlayerItem, AVURLAsset]?")

在使你的观察者无效之前,判断玩家是否真的在观察那个关键路径.

确保以符合KVO的方式完成对player.currentItem或相关属性的任何修改.这可能需要在您的LegacyVideoPlayer类中进行自定义处理.

您的deinit方法将是:

deinit {
    if player?.observationInfo != nil {
        rateObserver?.invalidate()
    }
}

由于player?.observationInfo != nil判断没有解决问题,请try 并判断LegacyAVPlayerViewController中的KVO设置和拆卸过程:这包括确保在视图控制器生命周期的适当时间正确添加和删除观察器.

有时,KVO相关的崩溃发生是因为观察者被多次添加,但只被删除一次.确保观察者只添加一次.这可以通过设置标志或在添加之前判断rateObserver是否为nil来管理.

deinit {
    rateObserver?.invalidate()
}

// In your observer setup
if rateObserver == nil {
    rateObserver = player?.observe(\AVPlayer.rate, options: [.new], changeHandler: rateHandler(_:change:))
}

拆卸时,将player设置为nil,然后使观察者无效.这确保在解除分配视图控制器之前停止所有与玩家相关的观察.仔细判断您的代码中是否有任何其他部分(可能在LegacyAVPlayerViewController之外)正在向AVPlayer或其属性添加观察器.

确保所有与KVO相关的操作(添加、删除观察者)都在同一个线程上完成,最好是在主线程上,以避免竞态条件.

Ios相关问答推荐

[NSJSONSerializationdataWithJSONObserver:选项:错误:]:SON写入中无效的顶级类型

在Android和iOS上从后台恢复应用程序后,inappwebview上出现白屏?

不使用iOS 17修改器修改SWIFT用户界面视图

Flutter 翼项目.升级到XCode 15.0.1`pod install`抛出错误FlutterMacOS.podspec.json 3.13.2的ParserError

swift|在具有自定义单元格的多个TableView中没有任何内容

在 SwiftUI 中以编程方式插入内嵌图像

错误地提取问题的答案选项

react 本机 |错误:无法解析 node_modules/react-native/index.js 中的模块

使用按位|带有布尔操作数

为什么@FetchRequest 在删除时不更新和重绘视图?

为什么我不能让 passthrough 科目为 combine 工作?

iOS,Swift 5,AppDelegate open url / .onOpenUrl - 如何找到调用我的自定义方案的调用应用程序

如何在保持纵横比的同时zoom UIView 的内容以适应目标矩形?

UICollectionView 添加 UICollectionCell

iOS:应用程序在安装应用程序时未征求用户许可.每次都获得 kCLAuthorizationStatusNotDetermined - Objective-c & Swift

刷新由 Xcode 7 管理的团队配置文件中的设备?

Swift:调用中缺少参数标签xxx

Swift中类的初始化类方法?

如何在 iOS 中判断暗模式?

判断密钥是否存在于 NSDictionary 中