330sysadmin
330sysadmin

Reputation: 21

Play/Stop button with SwiftUI

I'm working on an app that plays audio files displayed in a list.

Audio File 1
Audio File 2
...
Audio File 10

I have audio working with the following code.

var audioPlayer: AVAudioPlayer?


func playSound1() {

    let path = Bundle.main.path(forResource: "audiofile1.mp3", ofType:nil)!
    let url = URL(fileURLWithPath: path)

    do {
        audioPlayer = try AVAudioPlayer(contentsOf: url)
        audioPlayer?.play()
    } catch {
        print("Could not find file")

    }
}


struct ContentView: View {
    var body: some View {
        Button(action:{ playSound1()}) {
            Text("Audio File 1") }
    }
}

I'm looking to solve when Audio File 1 is tapped a second time it will stop playing Audio File 1. Also if Audio File 1 is playing and one tap's on Audio File 2 stop playing Audio File 1 and play Audio File 2. I would like this to happen on all 10 audio files.

Upvotes: 1

Views: 2456

Answers (2)

Evgenii Turchaninov
Evgenii Turchaninov

Reputation: 9

import AVFoundation

// Audio Model
final class AudioModel: ObservableObject {

    @Published var current = ""

    static let shared = AudioModel()
    private init() {}

    var audioPlayer: AVAudioPlayer?

    var audioFiles = ["audiofile1.mp3", "audiofile2.mp3", "audiofile3.mp3"]

    func playSound() {
    
        let path = Bundle.main.path(forResource: current, ofType: nil)!
        let url = URL(fileURLWithPath: path)
    
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: url)
                audioPlayer?.play()
            } catch {
                print("Could not find file")

            }
    }

    func stopSound() {
        // Stop AVAudioPlayer
        audioPlayer?.stop()
    }
}

    
    
    
// Usage example

struct ContentView: View {
    
    @StateObject var audioModel = AudioModel.shared

    var body: some View {
        VStack {
            ForEach(audioModel.audioFiles, id: \.self) { item in
                Button {
                    play(item)
                } label: {
                    Text(item)
                }
            }
        }
    }

    private func play(_ track: String) {
        audioModel.current = track
        audioModel.stopSound()
        audioModel.playSound()

    }
}

Upvotes: 0

krjw
krjw

Reputation: 4450

I would suggest something like the following:



class AudioModel: ObservableObject {
    var audioPlayer: AVAudioPlayer?

    @Published var current: String = ""

    var audioFiles = ["audiofile1.mp3", "audiofile2.mp3", "audiofile3.mp3"]

    func playSound() {

        let path = Bundle.main.path(forResource: self.current, ofType:nil)!
        let url = URL(fileURLWithPath: path)

        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url)
            audioPlayer?.play()
        } catch {
            print("Could not find file")

        }
    }

    func stopSound() {
        // Stop AVAudioPlayer
        // audioPlayer.stop() ???
    }
}




struct ContentView: View {

    @ObservedObject var audioModel = AudioModel()

    var body: some View {
        VStack {
            ForEach(self.audioModel.audioFiles, id: \.self) { item in
                Button(action:{ 
                        self.audioModel.current = item
                    }) {
                        Text(item) 
                    }
                }
            }
        }.onReceive(self.audioModel.$current) {_ in
            self.audioModel.stopSound()
            self.audioModel.playSound()
        }
    }

It's not finished because I couldn't test it but I hope this helps for further investigation.

@Published is similar to @State and additionally creates a Publisher so whenever it is updated, the onReceive() call will be made. This is achieved by "Observing" the object with the @ObservedObject decorator which can only be applied to a class that conforms to ObservableObject

Upvotes: 3

Related Questions