如何正确处理AVAudioEngine抢占其他音频应用会话时的卡顿问题
解决AVAudioEngine抢占音频会话时的卡顿问题
核心问题分析
你的卡顿主要来自两个点:
- 音频会话激活逻辑不完善,抢占其他应用音频时没有正确处理系统交互流程
- 过早启动AVAudioEngine且依赖重启引擎恢复播放,触发了系统层面的资源重载延迟
针对性解决方案
1. 优化音频会话抢占逻辑
修改音频会话激活代码,明确抢占行为并处理错误,避免静默失败:
func activateAudioSession() { let session = AVAudioSession.sharedInstance() do { // 设置播放类别,确保后台播放优先级 try session.setCategory(.playback, mode: .default) // 激活会话并抢占其他应用音频,同时通知其他应用暂停 try session.setActive(true, options: .notifyOthersOnDeactivation) } catch { print("音频会话激活失败: \(error.localizedDescription)") } }
2. 避免过早启动AVAudioEngine
移除init中的audioEngine.start(),改为按需启动引擎,减少不必要的初始化开销:
func startAudioEngineIfNeeded() { guard !audioEngine.isRunning else { return } do { try audioEngine.start() } catch { print("音频引擎启动失败: \(error.localizedDescription)") } }
3. 重构播放按钮点击逻辑
点击播放时按「激活会话→启动引擎→恢复播放」的顺序执行,确保每一步资源就绪:
func triggerPlay() { activateAudioSession() startAudioEngineIfNeeded() playerNode.play() musicPlayerControlsManager.isPlaying = true }
4. 修复中断恢复逻辑
中断结束后先重新激活会话,再恢复播放,避免直接操作节点导致的资源冲突:
@objc func handleInterruption(notification: Notification) { guard let userInfo = notification.userInfo, let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return } switch type { case .began: print("中断开始") musicPlayerControlsManager.isPlaying = false playerNode.pause() case .ended: print("中断结束") guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) if options.contains(.shouldResume) { activateAudioSession() startAudioEngineIfNeeded() musicPlayerControlsManager.isPlaying = true playerNode.play() } else { musicPlayerControlsManager.isPlaying = false } default: break } }
5. 新增蓝牙设备路由适配
监听路由变化,确保AirPods等设备切换后音频流畅恢复:
func setupNotifications() { let nc = NotificationCenter.default nc.addObserver(self, selector: #selector(handleInterruption), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) nc.addObserver(self, selector: #selector(handleRouteChange), name: AVAudioSession.routeChangeNotification, object: AVAudioSession.sharedInstance()) } @objc func handleRouteChange(notification: Notification) { guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } switch reason { case .newDeviceAvailable, .oldDeviceUnavailable: if musicPlayerControlsManager.isPlaying { startAudioEngineIfNeeded() playerNode.play() } default: break } }
关键注意事项
- 绝对避免重启AVAudioEngine,优先通过
pause()/play()控制节点状态 - 所有音频相关操作尽量放在后台线程执行,避免阻塞主线程
- 不要忽略
try?隐藏的错误,必须捕获并处理音频会话和引擎的启动异常
内容的提问来源于stack exchange,提问作者User95797654974
相关产品推荐
相关产品推荐

