3

I'm creating a traditional music player with AudioKit. Initially it plays one file, then you can tap the next button to skip to the next audio file. The songs aren't all known up-front, the playlist can change while a song is currently playing, so it's not known what the next audio file will be until we go to play it.

My current implementation for that works well. I create a player for the first audio file and set that to AudioKit.output and call AudioKit.start() then player.play(), then when next is tapped I call AudioKit.stop() and then create the new player, set it as the output, start AudioKit, and play the new player. If you don't stop AudioKit before modifying the output, you'll encounter an exception as I saw previously.

Now you should also be able to tap a fade button which will crossfade between the current song and the next song - fade out the current song for 3 seconds and immediately play the next song. This is proving to be difficult. I'm not sure how to properly implement it.

The AudioKit playgrounds have a Mixing Nodes example where multiple AKPlayers are created, AKMixer is used to combine them, and the mixer is assigned to the output. But it appears you cannot change the players in the mixer. So the solution I have currently is to stop AudioKit when the fade button is tapped, recreate the AKMixer adding a new player for the next song, start AudioKit, then resume playback of the first player and play the new player. This experience isn't smooth; you can certainly hear the audio stop and resume.

How can I properly fade out one song while playing the next song?

Please see my sample project on GitHub. I've included its code below:

final class Maestro: NSObject {

    static let shared = Maestro()

    private var trackPlayers = [AKPlayer]() {
        didSet {
            do {
                try AudioKit.stop()
            } catch {
                print("Maestro AudioKit.stop error: \(error)")
            }

            mixer = AKMixer(trackPlayers)
            AudioKit.output = mixer

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

            trackPlayers.forEach {
                if $0.isPlaying {
                    let pos = $0.currentTime
                    $0.stop()
                    $0.play(from: pos)
                }
            }
        }
    }
    private var mixer: AKMixer?

    private let trackURLs = [
        Bundle.main.url(forResource: "SampleAudio_0.4mb", withExtension: "mp3")!,
        Bundle.main.url(forResource: "SampleAudio_0.7mb", withExtension: "mp3")!
    ]

    func playFirstTrack() {
        playNewPlayer(fileURL: trackURLs[0])
    }

    func next() {
        trackPlayers.forEach { $0.stop() }
        trackPlayers.removeAll()

        playNewPlayer(fileURL: trackURLs[1])
    }

    func fadeAndStartNext() {
        playNewPlayer(fileURL: trackURLs[1])

        //here we would adjust the volume of the players and remove the first player after 3 seconds
    }

    private func playNewPlayer(fileURL: URL) {
        let newPlayer = AKPlayer(url: fileURL)!
        trackPlayers.append(newPlayer) //triggers didSet to update AudioKit.output

        newPlayer.play()
    }

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

0 Answers0