Search code examples
iosarraysswiftxcode8avaudiosession

Auto play audio files in Swift


I'm making a very simple app in which the user get to select some audio files he wants to play. Then, he pushes the Play button and, it would play all the files selected one after another.

To do so, I made an array that stores the name of the files the users wants to play. Then, a function triggered by the Play button would make a iteration of the array, look for the names and play the files.

It's something like this :

//declaring the audioplayer
var audioPlayer = AVAudioPlayer()

//declaring the array
var selectedMusic = [String]()

The interface got 6 buttons, one of each is linked to a specific music. The user can trigger the music instantly by tapping the button, or make a selection for a sequential play by a long press. Let's assume that the user got a selection and the array would look something like this :

selectedMusic = ["Song 1", "Song 5", "Song 3", "Song 2"]

My function to play the songs is :

for selection in selectedMusic {

        if selection == "Song 1" {

            guard let alertSound = Bundle.main.url(forResource: "Song 1", withExtension: "mp3") else {return}

            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                try AVAudioSession.sharedInstance().setActive(true)


                audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
                audioPlayer.prepareToPlay()
                audioPlayer.play()

            } catch let error {
                print(error.localizedDescription)
            }

        } else if selection == "Song 2" {

            guard let alertSound = Bundle.main.url(forResource: "Song 2", withExtension: "mp3") else {return}

            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                try AVAudioSession.sharedInstance().setActive(true)


                audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
                audioPlayer.prepareToPlay()
                audioPlayer.play()

            } catch let error {
                print(error.localizedDescription)
            }

Etc, until Song 5.

What I want is to play the selected songs in a specific order (1 to 5) and not the order the user chose (it's important).

Yet, this function is not giving the expected results, it's just playing the last item of the selection.

So, how should I make this work ? Is the array not the best option here ? As i mentioned, the fact that the songs are played in chronological order is mandatory.

Any help would be greatly appreciated :) Thanks !


Solution

  • Well first, you're using a for and repeating the code inside it.. in theory you could just do

    audioUrls.forEach { (url) in
            guard let alertSound = Bundle.main.url(forResource: url, withExtension: "mp3") else {return}
    
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                try AVAudioSession.sharedInstance().setActive(true)
    
    
                audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
                audioPlayer.prepareToPlay()
                audioPlayer.play()
    
            } catch let error {
                print(error.localizedDescription)
            }
        }
    

    second, you're not tracking the audio so your program does not know that the track finished playing and you're overwriting the player with a new object.

    For instance, in the following code, I am keeping track of my audio player and updating a label with the current time.

    func keepTrackOfAudio() {
        audioPlayer?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
    
        //track progress
    
        let interval = CMTime(value: 1, timescale: 2)
        audioPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main , using: { (progressTime) in
            let seconds = CMTimeGetSeconds(progressTime)
            let secondsString = String(format: "%02d", Int(seconds.truncatingRemainder(dividingBy: 60)))
            let minutesString = String(format: "%02d", Int(seconds / 60))
    
            self.audioCurrentTimeLabel.text = "\(minutesString):\(secondsString)"
    
            //lets move the slider thumb
            if let duration = self.audioPlayer?.currentItem?.duration {
                let durationSeconds = CMTimeGetSeconds(duration)
    
                self.audioSlider.value = Float(seconds / durationSeconds)
    
            }
        })
    
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    
    
            if let duration = audioPlayer?.currentItem?.duration {
                let seconds = CMTimeGetSeconds(duration)
    
                let secondsText = String(format: "%02d", Int(seconds) % 60)
                let minutesText = String(format: "%02d", Int(seconds) / 60)
                audioLengthLabel.text = "\(minutesText):\(secondsText)"
            }
    
        }
    }
    

    your homework is to calculate when the audio has reached the end and only then, play the next track.