Search code examples
iosswiftavaudioplayer

Swift iOS, Play tracks sequentially from array using AVAudioPlayer, with some function in between each track?


I have an array of audio instructions that I would like to randomize, play (one at a time), and do some data recording in between each instruction.

With the below code, I have a button press that triggers beginTest(), but the instruction tracks play simultaneously. It never gets to startRecord().

I've tried placing startRecord() inside audioPlayerDidFinishPlaying; in that case it does get to startRecord() for the first track, but doesn't continue to the next iteration of the loop inside beginTest() where playScript() is called.

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
    self.player = nil
    print("finished playing this file")

}

func playScript(c: String)
{
    let path = Bundle.main.path(forResource: c, ofType:"m4a")!
    let url = URL(fileURLWithPath: path)

    do {
        self.player = try AVAudioPlayer(contentsOf: url)
        self.player?.delegate = self
        self.player?.play()
        print(c)
        print("playing script for " + c)
    } catch {
        print("couldn't load script")
    }
}

func beginTest()
{
    for c in conditions.shuffled()
    {
        playScript(c: c)
        
        if self.player == nil
        {
            startRecord()
            print("started data recording")
        }
    }
    
    exportData()
}

I'm guessing that beginTest() keeps moving along the for loop before audioPlayerDidFinishPlaying resets the player to nil, but I don't know how to stall this.


Solution

  • The approach using audioPlayerDidFinishPlaying(_:,successfully:) is correct. In your code when you call playScript(c:) you will iterate through all audio files from conditions array and call startRecord method really quickly, while first audio is playing. All sounds will be placed in a queue and play one by one. I don't know contents of startRecord method, but I don't think that it was supposed to be called like that.

    In order to avoid those problems you need to call playScript(c:) and then wait for method startRecord() to finish, and then call playScript(c:) again. So you can try something like this:

    import AVFoundation
    
    class SomeClass: NSObject, AVAudioPlayerDelegate {
        
        var player: AVAudioPlayer?
        var conditions = [String]()
        var instructions: [() -> ()] = []
            
        func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
            print("finished playing \(player.url)")
            startRecord {
                playNextInstruction()
            }
        }
    
        func playScript(c: String)
        {
            let path = Bundle.main.path(forResource: c, ofType:"m4a")!
            let url = URL(fileURLWithPath: path)
    
            do {
                self.player = try AVAudioPlayer(contentsOf: url)
                self.player?.delegate = self
                self.player?.play()
                print(c)
                print("playing script for " + c)
            } catch {
                print("couldn't load script")
            }
        }
    
        func beginTest() {
            instructions = conditions.shuffled().map { [unowned self] condition in
                return {
                    self.playScript(c: condition)
                }
            }
            playNextInstruction()
        }
        
        func playNextInstruction() {
            if instructions.count != 0 {
                instructions.remove(at: 0)()
            } else {
                exportData()
            }
        }
        
        func exportData() {
        
        }
        
        func startRecord(completion: (() -> ())) {
            
        }
            
    }
    

    If it won't work then I suggest to try searching problem somewhere else in your code.