I'm trying to perfectly synchronize the playback of 2 AVAudioPlayerNode using AVAudioEngine. I can achieve this if the engine in running, because then I can use one of the player's lastRenderTime. However, after pausing audio (which pauses the engine) and then resuming (start the engine and then immediately start players) the lastRenderTime is invalid (lastRenderTime.isSampleTimeValid == false or it's too far in the past because it hasn't been updated yet).
I've looked at questions like this which recommend using lastRenderTime and it does work if there is a valid sample time, but how do I start it right away, since the time is invalid while the engine is paused and shortly after starting it?
Here's my code:
func play() {
// engine is not running after pausing, so start the engine again here
if self.engine.isRunning == false {
do {
self.setupAudioSession()
try self.engine.start()
} catch {
print(error)
return
}
}
// players is my array of AVAudioPlayerNode
// sampleRate is the file's processingFormat
let startTime: AVAudioTime? = {
guard let first = players.first else { return nil }
guard let renderTime = first.lastRenderTime, renderTime.isSampleTimeValid else {
return nil
}
return AVAudioTime(sampleTime: renderTime.sampleTime, atRate: sampleRate)
}()
for player in players {
player.player.play(at: startTime)
}
}
This code works well if I do not call engine.pause() when pausing audio, however I need to pause the engine or else control center always shows a pause icon regardless of what I tell it to do in MPNowPlayingInfoCenter, and control center is important for my app.
How can I synchronize my players when starting playback?