快速点击音量切换按钮引发的音频异常问题及修复咨询
问题修复方案
核心问题分析
- 快速点击导致音量跳变:未阻止并发的淡入淡出操作,新的
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
相关产品推荐
相关产品推荐

