做一个音乐类或播客类App的开发者,大概率遇到过这样的反馈:用户一边用自己的App听歌,一边打开另一个App时,声音突然被掐断了。更让人困惑的是,有些机型上还伴随VoiceOver朗读被打断、或者系统提示音频会话被占用的情况。这类问题的根源通常不在播放器本身,而在AVAudioSession的配置上,尤其是与系统的增强语音(Enhanced Voice)等辅助功能音频之间的冲突。这篇文章把这套机制掰开揉碎讲清楚。

一、先弄明白AVAudioSession的抢占机制
iOS把所有App的音频行为统一交给一个系统级的中介管理,这就是AVAudioSession。每个App在播放声音之前,实际上都是向这个中介申请一个类别(Category)和一组选项(Options)。类别决定了你这个App的音频定位:是播放、录音还是通话;而选项则是在类别基础上的微调,比如是否与其他App混音、是否降低其他音频的音量。
问题的关键在于,AVAudioSessionCategoryPlayback这个类别默认是独占的。也就是说,当你的App以这个类别激活音频会话时,系统会主动中断其他正在播放音频的应用,比如用户正在听的网易云或者Apple Music,会被直接暂停。反过来,如果你的App正在后台播放,另一个App也用了不带混音选项的playback类别,你的音频同样会被中断。这种设计对普通音乐App来说符合直觉,但在某些场景下就成了bug。
还有一层容易被忽视的因素:辅助功能音频。当用户开启了VoiceOver,系统需要朗读屏幕内容;当设备使用增强语音相关功能时,系统也需要占用音频通道。如果你的App配置了非混音的播放类别,就可能与这些系统级音频产生冲突,表现为朗读卡顿、音频被切断,甚至在控制中心出现异常的播放状态。
二、MixWithOthers选项的正确用法
解决冲突最直接的手段是给playback类别加上AVAudioSessionCategoryOptionMixWithOthers选项。加上之后,你的App音频会与其他App的音频混合播放,谁也不会打断谁。配置代码如下:
#import <AVFoundation/AVFoundation.h>
- (void)setupAudioSession {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
// 使用播放类别,并开启与其他音频混音
[session setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&error];
if (error) {
NSLog(@"音频会话配置失败: %@", error.localizedDescription);
}
[session setActive:YES error:&error];
}
Swift版本的写法基本一致,只是语法不同:
func setupAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, options: [.mixWithOthers])
try session.setActive(true)
} catch {
print("音频会话配置失败: \(error)")
}
}
这里有几个细节需要注意。第一,混音选项会带来一个副作用:你的App不再独占音频焦点,锁屏和控制中心的播放控件行为会发生变化,某些情况下锁屏界面上不会显示你的App的播放信息。第二,如果你同时需要后台播放,别忘了在Xcode的Signing and Capabilities中勾选Background Modes里的Audio, AirPlay, and Picture in Picture,否则退到后台几秒后声音照样会停。
与MixWithOthers相对的还有一个常用选项duckOthers,它的作用不是打断别人,而是把其他App的音量临时压低,等你的音频播完再恢复。典型的应用场景是导航播报、语音助手提示音。这两个选项可以按需选择,但要注意mixWithOthers和duckOthers同时设置时,duckOthers只有在mixWithOthers也生效的前提下才有意义,单独设置duckOthers而App持有独占音频焦点时,其他音频本来就会被中断,压低音量就无从谈起了。
三、增强语音与辅助功能音频的冲突处理
增强语音类功能和VoiceOver有一个特殊地位:它们的音频属于系统辅助功能通道,系统会尽量保证它们优先。当你的App使用了不带混音的playback类别,又恰好在系统需要朗读内容时占着音频焦点,就会出现朗读延迟、音频突然暂停等问题。这在无障碍测试中是很容易被扣分的点。
针对这类场景,推荐的策略是动态切换音频会话配置。比如你的App平时正常独占播放,一旦检测到VoiceOver正在运行(可以通过UIAccessibility.isVoiceOverRunning判断),就切换到带mixWithOthers的配置,让系统朗读和你的音频并行。示例代码:
func adaptAudioSessionForAccessibility() {
let session = AVAudioSession.sharedInstance()
do {
if UIAccessibility.isVoiceOverRunning {
// VoiceOver开启时,允许混音避免打断朗读
try session.setCategory(.playback, options: [.mixWithOthers])
} else {
// 常规场景使用duckOthers,播报时压低其他音频
try session.setCategory(.playback, options: [.duckOthers])
}
try session.setActive(true)
} catch {
print("动态切换音频会话失败: \(error)")
}
}
// 监听VoiceOver状态变化,及时调整
NotificationCenter.default.addObserver(
self,
selector: #selector(voiceOverStatusChanged),
name: UIAccessibility.voiceOverStatusDidChangeNotification,
object: nil
)
除了主动适配,还要正确处理音频中断。当电话来电、Siri唤醒或其他高优先级音频出现时,系统会向你的App发送中断通知。很多开发者只注册了中断开始的通知,却忘了在中断结束时重新激活会话并恢复播放,导致用户反馈声音莫名消失。完整的处理应该是同时监听AVAudioSession.interruptionNotification和AVAudioSession.routeChangeNotification,在中断结束的回调里根据shouldResume选项决定是否恢复播放。
四、常见踩坑点与排查思路
第一个坑是时机问题。setCategory必须在音频真正开始播放之前完成,如果播放器已经初始化并且会话已激活,中途改类别在部分系统版本上表现不稳定。建议在App启动阶段就完成会话配置,而不是等到点击播放按钮才设置。
第二个坑是三方播放器库。像AVPlayer、IJKPlayer、音频直播SDK等,有些内部会自行设置音频会话,覆盖你在外层的配置。排查时可以在配置完成后打印session.category和session.categoryOptions,确认最终生效的值是不是你期望的。如果被SDK覆盖,要么找SDK提供的配置入口,要么在播放前重新设置一次。
第三个坑是后台播放与混音的组合行为。开启了mixWithOthers的后台音频,在被其他独占音频打断时的表现和独占模式不同:你的音频不会被暂停,而是继续与对方混在一起播放。这对播客类App可能正是想要的效果,但对音乐App来说,如果产品逻辑要求被打断后暂停,就需要自己监听其他App的播放状态(比如通过MPNowPlayingInfoCenter和远程控制事件的变化间接判断)来做暂停处理。
最后建议在真机上做一轮完整的场景测试:开启VoiceOver、来电、唤醒Siri、切换到其他音乐App、锁屏后拔插耳机,把这些路径都走一遍。音频问题往往在模拟器上无法复现,只有真机才能暴露出会话抢占的真实行为。把这几个环节都覆盖到,后台播放与增强语音的冲突基本就能彻底解决了。
AVAudioSessionMixWithOthersiOS音频开发修改时间:2026-09-13 02:16:39