我有一个视频播放器视图,可以根据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
}
}