AVAudioPlayer进入后台后currentTime返回值异常问题求助
解决AVAudioPlayer恢复播放后currentTime停止更新的问题
我之前也踩过AVAudioPlayer在后台/锁屏恢复时状态不同步的坑,跟你描述的问题一模一样——音频正常播放,但currentTime卡住,isPlaying还返回false,而且这个bug确实偶发,得重复操作几次才会触发,挺头疼的。
问题根源
这是AVAudioPlayer的一个已知状态同步bug,当App从后台/锁屏恢复,或者音频中断结束后,AVAudioPlayer的内部状态没有跟上系统音频流的实际状态,导致它对外暴露的currentTime和isPlaying属性失效,但底层的音频播放其实还在正常进行。
可行的解决方案
方案1:监听音频会话和App状态,强制同步播放器状态
通过监听音频会话中断通知和App前后台切换事件,在状态恢复时手动同步AVAudioPlayer的状态,强制它更新内部属性:
首先注册相关通知(建议在初始化时添加):
override init() { super.init() // 监听音频会话中断 NotificationCenter.default.addObserver(self, selector: #selector(handleAudioSessionInterruption(_:)), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) // 监听App前后台切换 NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) }
然后实现通知处理逻辑:
@objc private func handleAudioSessionInterruption(_ notification: Notification) { guard let info = notification.userInfo, let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt, let interruptionType = AVAudioSession.InterruptionType(rawValue: typeValue) else { return } // 中断结束,尝试恢复播放并同步状态 if interruptionType == .ended { guard let optionValue = info[AVAudioSessionInterruptionOptionKey] as? UInt else { return } let options = AVAudioSession.InterruptionOptions(rawValue: optionValue) if options.contains(.shouldResume) { do { try AVAudioSession.sharedInstance().setActive(true) // 强制同步:如果播放器状态显示未播放,但实际应该在播放,先暂停再播放 if !player.isPlaying && playbackState == .playing { player.pause() player.play() } setTimer(true) // 确保Timer正常运行 } catch { print("激活音频会话失败:\(error.localizedDescription)") } } } } @objc private func appDidEnterBackground() { // 后台时确保音频会话处于激活状态 let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback, mode: .default, options: .mixWithOthers) try session.setActive(true) } catch { print("后台音频会话配置失败:\(error.localizedDescription)") } } @objc private func appWillEnterForeground() { // 前台恢复时检查状态一致性 if !player.isPlaying && playbackState == .playing { player.pause() player.play() setTimer(true) } }
方案2:自行维护播放时间,不依赖AVAudioPlayer的currentTime
如果不想跟AVAudioPlayer的状态bug较劲,可以完全自己计算播放时长,彻底摆脱对它的currentTime依赖:
添加几个变量来记录播放状态:
private var accumulatedPlayTime: TimeInterval = 0 // 累计播放时长 private var lastPlayStartTime: Date? // 上次开始播放的时间戳
修改play()和pause()方法:
func play() { print("Tries playing") guard player.prepareToPlay(), player.play() else { playbackState = .failed return } print("Playing OK") // 记录开始播放的时间 lastPlayStartTime = Date() setTimer(true) playbackState = .playing } func pause() { player.pause() // 累加播放时长 if let startTime = lastPlayStartTime { accumulatedPlayTime += Date().timeIntervalSince(startTime) lastPlayStartTime = nil } setTimer(false) playbackState = .paused }
修改Timer的回调逻辑,用自己计算的时间替代player.currentTime:
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in var currentPlayTime = self.accumulatedPlayTime // 如果正在播放,加上从上次开始到现在的时长 if let startTime = self.lastPlayStartTime { currentPlayTime += Date().timeIntervalSince(startTime) } self.currentPlaybackTime = currentPlayTime print("Delegate newTime: \(currentPlayTime)") })
这个方案的优势是完全绕过了AVAudioPlayer的状态问题,时间计算更可靠,适合对播放进度精度要求高的场景。
额外注意事项
- 确保你的音频会话类别设置为
.playback,并且开启了后台播放权限(在Info.plist中添加UIBackgroundModes并包含audio) - 避免在后台频繁调用AVAudioPlayer的属性,可能会加剧状态同步问题
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

