If you ever worked with streaming audio on iOS you definitely used AVAudioPlayer or AVPlayer as one of the low-hanging and robust options provided by Apple.
Outside of the stellar performance and versatility the problem with both of them is a weak delegation system, especially for medium/pro use. It's true that you can go far with AVPlayer and even use its basic status changes to react to its behavior but they have few limitations:
- You actually have to work with them via KVO (Key-Value Observation) which is not ideal
- KVO still doesn't give you answers to everything you need
I had this basic need for a while: do some stuff in the app once the AVPlayer starts playing music from a stream. KVO'ing 'status', 'rate' and 'currentItem.status' was advised everywhere but none of them worked properly. And relying on AVPlayer's 'readyToPlay' status was incorrect as well - the player changes its status to it as soon as possible and not when actually starting playing the stream.
Thankfully I got to know about another key to look after, the 'currentItem.loadedTimeRanges'. And here's how you use it:
First, you add the observer (and then don't forget to remove it at the end of the player's lifecycle!):
avPlayer.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
And then you just observe it!
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as? AVPlayer === avPlayer && keyPath == "currentItem.loadedTimeRanges" {
if let timeRanges = avPlayer.currentItem?.loadedTimeRanges, timeRanges.count > 0 {
let timeRange = timeRanges.first as! CMTimeRange
let currentBufferDuration = CMTimeGetSeconds(CMTimeAdd(timeRange.start, timeRange.duration))
let duration = avPlayer.currentItem!.asset.duration
let seconds = CMTimeGetSeconds(duration)
if currentBufferDuration > 2 || currentBufferDuration == seconds {
delegate.startedPlayingStream()
}
}
}
Next the player calls its delegate when the stream actually started playing. Neat! 🙂