I am trying to implement a singleton class with AVPlayer in it. The key value observing throws exception. I think the object of this class is getting dealloced.
import Foundation
import UIKit
import AVKit
class Player: NSObject, ObservableObject {
var player : AVPlayer!
var playerController = AVPlayerViewController()
var presentingVC : UIViewController!
static let shared = Player()
private override init() { }
func play(urlString: String) {
let auth = Authentication()
let headers: [String: String] = ["x-auth-token" : auth.token]
let url = URL(string: urlString)
let asset = AVURLAsset(url: url!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
let playerItem = AVPlayerItem(asset: asset)
//let avPlayer = AVPlayer(url: url!)
player = AVPlayer(playerItem: playerItem)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: nil)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.status), options: [.new, .initial], context: nil)
let center = NotificationCenter()
center.addObserver(self, selector: Selector(("newErrorLogEntry")), name: .AVPlayerItemNewErrorLogEntry, object: player.currentItem)
center.addObserver(self, selector: Selector(("failedToPlayTillEnd")), name: .AVPlayerItemFailedToPlayToEndTime, object: player.currentItem)
playerController.player = player
presentingVC.present(playerController, animated: true) {
self.player.play()
}
}
func newErrorLogEntry(_ notification: Notification) {
guard let object = notification.object, let playerItem = object as? AVPlayerItem else {
return
}
guard let errorLog: AVPlayerItemErrorLog = playerItem.errorLog() else {
return
}
print("2")
NSLog("Error: \(errorLog)")
}
func failedToPlayToEndTime(_ notification: Notification) {
let error = notification.userInfo!["AVPlayerItemFailedToPlayToEndTimeErrorKey"]
print("3")
NSLog("Error: \(String(describing: error))")
DispatchQueue.main.async {
let alert = UIAlertController(title: "Playback error {}{}", message: "Unable to Play Channel {}{} \n", preferredStyle: UIAlertController.Style.alert)
self.playerController.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { (UIAlertAction) in
self.playerController.dismiss(animated: true, completion: nil)
print("Cancel")
}))
}
}
}
Calling from ViewController
class
Player.shared.presentingVC = self
Player.shared.play(urlString: url)
Following is the exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<VideoPlayer_v2.Player: 0x600001f84cc0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: status
Observed object: <AVPlayer: 0x600001dc1550>
Change: {
kind = 1;
new = 0;
}
Looks like AVPlayer.status message is received. But the object is already de-allocated. Correct me if I am wrong.
What is the best way to separate out AVPlayer
functions in a separate class other than UIViewController
class?
The reason for the exception is that you are adding observers inside your Player class, but you are not observing the values. To do that, add this method in side Player class then you handle the observed values as you need:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as AnyObject? === player {
if keyPath == "status" {
// Do something like this or whatever you need to do:
if player.status == .readyToPlay {
player.play()
}
} else if keyPath == "currentItem.status" {
// Do something
} else {
// Do something
}
}
}