SwiftUI计时器结束后音频重复播放且仅播放前2秒问题求助
问题排查与解决方案
核心问题原因
- 重复触发音频播放:计时器每秒执行一次
onReceive回调,当倒计时结束(countDownTimer < 1)后,该条件会一直满足,导致每秒都调用playSound(),每次都新建AVAudioPlayer并从头播放,所以音频会反复被打断重启,表现为“仅播放前2秒且循环”。 - 停止功能失效:每次调用
playSound()都会替换SoundManager中的player实例,调用stopSound()只能停止当前的player,但下一秒计时器又会创建新的player并播放,因此无法彻底终止音频。
修复方案
1. 添加状态标记避免重复播放
在TimerCountDown中新增状态变量,标记结束音效是否已经播放过,确保倒计时结束后仅播放一次音频。
2. 完善停止与重置逻辑
在停止按钮中同时停止音频并重置状态;重置按钮也要重置所有相关状态,保证下次计时正常。
3. 优化SoundManager(可选但更健壮)
提前初始化音频播放器,避免每次播放都重新创建实例,同时添加播放状态检查。
修改后的完整代码
import SwiftUI import AVKit class SoundManager { static let instance = SoundManager() var player: AVAudioPlayer? // 初始化时加载音频,避免重复创建 init() { guard let url = Bundle.main.url(forResource: "Tada", withExtension: ".mp3") else { return } do { player = try AVAudioPlayer(contentsOf: url) player?.prepareToPlay() // 提前准备,优化播放启动速度 } catch let error { print("Error initializing sound. \(error.localizedDescription)") } } func playSound() { guard let player = player else { return } // 如果正在播放,先停止再从头开始(根据需求调整,这里确保每次播放都是完整的) if player.isPlaying { player.stop() player.currentTime = 0 } player.play() } func stopSound() { player?.stop() player?.currentTime = 0 // 重置播放位置 } } struct TimerCountDown: View { @State var countDownTimer = 30 @State var timerRunning = false @State var hasPlayedEndSound = 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 hasPlayedEndSound = false // 计时中重置标记 } else if countDownTimer < 1 && !hasPlayedEndSound { SoundManager.instance.playSound() hasPlayedEndSound = true // 标记已播放,避免重复触发 timerRunning = false // 自动停止计时器 } } .font(.system(size: 80, weight: .bold)) .opacity(0.80) HStack (spacing: 30) { Button("Start") { timerRunning = true hasPlayedEndSound = false // 启动时重置标记 } .padding() .background(.blue) .foregroundColor(.white) .cornerRadius(8) Button("Stop") { timerRunning = false SoundManager.instance.stopSound() // 停止音频 hasPlayedEndSound = false // 重置标记 } .padding() .background(.blue) .foregroundColor(.white) .cornerRadius(8) Button("Reset") { countDownTimer = 30 timerRunning = false SoundManager.instance.stopSound() // 重置时停止音频 hasPlayedEndSound = false // 重置标记 } .padding() .background(.red) .foregroundColor(.white) .cornerRadius(8) } } } } struct TimerCountDown_Previews: PreviewProvider { static var previews: some View { TimerCountDown() } }
关键修改点说明
- SoundManager:初始化时提前加载音频,调用
prepareToPlay()优化播放启动速度,播放时检查状态并重置播放位置,确保每次播放都是完整的。 - TimerCountDown:新增
hasPlayedEndSound状态,仅在倒计时结束且未播放过音效时触发一次播放;停止和重置操作同时处理音频停止与状态重置,保证逻辑一致性。
内容的提问来源于stack exchange,提问作者Curiousnoes
相关产品推荐
相关产品推荐

