I need to jump to particular time in audiofile right after it starts to play. So I use seekToTime method with completion handler
avPlayer.play()
...
avPlayer?.seekToTime(jumpTime, completionHandler: { isComplete in
    if isComplete {
        MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo![MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds((self.avPlayer!.currentItem?.currentTime())!)
    }
})
The problem is that it needs time to start playing file from the internet. And for some reason the version of seekToTime with completion handler crashes the app, because it's invoked before avPlayer started to play. The version without completion handler works fine.
Error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'AVPlayerItem cannot service a seek request with a completion handler until its status is AVPlayerItemStatusReadyToPlay.'
Is there a way to use a callback with avPlayer.play()?
Yes, there is a way.
You need to observe changes in player:
avPlayer?.addPeriodicTimeObserverForInterval(CMTime(value: 1, timescale: 3), queue: dispatch_get_main_queue()) { [weak self] time in
    self?.handleCallback(time)
}
And then you handle the state in the callback:
func handleCallback(time: CMTime) {    
   if avPlayer?.status == .ReadyToPlay {
       // place your seek logic here
   }
}
Please look at the Apple's answer for it: https://developer.apple.com/library/content/qa/qa1820/_index.html
    class MyClass {
    var isSeekInProgress = false
    let player = <#A valid player object #>
    var chaseTime = kCMTimeZero
    // your player.currentItem.status
    var playerCurrentItemStatus:AVPlayerItemStatus = .Unknown
    ...
    func stopPlayingAndSeekSmoothlyToTime(newChaseTime:CMTime)
    {
        player.pause()
        if CMTimeCompare(newChaseTime, chaseTime) != 0
        {
            chaseTime = newChaseTime;
            if !isSeekInProgress
            {
                trySeekToChaseTime()
            }
        }
    }
    func trySeekToChaseTime()
    {
        if playerCurrentItemStatus == .Unknown
        {
            // wait until item becomes ready (KVO player.currentItem.status)
        }
        else if playerCurrentItemStatus == .ReadyToPlay
        {
            actuallySeekToTime()
        }
    }
    func actuallySeekToTime()
    {
        isSeekInProgress = true
        let seekTimeInProgress = chaseTime
        player.seekToTime(seekTimeInProgress, toleranceBefore: kCMTimeZero,
                toleranceAfter: kCMTimeZero, completionHandler:
        { (isFinished:Bool) -> Void in
            if CMTimeCompare(seekTimeInProgress, chaseTime) == 0
            {
                isSeekInProgress = false
            }
            else
            {
                trySeekToChaseTime()
            }
        })
    }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With