离开视频会导致 AVPlayer NSInternalInconsistencyException 崩溃

问题描述 投票:0回答:1

我有一个视频播放器视图,可以根据 URL 播放视频。当我离开视频时,我遇到崩溃并打印以下错误。在我的 deinit 中,我尝试使观察者无效,但这没有用。我的 deinit 函数中缺少导致此错误的步骤。

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无法从 中删除键路径“currentItem.videoComposition”的观察者,很可能是因为键“currentItem”的值已更改,而没有发送适当的 KVO 通知。检查 AVQueuePlayer 类的 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
   }
}
ios swift swiftui video avplayer
1个回答
0
投票

我遇到了类似的问题,这些是我的发现:

我对崩溃调用堆栈进行了更深入的分析。

Fatal Exception: NSInternalInconsistencyException
0  CoreFoundation                 0x83f20 __exceptionPreprocess
1  libobjc.A.dylib                0x16018 objc_exception_throw
2  Foundation                     0x13c6ac -[NSKeyValueNestedProperty object:didRemoveObservance:recurse:]
3  Foundation                     0x13cf60 -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:]
4  Foundation                     0x13ce00 -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:]
5  Foundation                     0x13c574 -[NSKeyValueNestedProperty object:didRemoveObservance:recurse:]
6  Foundation                     0x13cf60 -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:]
7  Foundation                     0x13ce00 -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:]
8  Foundation                     0x13cd18 -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:context:]
9  AVKit                          0xa04e8 -[AVProxyKVOObserver stopObserving]
10 AVKit                          0x37e30 -[AVObservationController _stopAllObservation]
11 AVKit                          0x37d64 -[AVObservationController stopAllObservation]
12 AVKit                          0x9f63c -[AVVideoFrameVisualAnalyzer _updateObserversIfNeeded]
13 AVKit                          0x9f7cc -[AVVideoFrameVisualAnalyzer _updateActualEnabledStateIfNeeded]
14 AVKit                          0x4fa4 __105-[AVObservationController startObserving:keyPaths:includeInitialValue:includeChanges:observationHandler:]_block_invoke
15 AVKit                          0x4e80 -[AVProxyKVOObserver _handleValueChangeForKeyPath:ofObject:oldValue:newValue:context:]
16 AVKit                          0xa4a0 -[AVProxyKVOObserver observeValueForKeyPath:ofObject:change:context:]
17 Foundation                     0x1a684 NSKeyValueNotifyObserver
18 Foundation                     0x1a378 NSKeyValueDidChange
19 Foundation                     0x147c58 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
20 Foundation                     0x1478ac -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
21 Foundation                     0x146e40 _NSSetObjectValueAndNotify
22 AVKit                          0x1e824 -[AVPlayerController _prepareAssetForInspectionIfNeeded]
23 AVKit                          0x4fa4 __105-[AVObservationController startObserving:keyPaths:includeInitialValue:includeChanges:observationHandler:]_block_invoke
24 AVKit                          0x4e80 -[AVProxyKVOObserver _handleValueChangeForKeyPath:ofObject:oldValue:newValue:context:]
25 AVKit                          0xa4a0 -[AVProxyKVOObserver observeValueForKeyPath:ofObject:change:context:]
26 Foundation                     0x1a684 NSKeyValueNotifyObserver
27 Foundation                     0x1a378 NSKeyValueDidChange
28 Foundation                     0x19fa4 NSKeyValueDidChangeWithPerThreadPendingNotifications
29 Foundation                     0x1361a0 -[NSKeyValueObservance observeValueForKeyPath:ofObject:change:context:]
30 Foundation                     0x1a684 NSKeyValueNotifyObserver
31 Foundation                     0x1a378 NSKeyValueDidChange
32 Foundation                     0x19fa4 NSKeyValueDidChangeWithPerThreadPendingNotifications
33 AVFCore                        0x34b7c __109-[AVPlayer _runOnIvarAccessQueueOperationThatMayChangeCurrentItemWithPreflightBlock:modificationBlock:error:]_block_invoke_2
34 AVFCore                        0x47578 -[AVSerializedMostlySynchronousReentrantBlockScheduler scheduleBlock:]
35 AVFCore                        0x4720c -[AVPlayer _runOnIvarAccessQueueOperationThatMayChangeCurrentItemWithPreflightBlock:modificationBlock:error:]
36 AVFCore                        0x7a534 -[AVPlayer _removeItem:]
37 AVFCore                        0x7951c -[AVPlayer _advanceCurrentItemAccordingToFigPlaybackItem:]
38 AVFCore                        0x38c8 __avplayer_fpNotificationCallback_block_invoke
39 libdispatch.dylib              0x213c _dispatch_call_block_and_release
40 libdispatch.dylib              0x3dd4 _dispatch_client_callout
41 libdispatch.dylib              0x125a4 _dispatch_main_queue_drain
42 libdispatch.dylib              0x121b8 _dispatch_main_queue_callback_4CF
43 CoreFoundation                 0x56710 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
44 CoreFoundation                 0x53914 __CFRunLoopRun
45 CoreFoundation                 0x52cd8 CFRunLoopRunSpecific
46 GraphicsServices               0x11a8 GSEventRunModal
47 UIKitCore                      0x40a90c -[UIApplication _run]
48 UIKitCore                      0x4be9d0 UIApplicationMain

我看到在堆栈的第 14 帧,AVVideoFrameVisualAnalyzer 正在执行与观察者和侦听器相关的操作。

我的猜测是,这与崩溃的根本原因有关,很可能是 AVPlayer 中的错误。

AVVideoFrameVisualAnalyzer 与一项新功能(在 iOS 16 中引入)相关,其中播放器视图在您暂停媒体播放时尝试查找对象、文本和人物。如果它找到一个对象,用户可以通过长按来显示上下文菜单来与其交互。

幸运的是,可以使用 allowsVideoFrameAnalysis 属性禁用此新功能(默认启用)。

在我的用例中,我只需将 allowedVideoFrameAnalysis 设置为 false 即可完全避免崩溃

if #available(iOS 16.0, *) {
   playerViewController.allowsVideoFrameAnalysis = false
} 

当然,如果您对这个全新功能不感兴趣,这只是一个很好的解决方案。我不是:-)

© www.soinside.com 2019 - 2024. All rights reserved.