Swift应用中断开音频设备时如何停止音频播放?
处理Swift音频应用的路由变化问题
核心思路
不需要维护headphonesConnected这类状态变量,直接通过解析路由变化通知里的前后路由信息,判断是否断开了外部播放设备(耳机、蓝牙、CarPlay等),进而触发停止播放的逻辑。
步骤1:配置音频会话并注册路由通知
首先要正确设置音频会话类别(比如.playback,支持后台播放),并注册AVAudioSessionRouteChangeNotification通知来监听路由变化:
import AVFoundation class AudioPlayerManager { private var audioPlayer: AVPlayer? private var routeChangeObserver: NSObjectProtocol? init() { // 配置音频会话 let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback, mode: .default) try session.setActive(true) } catch { print("音频会话配置失败: \(error)") } // 注册路由变化通知 routeChangeObserver = NotificationCenter.default.addObserver( forName: AVAudioSession.routeChangeNotification, object: nil, queue: .main ) { [weak self] notification in self?.handleRouteChange(notification: notification) } } deinit { // 移除通知观察者 if let observer = routeChangeObserver { NotificationCenter.default.removeObserver(observer) } } // 加载网络电台URL并开始播放 func startStreaming(with url: URL) { audioPlayer = AVPlayer(url: url) audioPlayer?.play() } }
步骤2:处理路由变化事件,判断外部设备断开
在回调里解析路由变化的原因,重点处理.oldDeviceUnavailable(旧设备不可用,即外部设备断开)的情况,通过对比之前的路由判断是否存在过外部输出设备:
extension AudioPlayerManager { private 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 .oldDeviceUnavailable: // 获取断开前的路由信息 guard let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription else { return } // 定义所有需要监听的外部输出设备类型 let externalOutputPorts: Set<AVAudioSession.Port> = [ .headphones, .bluetoothA2DP, // 蓝牙立体声设备 .bluetoothHFP, // 蓝牙通话设备(如车载蓝牙) .carPlay, .lineOut ] // 检查之前的路由是否包含外部输出设备 let hadExternalOutput = previousRoute.outputs.contains { outputPort in externalOutputPorts.contains(outputPort.portType) } // 如果之前有外部设备,现在断开了,停止播放 if hadExternalOutput { audioPlayer?.pause() // 也可根据需求调用stop()并重置播放器状态 } default: // 其他路由变化场景(如插入设备),按需处理 break } } }
关键说明
- 为什么不用
headphonesConnected:状态变量容易出现同步问题(比如系统路由变化没及时更新变量),直接通过previousRoute获取断开前的路由状态更可靠。 - 覆盖所有外部设备:通过
AVAudioSession.Port枚举的不同值,把所有需要监听的外部输出类型加入集合即可,蓝牙设备、CarPlay、有线输出等都能覆盖。 - 遵循iOS规范:当外部设备断开时,系统默认会把音频切换到内置扬声器,此时主动停止播放符合用户预期,也符合iOS的交互规范。
内容的提问来源于stack exchange,提问作者Federico Chiesa
相关产品推荐
相关产品推荐

