You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI计时器结束后音频重复播放且仅播放前2秒问题求助

问题排查与解决方案

核心问题原因

  1. 重复触发音频播放:计时器每秒执行一次onReceive回调,当倒计时结束(countDownTimer < 1)后,该条件会一直满足,导致每秒都调用playSound(),每次都新建AVAudioPlayer并从头播放,所以音频会反复被打断重启,表现为“仅播放前2秒且循环”。
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:40:26