Building a video player to stream content is absolutely amazing, however, your customer experience is only as good as how accurate the information that is presented to them. I have had challenges putting accurate data in front of the user, and here is how I overcame this challenge when using AVPlayer in iOS apps.
The problem
If you are developing a custom video player skin, chances you have to somehow show the overall duration of the current playing media. And using AVPlayerItem’s duration is the best way of doing this. And this is how duration is accessed.
(lldb) po playerItem!.duration.seconds
nan
However, when done this way after we start playing, we get this “nan” 🙁
What the… NaN
The problem here is we are trying to access the duration before AVPlayer gets a chance to download the content (or manifest if using a streaming protocol like HLS). And the solution is to wait for the right time.
So, when’s the right time?
Simple answer. When the content is ready. Or otherwise when AVPlayerItem.Status.readyToPlay
. Let’s see it in code
let streamURL = "http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8"
playerItem = AVPlayerItem(url: URL(string: streamURL)!)
player = AVPlayer(playerItem: playerItem)
playerItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: nil)
and then observing like this:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(AVPlayerItem.status), let statusNumber = change?[.newKey] as? NSNumber {
switch statusNumber.intValue {
case AVPlayerItem.Status.readyToPlay.rawValue:
let durationInSeconds = playerItem?.asset.duration.seconds ?? 0
print("Ready to play. Duration (in seconds): \(durationInSeconds)")
default: break
}
}
}
When you take this approach, make sure to remove the observer when you close the player or change playerItem inside the player
playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
It’s easier with Swift 4
With Swift 4, listening to changes like these are even easier. This is how:
observation = playerItem?.observe(\AVPlayerItem.status, changeHandler: { observedPlayerItem, change in
if (observedPlayerItem.status == AVPlayerItem.Status.readyToPlay) {
print("Current stream duration \(observedPlayerItem.duration.seconds)")
}
})
It is easy to remove this observer, just assign it to nil
. Here’s the link to the WWDC video
And that’s it! Happy streaming.