Curiousnoes
Curiousnoes

Reputation: 73

SwiftUI sound on timer ending is playing over and over

I created a timer. When the timer ends it should play a complete sounds once. It is a soundfile that is about 5 seconds long.

But what happens is that when the timer ends, the sounds is playing over and over again and it only plays the first 2 seconds of the sound.

It happens on the simulator and a real device.

I have no idea why this is happening. I've been googling voor quite some time but I can't find a solution.

I tried to connect the sound to buttons, and the play and stop functions work fine. But even if I try to put the 'SoundManager.instance.stopSound()' into the stop button, it still doesn't stop playing the sound when I tap it when the timer ends.

Can someone tell me what I am doing wrong?

This is my code:

import SwiftUI
import AVKit


class SoundManager {
    static let instance = SoundManager()
    var player: AVAudioPlayer?
    
    func playSound() {
        guard let url = Bundle.main.url(forResource: "Tada", withExtension: ".mp3") else {return}
        
        do {
        player = try AVAudioPlayer(contentsOf: url)
            player?.play()
        } catch let error {
            print("Error playing sound. \(error.localizedDescription)")
            
        }
    }
    func stopSound() {
            // Stop AVAudioPlayer
        player?.stop()
        }
}

struct TimerCountDown: View {
    @State var countDownTimer = 30
    @State var timerRunning = false
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        VStack {
            Text("\(countDownTimer)")
                .onReceive(timer) { _ in
                    if countDownTimer > 0 && timerRunning {
                        countDownTimer -= 1
                    }
                    else if countDownTimer < 1 {
                        SoundManager.instance.playSound()

                    }
                    else {
                        timerRunning = false
                        
                    }
                    
                }
                .font(.system(size: 80, weight: .bold))
                .opacity(0.80)
            
            HStack (spacing: 30) {
                Button("Start") {
                    timerRunning = true
                }
                .padding()
                .background(.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
                
                Button("Stop") {
                    timerRunning = false
                }
                .padding()
                .background(.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
                    
                
                Button("Reset") {
                    countDownTimer = 30
                }
                .padding()
                .background(.red)
                .foregroundColor(.white)
                .cornerRadius(8)
            }
        }
        
       
    }
}

struct TimerCountDown_Previews: PreviewProvider {
    static var previews: some View {
        TimerCountDown()
    }
}

Upvotes: 0

Views: 435

Answers (1)

vacawama
vacawama

Reputation: 154631

Your problem is in your .onReceive(timer) code. When the countDownTimer has reached 0, the timer is still publishing every second, so your code enters the second clause which plays the end timer sound. It does this every second. You should only do that if timerRunning and you should set that to false when your count reaches 0

.onReceive(timer) { _ in
    if timerRunning {
        if countDownTimer > 0 {
            countDownTimer -= 1
            if countDownTimer == 0 {
                timerRunning = false
                SoundManager.instance.playSound()
            }
        }
    }
}

Note: Your code leaves the timer publishing at all times, and it is really only needed when the timer is running. Check this answer for ideas on how to stop and restart the publishing of the timer.

Upvotes: 1

Related Questions