iOS 11 AVPlayer/mp3 currentTime not accurate

Viewed 899

I am developing a karaoke app which consist of animation and playback song. The most important thing here is very precise synchronization between both.

I implemented it few months ago and on iOS 9/10 everything was working great. We had a problem with Android app (ExoPlayer) because there were too big delay between playback and animation. Finally we found out that the problem was caused by wrong song format (mp3) - which was explained here: https://github.com/google/ExoPlayer/issues/3233. We fixed it by packing song into mp4 format (on iOS there is still mp3 used).

I use AVPlayer with HLS

self.player = AVPlayer(url: url)

//
var currentTime_: Double {
    if let player = self.player {
        return player.currentTime().seconds
    }

    return 0
}

Everything was ok until iOS 11 release. On iOS 11 there is the same desynchronization (player.currentTime is not accurate) that was on Android. What's interesting is that even app that was build on iOS 10 SDK and is available on AppStore doesn't work properly on iOS 11 - but it still works great on iOS 10.

Nothing on server side was changed. So Apple had to change something in decoding/buffering. Right now we are working on changing audio format/decoding but still I am curious - why that has happened? Anyone encountered something similar?

Regards

2 Answers

You need use this option when create AVURLAsset

let asset = AVURLAsset(url: url, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])

If you not use this option, AVPlayer measures mp3 audio duration by file length, so it has small error to get duration.

As woosiki pointed it out, you need to use the AVURLAsset. Just keep in mind that loading of large files will take longer due to the pre-calculation of the precise duration. You may want to put a loader indicator.

  func prepareAudioFile(with track: Track) {
    
    guard let url = URL(string: track.streamURL) else {
      return
    }
        
    let asset = AVURLAsset(url: url,
                           options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
    
    let playerItem = AVPlayerItem(asset: asset)

    player = AVPlayer(playerItem: playerItem)
    observePlayerProgress()
  }

  private func observePlayerProgress() {

    let interval = CMTimeMake(value: 1, timescale: 5)
    
    player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
      
      if let track = self?.player.currentItem, track.status == .readyToPlay {
        // Once the loading is finished this callback will return
        // Here you can calculate and set duration and elapsed time (track.duration, time.seconds)
      }
    }
  }

Related