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

快速点击音量切换按钮引发的音频异常问题及修复咨询

问题修复方案

核心问题分析

  • 快速点击导致音量跳变:未阻止并发的淡入淡出操作,新的setVolume会直接覆盖旧的渐变过程,同时缺少状态锁导致逻辑混乱。
  • 延时队列引发意外暂停:pause方法中的延时任务未绑定当前状态,即使后续重新触发播放,到时间依然会执行pause。

具体修复步骤

1. 给Audio类添加状态管理与任务控制

在Audio类中新增变量,跟踪播放状态、渐变状态,并管理延时任务:

class Audio: NSObject, AVAudioPlayerDelegate {
    var audioPlayer: AVAudioPlayer!
    // 记录当前播放状态
    private var isPlaying = false
    // 标记是否正在执行淡入/淡出操作
    private var isFading = false
    // 存储暂停的延时任务,用于后续取消
    private var pauseDelayTask: DispatchWorkItem?
    
    // 原有loopedAudio方法保持不变
    func loopedAudio(fileName: String, fileExtension: String) {
        try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
        try? AVAudioSession.sharedInstance().setActive(true)
        
        guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else { return }
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url)
            audioPlayer.prepareToPlay()
            audioPlayer.delegate = self
            audioPlayer.numberOfLoops = -1
            // 初始化时同步播放状态
            isPlaying = audioPlayer.isPlaying
        } catch {
            print("audio init error: \(error)")
        }
    }
}

2. 修改play方法,阻止并发渐变操作

播放前取消可能存在的暂停延时任务,同时判断是否正在渐变,避免冲突:

func play(volume: Float, duration: Double) {
    // 取消之前未执行的暂停任务
    pauseDelayTask?.cancel()
    pauseDelayTask = nil
    
    // 正在渐变时直接返回,避免操作冲突
    guard !isFading else { return }
    
    isFading = true
    isPlaying = true
    
    if !audioPlayer.isPlaying {
        audioPlayer.play()
    }
    // 基于当前音量开始淡入,而非强制设为0
    audioPlayer.setVolume(volume, fadeDuration: duration)
    
    // 渐变完成后重置标记
    DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
        self.isFading = false
    }
}

3. 修改pause方法,绑定状态并支持任务取消

暂停时先取消之前的延时任务,渐变完成后仅当当前状态仍需暂停时执行pause:

func pause(volume: Float, duration: Double) {
    // 取消之前未执行的暂停任务
    pauseDelayTask?.cancel()
    pauseDelayTask = nil
    
    // 正在渐变时直接返回,避免操作冲突
    guard !isFading else { return }
    
    isFading = true
    isPlaying = false
    
    // 基于当前音量开始淡出,而非强制设为1
    audioPlayer.setVolume(volume, fadeDuration: duration)
    
    // 创建新的延时任务,执行前校验当前状态
    pauseDelayTask = DispatchWorkItem { [weak self] in
        guard let self = self, !self.isPlaying else { return }
        self.audioPlayer.pause()
        self.isFading = false
    }
    
    if let task = pauseDelayTask {
        DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: task)
    }
}

4. 同步ViewController中的状态判断

确保defaults的状态与Audio内部状态一致,避免逻辑错位:

@objc func soundState() {
    let shouldPlay = defaults.bool(forKey: "sound")
    if shouldPlay {
        audio.play(volume: 1.0, duration: 3.0)
        defaults.set(true, forKey: "sound")
    } else {
        audio.pause(volume: 0.0, duration: 3.0)
        defaults.set(false, forKey: "sound")
    }
}

额外优化建议

  • 将defaults中存储的字符串"true"/"false"改为布尔值,减少类型转换错误,提升效率。
  • 在Audio类的audioPlayerDidFinishPlaying代理方法中同步isPlaying状态,确保异常结束时状态一致。

内容的提问来源于stack exchange,提问作者user1342352532

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:33:27