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

Hi there. How are you doing?
I'm [hiroakit](https://github.com/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.

```swift
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

- [Error Handling Best Practices for HTTP Live Streaming - WWDC 2017 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2017/514/)
- [Technical Note TN2436: Debugging HTTP Live Streaming](https://developer.apple.com/library/content/technotes/tn2436/_index.html)
- [AVError.Code - AVError | Apple Developer Documentation](https://developer.apple.com/documentation/avfoundation/averror.code)