0

I have a set of buttons in a stack view. Each button when pressed plays a different sound. I have a separate button (loop button) that when pressed calls the loopButtonPressed function. My goal is that when this loop button is pressed, it will loop through the subviews that are buttons in this stack view and play each of the sounds sequentially in order using the soundButtonPressed function. I saw a method that I implemented below using the run() function which sets each consecutive function to run after a given amount of time. Although this kind of works it is not a great solution because the sound files are of varying length. I was thinking there may be a way to do this using dispatch groups, which I don't fully understand. If I take away the run function, it will only play the sound of the last button in the stack view. I am using AVFoundation to play the wav files as well. I appreciate any advice or direction, thanks.

    func run(after seconds: Int, completion: @escaping () -> Void) {
        let deadline = DispatchTime.now() + .milliseconds(seconds)
        DispatchQueue.main.asyncAfter(deadline: deadline) {
            completion()
        }
    }

    @objc func loopButtonPressed(_ sender: UIButton) {
        var i = 1
        for case let button as UIButton in self.colorBubblesStackView.subviews {
            run(after: 800*i) {
                self.soundButtonPressed(sender: button)
            }
            i += 1
        }
    }

My soundButtonPressed function is just a switch statement where each case calls the function playSound() with the correct sound file name. Here is the playSound function:

func playSound(_ soundFileName: String) {
        guard let url = Bundle.main.url(forResource: soundFileName, withExtension: "wav") else { return }

        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
            try AVAudioSession.sharedInstance().setActive(true)

            player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue)

            guard let player = player else { return }

            player.play()

        } catch let error {
            print(error.localizedDescription)
        }
    }
Max Dolensky
  • 107
  • 7
  • If you set player.delegate you can get a callback when the sound finishes playing. That will let you know when you need to start playing the next sound. – Rob C Mar 18 '20 at 04:44

2 Answers2

0
func playSound(name: String ) {
        guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
            print("url not found")
            return
        }

        do {
            /// this codes for making this app ready to takeover the device audio
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
            try AVAudioSession.sharedInstance().setActive(true)

            /// change fileTypeHint according to the type of your audio file (you can omit this)
            player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)

            player?.delegate = self

            // no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
            player!.play()
        } catch let error as NSError {
            print("error: \(error.localizedDescription)")
        }
    }

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        print("finished")//It is working now! printed "finished"!
    }

confirm to protocol

class ViewController: UIViewController,AVAudioPlayerDelegate {

and instead of looping add each button a Tag and start from first tag sat 100 . Then when the call back obtained for player finished playing play the next File with new icremented Tag say 101

ManuRaphy
  • 361
  • 1
  • 13
0

Try AVQueuePlayer

A player used to play a number of items in sequence.

@objc func loopButtonPressed(_ sender: UIButton) {
    let allUrls = allSongs.compactMap { Bundle.main.url(forResource: $0, withExtension: "wav") }
    let items = allUrls.map { AVPlayerItem(url: $0) }
    let queuePlayer = AVQueuePlayer(items: items)
    queuePlayer.play()
}
iOSDev
  • 199
  • 11
  • I tried this but it seems to play only a fraction of the first sound file. Correct me if I am wrong but allSongs is a list of all the song file names that should be played. It set a print statement below items and it prints out all the correct URLs, but is not playing all the sounds – Max Dolensky Mar 18 '20 at 19:46
  • @MaxDolensky Yes. `allSongs` is an array of sound names like `["sound1", "sound2"]`. You should have `sound1.wav` and `sound2.wav` files in the project – iOSDev Mar 19 '20 at 04:43