2

We have an app which plays a long mp3 file (1 hour long). We want to be able to play from set points within the file. But, when we do it, it is inaccurate by up to 10 seconds.

Here's the code:

  let trackStart = arrTracks![MediaPlayer.shared.currentSongNo].samples

  let frameRate : Int32 = (MediaPlayer.shared.player?.currentItem?.asset.duration.timescale)!

  MediaPlayer.shared.player?.seek(to: CMTimeMakeWithSeconds(Double(trackStart), frameRate), 
    toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
  • We have to use AVPlayer because we need the better quality "spectral:" AVAudioTimePitchAlgorithm.
  • We didn't have the problem with AVAudioPlayer, but (AFAIK) we have to use AVPlayer because we need the better quality "spectral:" AVAudioTimePitchAlgorithm.

  • [Edit:] - The error is consistent - it always plays from the same (wrong) place for a given requested position. This is also true after restarting.

Any help very much appreciated! Thanks

[Edit:]

  • We have already tried preferredTimescale: playerTimescale
  • Also tried kCMTimeIndefinite instead of kCMTimeZero
Ian C
  • 23
  • 6
  • This sounds like a job for AVURLAsset’s AVURLAssetPreferPreciseDurationAndTimingKey https://developer.apple.com/documentation/avfoundation/avurlassetpreferprecisedurationandtimingkey – Rhythmic Fistman Feb 08 '19 at 11:47
  • @RhythmicFistman that fixed it. Many thanks, hugely appreciate your help on this. For some reason I'm not able to click a Tick to accept this answer as it's in a comment, but if you repost as the anwer I'll Accept it. – Ian C Feb 11 '19 at 12:34
  • Great! I've turned my speculative guess into an authoritative answer. – Rhythmic Fistman Feb 11 '19 at 12:41

3 Answers3

2

I have done something similar but with a slider to change seconds of playing and worked perfectly.

 @objc func handleSliderChange(sender: UISlider?){
        if let duration = player?.currentItem?.duration{
            let totalSeconds = CMTimeGetSeconds(duration)
            let value = Float64(videoSlider.value) * totalSeconds
            let seekTime = CMTime(value: CMTimeValue(value), timescale: 1)
            player?.seek(to: seekTime , completionHandler: { (completedSeek) in
                //do smthg later
            })
        }
    }

in you case this will be like this:

let trackStart = arrTracks![MediaPlayer.shared.currentSongNo].samples
let value = Float64(trackStart)
let seekTime = CMTime(value: CMTimeValue(value), timescale: 1)
MediaPlayer.shared.player?.seek(to: seekTime , completionHandler: { (completedSeek) in
                //do smthg later
            })
Enea Dume
  • 3,014
  • 3
  • 21
  • 36
2

This is what AVURLAsset’s AVURLAssetPreferPreciseDurationAndTimingKey is for.

Apple's documentation.

Beware that this should increase the loading time.

Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
0

Try this, It's working perfectly for me

@IBAction func playbackSliderValueChanged(_ playbackSlider: UISlider) {
    let seconds : Int64 = Int64(playbackSlider.value)
    let targetTime: CMTime = CMTimeMake(value: seconds, timescale: 1)
    DispatchQueue.main.async {
        self.player!.seek(to: targetTime)
        if self.player!.rate == 0 { // if the player is not yet started playing
            self.player?.play()
        }
    }
}
  • Thanks Naveen we tried this but in the end it was the AVURLAssetPreferPreciseDurationAndTimingKey . But appreciate your suggestion. – Ian C Feb 11 '19 at 12:37