3
votes

So I have a class called audio settings that is as follows:

class audioSettings: ObservableObject {
    
    var audioPlayer: AVAudioPlayer?
    var playing = false
    var playValue: TimeInterval = 0.0
    var playerDuration: TimeInterval = 146
    var timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    
    func playSound(sound: String, type: String) {
        if let path = Bundle.main.path(forResource: sound, ofType: type) {
            do {
                if playing == false {
                    if (audioPlayer == nil) {
                        
                        
                        audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
                        audioPlayer?.prepareToPlay()
                        
                        audioPlayer?.play()
                        playing = true
                    }
                    
                }
                if playing == false {
                    
                    audioPlayer?.play()
                    playing = true
                }
                
                
            } catch {
                print("Could not find and play the sound file.")
            }
        }
        
    }
    
    
    
    func stopSound() {
        //   if playing == true {
        audioPlayer?.stop()
        audioPlayer = nil
        playing = false
        playValue = 0.0
        
        
        //   }
    }
    
    func pauseSound() {
        if playing == true {
            audioPlayer?.pause()
            playing = false
            
            
        }
    }
    
    
    func changeSliderValue() {
        if playing == true {
            pauseSound()
            audioPlayer?.currentTime = playValue
            
        }
        
        if playing == false {
            
            
            audioPlayer?.play()
            playing = true
        }
    }
}

And when I implement the code in to the view I have success with playing the file. I have success with stopping it and I have success with moving the slider and getting a new position in the audio.

Where i'm struggling is in getting the slider to update in real time as the audio is playing. If I hit play the slider will do nothing until I hit pause. At which point it will move to the correct position.

Here is the views code:

struct myExperienceFearChunk: View {
    
    
    @ObservedObject var audiosettings = audioSettings()
    
    
    var body: some View {
        
            
            HStack {
                Button(action: {
                    if (self.playButton == Image(systemName: "play.circle")) {
                        print("All Done")
                        self.audiosettings.playSound(sound: "audio file", type: "mp3")
                        self.audiosettings.timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
                        self.playButton = Image(systemName: "pause.circle")
                        
                    } else {
                        
                        self.audiosettings.pauseSound()
                        self.playButton = Image(systemName: "play.circle")
                        
                        
                    }

                    
                }) {
                    self.playButton
                        .foregroundColor(Color.white)
                        .font(.system(size: 44))
                }
                Button(action: {
                    print("All Done")
                    self.audiosettings.stopSound()
                    self.playButton = Image(systemName: "play.circle")
                    self.audiosettings.playValue = 0.0
                    
                }) {
                    self.stopButton
                        .foregroundColor(Color.white)
                        .font(.system(size: 44))
                    
                }
            }
            Slider(value: $audiosettings.playValue, in: TimeInterval(0.0)...audiosettings.playerDuration, onEditingChanged: { _ in
                self.audiosettings.changeSliderValue()
            })
                .onReceive(audiosettings.timer) { _ in
                    
                    if self.audiosettings.playing {
                        if let currentTime = self.audiosettings.audioPlayer?.currentTime {
                            self.audiosettings.playValue = currentTime
                            
                            if currentTime == TimeInterval(0.0) {
                                self.audiosettings.playing = false
                            }
                        }
                        
                    }
                    else {
                        self.audiosettings.playing = false
                        self.audiosettings.timer.upstream.connect().cancel()
                    }
            }
        }
    }
}

I know that the .onReceive is where the slider is being updated from when I hit pause, as without it the slider doesn't update at all unless dragged.

Any ideas how I can get the slider to update as the audio is playing setting the playValue as it goes.

Thanks.

1

1 Answers

3
votes

Here is fixed code. Tested with Xcode 11.4 / iOS 13.4

Main thing around fixes is to made @Published var playValue published to give chance for observed object in view be notified about changes in audioSettings and thus update Slider... others are minor.

class audioSettings: ObservableObject {
    
    var audioPlayer: AVAudioPlayer?
    var playing = false
    @Published var playValue: TimeInterval = 0.0
    var playerDuration: TimeInterval = 146
    var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    
    func playSound(sound: String, type: String) {
        if let path = Bundle.main.path(forResource: sound, ofType: type) {
            do {
                if playing == false {
                    if (audioPlayer == nil) {
                        
                        
                        audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
                        audioPlayer?.prepareToPlay()
                        
                        audioPlayer?.play()
                        playing = true
                    }
                    
                }
                if playing == false {
                    
                    audioPlayer?.play()
                    playing = true
                }
                
                
            } catch {
                print("Could not find and play the sound file.")
            }
        }
        
    }

    func stopSound() {
        //   if playing == true {
        audioPlayer?.stop()
        audioPlayer = nil
        playing = false
        playValue = 0.0
        //   }
    }
    
    func pauseSound() {
        if playing == true {
            audioPlayer?.pause()
            playing = false
        }
    }
    
    func changeSliderValue() {
        if playing == true {
            pauseSound()
            audioPlayer?.currentTime = playValue
            
        }
        
        if playing == false {
            audioPlayer?.play()
            playing = true
        }
    }
}

struct myExperienceFearChunk: View {
    @ObservedObject var audiosettings = audioSettings()
    @State private var playButton: Image = Image(systemName: "play.circle")
    
    var body: some View {
        
        VStack {
        HStack {
            Button(action: {
                if (self.playButton == Image(systemName: "play.circle")) {
                    print("All Done")
                    self.audiosettings.playSound(sound: "filename", type: "wav")
                    self.audiosettings.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
                    self.playButton = Image(systemName: "pause.circle")
                    
                } else {
                    
                    self.audiosettings.pauseSound()
                    self.playButton = Image(systemName: "play.circle")
                }
            }) {
                self.playButton
                    .foregroundColor(Color.white)
                    .font(.system(size: 44))
            }
            Button(action: {
                print("All Done")
                self.audiosettings.stopSound()
                self.playButton = Image(systemName: "play.circle")
                self.audiosettings.playValue = 0.0
                
            }) {
                Image(systemName: "stop.circle")
                    .foregroundColor(Color.white)
                    .font(.system(size: 44))
            }
        }
        Slider(value: $audiosettings.playValue, in: TimeInterval(0.0)...audiosettings.playerDuration, onEditingChanged: { _ in
            self.audiosettings.changeSliderValue()
        })
            .onReceive(audiosettings.timer) { _ in
                
                if self.audiosettings.playing {
                    if let currentTime = self.audiosettings.audioPlayer?.currentTime {
                        self.audiosettings.playValue = currentTime
                        
                        if currentTime == TimeInterval(0.0) {
                            self.audiosettings.playing = false
                        }
                    }
                    
                }
                else {
                    self.audiosettings.playing = false
                    self.audiosettings.timer.upstream.connect().cancel()
                }
            }
        }
    }
}