Reputation: 21
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
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
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