How to get media duration from your iOS video player (AVPlayer)

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.

How useful was this post?

Click on a star to rate it!

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?