4

I'm implementing an app utilizing AudioKit that allows you to play a large number of audio files and switch between them at any time. Additionally we need one audio file to play on loop for ambient noise.

What is the correct way to dynamically change players in AudioKit's output? Or what's the proper way to implement this behavior?

AudioKit requires you set its output which can be an AKPlayer that plays one audio file or an AKMixer given an array of AKPlayers for example. You cannot change the output once AudioKit has been started as I saw previously. So currently my approach is to use AKMixer to play two AKPlayers - one for the current file and one for the ambient noise. When the user taps the 'Next' button I stop() AudioKit, recreate an AKMixer with a new player for the next song's audio file and the ambient noise player, assign that to output, start() AudioKit, and play both players. This results in undesirable behavior because playback of the ambient noise is stopped when switching songs resulting in a brief pause.

If this is the correct approach, how can we update the output without stopping AudioKit? I wondered if you can initialize the mixer with an array of players that's a strongly held property and simply add/remove players in the array. But that doesn't work - starting a new player throws an error player started when in a disconnected state because this node is not attached to the output.

I've created a sample project to demonstrate this behavior. When launched the app plays drums as ambient noise and waves for the current track. When you tap Next it'll switch to the next track (which in this demo code is just the same audio file) and you can hear the drums stop and then resume which is undesirable. I've included the project's code below:

final class Maestro: NSObject {

    static let shared = Maestro()

    private var audioPlayer: AKPlayer?
    private var ambientPlayer: AKPlayer = {
        let player = AKPlayer(url: Bundle.main.url(forResource: "drums", withExtension: "wav")!)!
        player.isLooping = true
        return player
    }()
    private var mixer: AKMixer?

    private let audioFileURL = Bundle.main.url(forResource: "waves", withExtension: "mp3")!

    func play() {
        playNewPlayer(fileURL: audioFileURL)
    }

    func next() {
        //In the real app we'd play the next audio file in the playlist but for the demo we'll just play the same file
        playNewPlayer(fileURL: audioFileURL)
    }

    private func playNewPlayer(fileURL: URL) {
        audioPlayer?.stop()
        audioPlayer = nil

        do {
            try AudioKit.stop()
        } catch {
            print("Maestro AudioKit.stop error: \(error)")
        }

        audioPlayer = AKPlayer(url: fileURL)!
        mixer = AKMixer([audioPlayer!, ambientPlayer])
        AudioKit.output = mixer

        do {
            try AudioKit.start()
        } catch {
            print("Maestro AudioKit.start error: \(error)")
        }

        if ambientPlayer.isPlaying {
            //need to resume playback from current position
            let pos = ambientPlayer.currentTime
            ambientPlayer.stop()
            ambientPlayer.play(from: pos)
        } else {
            ambientPlayer.play()
        }

        audioPlayer?.play()
    }

}
Jordan H
  • 52,571
  • 37
  • 201
  • 351

0 Answers0