Help us understand the problem. What is going on with this article?

How to handle error which was detected by AVPlayer that get video content from HLS

More than 3 years have passed since last update.

Hi there. How are you doing?
I'm hiroakit that is iOS/tvOS Engineer.

This letter introduces you to the basic error handling of AVPlayer.

Introduction

AVPlayer will provide a video experience to your end-user.
And it may get some error from server interaction.
So it give us to issues below:

  • What kind of errors do AVPlayer catch?
  • What should app do to handle error which was catched by AVPlayer?

Then I looked up these issues on Apple's documents.

What kind of errors do AVPlayer catch?

AVPlayer will get these errors:

  • AVFoundationErrorDomain
  • CoreMediaErrorDomain

AVFoundationErrorDomain

AVPlayer will get AVFoundationErrorDomain errors when failures occur.
via: https://developer.apple.com/videos/play/wwdc2017/514/?time=875

Type Description AVError
Network errors HTTP 4xx errors, HTTP 5xx errors, TCP/IP, DNS errors AVErrorContentIsUnavailable, AVErrorNoLongerPlayable
Timeouts Master playlist, media playlist, media file, keys timeout AVErrorContentIsUnavailable, AVErrorNoLongerPlayable
Format errors Playlist format error, Key format error, Session data format error AVErrorFailedToParse
Live playlist update errors Must update live playlist in time as per HLS Spec AVErrorContentNotUpdated

The above AVFoundationErrorDomain error has assigned error code by Apple.
It is below:

Error Code
AVErrorContentIsUnavailable -11863
AVErrorNoLongerPlayable -11867
AVErrorFailedToParse -11853
AVErrorContentNotUpdated -11866

Example is here.

Error Domain=AVFoundationErrorDomain Code=-11867 "See -[AVPlayerItem errorLog] for 2 events" UserInfo={NSLocalizedDescription=Playback Stopped, NSUnderlyingError=0x60c000444b90 {Error Domain=CoreMediaErrorDomain Code=-12880 "Can not proceed after removing variants" UserInfo={NSDescription=Can not proceed after removing variants}}, NSDebugDescription=See -[AVPlayerItem errorLog] for 2 events, NSLocalizedFailureReason=Could not download required resources.})

AVPlayer will get to detect these errors at the start of playback.
If you handle AVFoundationErrorDomain errors, Your app should observe AVPlayer.status and AVPlayer.currentItem.status

CoreMediaErrorDomain

Your app receive errors from AVPlayerItemNewErrorLogEntry notification.
The notification has CoreMediaErrorDomain error.

Example is here.

Error Domain=CoreMediaErrorDomain Code=-12642 "Playlist parse error" (See -[AVPlayerItem errorLog] for 2 events) UserInfo={NSDescription=Playlist parse error, NSDebugDescription=See -[AVPlayerItem errorLog] for 2 events})

AVPlayer will catch CoreMediaErrorDomain errors while playback video.

Expected Failures and Error Codes

AVPlayer expect these error code when a failure occurs on HTTP.
via: https://developer.apple.com/videos/play/wwdc2017/514/?time=218

Failure Error Code
Authentication failures 401 Unauthorized
Client doesn’t have permissions 403 Forbidden
Resource unavailable but may become available in the future 404 Not Found
Resource unavailable and will never become available in the future 410 Gone
Internal server errors 500 Internal Server Error
Invalid response from gateway 502 Bad Gateway
Server unavailable 503 Service Unavailable
Gateway timeouts 504 Gateway Time-Out

When AVPlayer get these error codes, it exec the recovery below:

  • 401, 403, 404: Retry
  • 410, 500, 502, 503, 504: Switch other variants.

iOS 11 catch temporary resource/server unavailability with EXT-X-GAP tag in Playlist.
It is unverified I wish to Write this feature on next letter.

What should app do to handle error which was catched by AVPlayer?

Get error via these methods below:

  • Watch AVPlayer.status and AVPlayerItem.status
  • Listen to these notifications
    • NSNotification.Name.AVPlayerItemFailedToPlayToEndTime
    • NSNotification.Name.AVPlayerItemNewErrorLogEntry

Example is here.

func foo() {
    // Get AVPlayerItem
    let url: URL // something url
    let asset = AVURLAsset(URL: url)
    let item = AVPlayerItem(asset: asset)

    // Get AVPlayer
    self.player = AVPlayer(playerItem: item)

    // Add observer for AVPlayer status and AVPlayerItem status
    self.player.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: nil)
    self.player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.status), options:[.new, .initial], context: nil)

    // Watch notifications
    let center = NotificationCenter.default
    center.addObserver(self, selector:"newErrorLogEntry:", name: .AVPlayerItemNewErrorLogEntry, object: player.currentItem)
    center.addObserver(self, selector:"failedToPlayToEndTime:", name: .AVPlayerItemFailedToPlayToEndTime, object: player.currentItem)
}

// Observe If AVPlayerItem.status Changed to Fail
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let player: AVPlayer = object as? AVPlayer && keyPath == #keyPath(AVPlayer.currentItem.status) {
        let newStatus: AVPlayerItemStatus
        if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber {
            newStatus = AVPlayerItemStatus(rawValue: newStatusAsNumber.intValue)!
        } else {
            newStatus = .unknown 
        }
        if newStatus == .failed {            
            NSLog("Error: \(String(describing: self.player?.currentItem?.error?.localizedDescription)), error: \(String(describing: self.player?.currentItem?.error))")
        }
    }   
}

// Getting error from Notification payload
func newErrorLogEntry(_ notification: Notification) {
    guard let object = notification.object, let playerItem = object as? AVPlayerItem else {
        return
    }
    guard let errorLog: AVPlayerItemErrorLog = playerItem.errorLog() else {
        return
    }
    NSLog("Error: \(errorLog)")
}

func failedToPlayToEndTime(_ notification: Notification) {
    let error = notification.userInfo![AVPlayerItemFailedToPlayToEndTimeErrorKey]
    NSLog("Error: \(error?.localizedDescription), error: \(error)")
}

Conclusion

In this letter, I described how to handle errors by the approach below:

  • Watch AVPlayer.status and AVPlayerItem.status
  • Listen to these notifications
    • NSNotification.Name.AVPlayerItemFailedToPlayToEndTime
    • NSNotification.Name.AVPlayerItemNewErrorLogEntry

Perhaps we can get how to reduce errors or more good error handling by taking part in the discussion from the server side (e.g, EXT-X-GAP, Fail. So error handling is debatable. It will take good for client side too.
Finally, I think that i/we can still improve the video user experience, so any ideas are welcome. Then we can do better together.

References

hiroakit
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away