You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
        }
    }
}

关键说明

  1. 为什么不用headphonesConnected:状态变量容易出现同步问题(比如系统路由变化没及时更新变量),直接通过previousRoute获取断开前的路由状态更可靠。
  2. 覆盖所有外部设备:通过AVAudioSession.Port枚举的不同值,把所有需要监听的外部输出类型加入集合即可,蓝牙设备、CarPlay、有线输出等都能覆盖。
  3. 遵循iOS规范:当外部设备断开时,系统默认会把音频切换到内置扬声器,此时主动停止播放符合用户预期,也符合iOS的交互规范。

内容的提问来源于stack exchange,提问作者Federico Chiesa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 09:40:29