Swift iOS -How to Access Error from AVPlayerItemStatus? - ios

I'm using an AVPlayer to play a video url. I followed Apple's code to play the video and handle errors:
Inside the section that says Respond to A State Change there is a switch statement that has a .failed case and the commented out code says: // Player item failed. See error. I've had this run a couple of times by putting a break point there.
The problem is I don't see anything like a variable with a NSError type that would give me an option to actually print out what the error is. How can I find out what the .failed the error is?
Observe the Player's State:
let url: URL = // Asset URL
var asset: AVAsset!
var player: AVPlayer!
var playerItem: AVPlayerItem!
// Key-value observing context
private var playerItemContext = 0
let requiredAssetKeys = [
"playable",
"hasProtectedContent"
]
func prepareToPlay() {
// Create the asset to play
asset = AVAsset(url: url)
// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
playerItem = AVPlayerItem(asset: asset,
automaticallyLoadedAssetKeys: requiredAssetKeys)
// Register as an observer of the player item's status property
playerItem.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
// Associate the player item with the player
player = AVPlayer(playerItem: playerItem)
}
Respond to a State Change
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItemStatus
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
// Switch over status value
switch status {
case .readyToPlay:
// Player item is ready to play.
case .failed:
// Player item failed. See error.
case .unknown:
// Player item is not yet ready.
}
}
}

if status is .failed then you can access the error from AVPlayer itself like this.
switch status {
case .readyToPlay:
// Player item is ready to play.
case .failed:
handleErrorWithMessage(player.currentItem?.error?.localizedDescription, error:player.currentItem?.error)
case .unknown:
// Player item is not yet ready.
}
Find below function.
func handleErrorWithMessage(_ message: String?, error: Error? = nil) {
print("Error occured with message: \(message), error: \(error).")
}
Hope this helps.

Related

Why does observer for status change of AVAsset not work?

I implemented the following code from Apple. It is meant to observe for a change in the status of a playerItem. The problem is that for some reason it does not work. The observe function does not run when ready.
All relevant code is below:
func preloadVideo(media: Media) {
//setup code, and then:
media.playerItem1!.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
}
Observe method:
private var playerItemContext = 0
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
print("jdslfhjkfdhaldfahjkflhajfldashkjfdshkjlas")
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath, of: object,change: change, context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItem.Status
// Get the status change from the change dictionary
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
print("dsfdgddgdggdgdgdgddsafdsaf434fdfdg433444343")
// Switch over the status
switch status {
case .readyToPlay:
//Stop animation, and go to p3
print("dsfdsafdsaf434433444343")
if goToVideo {
print("Runinfdsh sahkbdsfhbsadfbadsfl")
goToP3()
}
break
// Player item is ready to play.
case .failed: break
// Player item failed. See error.
case .unknown: break
// Player item is not yet ready.
#unknown default: break
//fatal error
}
}
}
This method will run first:
#objc func viewHandleTap(_ sender: UITapGestureRecognizer) {
if selectedPost?.interimMedia.first?.imageURLString != nil || selectedPost?.interimMedia.first!.playerQueue?.status.rawValue == 1 {
//do something
} else {
//status is 0 (video has not loaded yet)//load animation
//this runs...
goToVideo = true
}
}
How can I make it work? Currently nothing in the observe method is printing.
If there is another method to accomplish this go ahead and post as answer.
Try by updating addObserver options to [.old, .new, .initial, .prior].

iOS - Getting current AVPlayer(Item) state

I'm looking for a definitive answer how to get current AVPlayer/AVPlayerItem state on iOS 9 onwards. To put things simple, let them be Google's ExoPlayer style states:
idle (no media or error)
buffering (video is not actually playing/advancing and waiting for
more data)
playing (video is actually playing/advancing)
completed (video finished playing to the end)
Note, that at this point I'm not looking for a way to track state changes (via notifications, KVO-observing or other means), just a state at current point of time. Consider the following pseudo-code:
typedef enum : NSUInteger {
PlayerStateIdle,
PlayerStateBuffering,
PlayerStatePlaying,
PlayerStateCompleted
} PlayerState;
+ (PlayerState)resolvePlayerState:(AVPlayer*)player {
// Magic code here
}
Walls, I've been banging my head against so far:
timeControlStatus available from iOS 10 onwards
playbackBufferEmpty is always true
playbackBufferFull is always false
loadedTimeRanges may look promising at first glance, but there's neither an indication of how much time must be pre-buffered for playback nor guarantee that currentTime being on the edge of loaded time range is a stall
According to the documentation
You can use Key-value observing to observe these state changes as they
occur. One of the most important player item properties to observe is
its status. The status indicates if the item is ready for playback and
generally available for use.
To setup observing:
func prepareToPlay() {
let url = <#Asset URL#>
// Create asset to be played
asset = AVAsset(url: url)
let assetKeys = [
"playable",
"hasProtectedContent"
]
// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
playerItem = AVPlayerItem(asset: asset,
automaticallyLoadedAssetKeys: assetKeys)
// Register as an observer of the player item's status property
playerItem.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
// Associate the player item with the player
player = AVPlayer(playerItem: playerItem)
}
To handle:
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItemStatus
// Get the status change from the change dictionary
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
// Switch over the status
switch status {
case .readyToPlay:
// Player item is ready to play.
case .failed:
// Player item failed. See error.
case .unknown:
// Player item is not yet ready.
}
}
}
Not sure what your problem is.
You can just access player.status or player.timeControlStatus and return a result that matches your ENUM
Is that what you mean?

How to play Data type video using AVPlayer?

I am trying to play a video after I download from remote using following code.
let player = AVPlayer(url: URL(dataRepresentation: VideoService.instance.videoInfoArray[0].video, relativeTo: nil)!)
let playerController = AVPlayerViewController()
playerController.player = player
present(playerController, animated: true) {
player.play()
}
My VideoService class is a singleton class and video variable type is Data(). However when I try to play video with below code it is not working. How can I play video using data representative?
According to URL class documentation the initializer init?(dataRepresentation:relativeTo:isAbsolute:) takes Data argument, yes, but that data is supposed to be an ASCII representation of an URL string, not the actual data you want to play.
What you need to do is save your video data to a file and use the file's URL to initialize AVPlayer
guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)?.first else { return }
url.appendPathComponent("video.mpeg") // or whatever extension the video is
VideoService.instance.videoInfoArray[0].video.write(to: url) // assuming video is of Data type
let player = AVPlayer(url: url)
// other stuff with the player
To determine the reason why the player can't play your file, use key-value observing of player's status property. If you observe a change to AVPlayer.Status.failed, then you can check the player's error property to see the localized reason and error code.
var playerStatusContext = 0
player.addObserver(player, forKeyPath: "status", options: [.new, .initial], context: &playerStatusContext)
// (...)
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
guard context == &playerStatusContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayer.status) {
let status: AVPlayer.Status
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayer.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
if status == .failed {
print(player.error)
}
}
}

AVPlayer too long and with delay to readyToPlay and play server music in swift

I tried to buffer and immediately play remote url audio with swift language.
but problem is with long time to readyToPlay case and play.
for example a sound url takes about 12 to 15 second to run.
this is my code :
var asset: AVAsset!
var player: AVPlayer!
var playerItem: AVPlayerItem!
private var playerItemContext = 0
let requiredAssetKeys = [ "playable","hasProtectedContent"]
let url = URL(string: "http://sound_link.mp3")!
asset = AVAsset(url: url)
playerItem = AVPlayerItem(asset: asset,
automaticallyLoadedAssetKeys: requiredAssetKeys)
playerItem.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
player = AVPlayer(playerItem: playerItem)
that according to this (ExploringAVFoundation) documentation've done that
and for handle that player is ready to player to play i use observeValue func :
override func observeValue(forKeyPath keyPath: String?,of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItemStatus
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
// Switch over status value
switch status {
case .readyToPlay:
print("readyToPlay \(status.rawValue)")
player.play() // here play remote sound
case .failed:
print("readyToPlay \(status.rawValue)")
case .unknown:
print("failed \(status.rawValue)")
}
}
}
and this is returned log :
2017-02-08 13:44:00.626036 [15144:3747850] [aqme] 255: AQDefaultDevice (1): skipping input stream 0 0 0x0
readyToPlay 1
2017-02-08 13:44:02.631182 [15144:3747850] [aqme] 255: AQDefaultDevice (173): skipping input stream 0 0 0x0
in above log , take 4 sec to appear readyToPlay 1 then take 10 sec to play sound
sever speed is good and i tried to play server sound in Android and max time to buffer and play sound is about 3 sec.(in Android application) but in IOS buffering and play sound totally , totally take about 15 sec!
Thanks for your attention
Try to use this:
player.automaticallyWaitsToMinimizeStalling = false
Try to instantiate your avPlayerItem with URL and then fetch other assets in global thread , I think the problems happens because avPlayer is trying to fetch assets in main UI thread
I had the same problem when I was trying to read subtitle options from avAssets
it took 5-6 seconds more to load the movie in my player.
This is how I solved my problem (in Swift 2), I hope it helps:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let avMediaSelectionGroup : AVMediaSelectionGroup? = self.container.player.currentItem?.asset.mediaSelectionGroupForMediaCharacteristic(AVMediaCharacteristicLegible)
dispatch_async(dispatch_get_main_queue(), {
if (avMediaSelectionGroup != nil && (avMediaSelectionGroup!.options.count != 0) && avMediaSelectionGroup?.options[0].valueForKey("title") != nil)
{
// show hide subtitle button
}
})
})

Detect when AVPlayer is playing

How do you detect when an AVPlayer is playing?. There appears to be a slight delay between when the play() function is called and the video actually plays.
For Video:
AVPlayer have an argument called rate (Float), if the rate is greater than 0.0, there a video that currently playing.
You can check the if the rate is !=0: (the rate can be negative if the player goes backwards)
if vidPlayer != nil && vidPlayer.rate != 0 {
println("playing")
}
AVPlayer class reference
As far as I know, I agree with you that there is a slight delay between when the play() function is called and the video actually plays (In another word, the time that the first frame of the video has been rendered). The delay depends on some criteria such as video types (VOD or live streaming), the network condition, ... However, fortunately, we are able to know whenever the first frame of the video rendered, I mean exactly when the video actually plays.
By observing the status of the current AVPlayerItem and whenever it is AVPlayerItemStatusReadyToPlay, that should be the first frame has been rendered.
[self.playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context {
if([self.playerItem status] == AVPlayerStatusReadyToPlay){
NSLog(#"The video actually plays")
}
}
By the way, there is another solution where we observe readyForDisplay status of AVPlayerLayer, it also indicates whenever the video rendered. However, this solution has a drawback as mentioned in Apple document
/*!
#property readyForDisplay
#abstract Boolean indicating that the first video frame has been made ready for display for the current item of the associated AVPlayer.
#discusssion Use this property as an indicator of when best to show or animate-in an AVPlayerLayer into view.
An AVPlayerLayer may be displayed, or made visible, while this propoerty is NO, however the layer will not have any
user-visible content until the value becomes YES.
This property remains NO for an AVPlayer currentItem whose AVAsset contains no enabled video tracks.
*/
#property(nonatomic, readonly, getter=isReadyForDisplay) BOOL readyForDisplay;
Here is the sample code
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
[self.playerLayer addObserver:self forKeyPath:#"readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context {
if([self.playerLayer isReadyForDisplay]){
NSLog(#"Ready to display");
}
}
Thereotically, [self.playerLayer isReadyForDisplay] should return YES, however, as the document, it is not guaranted.
I hope this would be helpful.
Swift 4
Method 1
var rateObserver: NSKeyValueObservation?
self.rateObserver = myPlayer.observe(\.rate, options: [.new, .old], changeHandler: { (player, change) in
if player.rate == 1 {
print("Playing")
}else{
print("Stop")
}
})
// Later You Can Remove Observer
self.rateObserver?.invalidate()
Method 2
myPlayer.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions(rawValue: NSKeyValueObservingOptions.new.rawValue | NSKeyValueObservingOptions.old.rawValue), context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" {
if player.rate == 1 {
print("Playing")
}else{
print("Stop")
}
}
}
There are two things you might want to detect:
AVPlayerLayer's isReadyForDisplay is when the first frame has been received.
AVPlayerItem's readyToPlay is when the video actually starts playing.
In order to check both status you can use observability.
You're going to have three objects defined in your class:
var player: AVPlayer?
var playerItem: AVPlayerItem?
var playerLayer: AVPlayerLayer?
The player implementation is going to be like the following:
guard let videoURL = URL(string: "<videoPath>") else {
return
}
asset = AVAsset(url: videoURL)
guard let asset = asset else {
return
}
playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: requiredAssetKeys)
guard let playerItem = playerItem else {
return
}
playerItem.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
player = AVPlayer(playerItem: playerItem)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.addObserver(self,
forKeyPath: #keyPath(AVPlayerLayer.isReadyForDisplay),
options: [.old, .new],
context: &playerLayerContext)
avpController.player = player
avpController.view.frame.size.height = playerView.frame.size.height
avpController.view.frame.size.width = playerView.frame.size.width
playerView.addSubview(avpController.view)
avpController.player?.play()
Here, the context are either simple integers or enum.
You can handle the events by overriding the observeValue method.
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard (context == &playerItemContext) ||
(context == &playerLayerContext) else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItem.Status
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
switch status {
case .readyToPlay:
print("Ready to play")
case .failed:
navigationController?.popViewController(animated: true)
case .unknown:
print("Unknown")
#unknown default:
print("Unknown default")
}
} else if keyPath == #keyPath(AVPlayerLayer.isReadyForDisplay) {
if playerLayer?.isReadyForDisplay ?? false {
print("Ready to display")
}
}
}
Don't forget to remove the observers.
deinit {
playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
playerLayer?.removeObserver(self, forKeyPath: #keyPath(AVPlayerLayer.isReadyForDisplay))
}

Resources