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

Swift如何在VC首次展示时检测耳机是否已插入

问题根因

AVAudioSession.routeChangeNotification 只会在音频路由发生实际变更的瞬间触发,VC首次加载、音频会话首次激活的过程中如果没有发生路由切换,系统不会投递这个通知。仅靠注册通知监听,只能捕获注册完成之后发生的耳机插拔事件,无法获取注册前就已经存在的耳机插入状态,这就是首次加载时检测失效的核心原因。

修复方案

核心逻辑是:不要完全依赖通知回调获取耳机状态,在完成音频会话激活、注册路由监听之后,主动查询一次当前的音频路由,做初始状态校验,同时抽离通用的状态判断、UI更新逻辑,供初始检测和通知回调复用。

1. 抽离通用状态检测方法

把耳机状态判断逻辑从通知回调中独立出来,方便多处调用:

/// 检测当前是否有耳机类设备接入
func isHeadphoneConnected() -> Bool {
    let currentOutputs = AVAudioSession.sharedInstance().currentRoute.outputs
    for output in currentOutputs {
        // 可根据业务需求调整需要识别的端口类型,以下覆盖常见有线/蓝牙耳机
        switch output.portType {
        case .headphones, .bluetoothHFP, .bluetoothA2DP:
            return true
        default:
            continue
        }
    }
    return false
}

2. 抽离UI更新逻辑

把状态变更后的UI操作独立,保证初始加载和后续路由变更的UI表现一致:

func updateHeadphoneRelatedUI(isConnected: Bool) {
    if isConnected {
        print("耳机已接入")
        view.backgroundColor = .blue
    } else {
        print("耳机未接入")
        view.backgroundColor = .white
    }
}

3. 修正初始化流程

去掉重复的通知注册逻辑,在音频会话激活成功后,完成监听注册+初始状态检测:

var didSetupAudioSession = false
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // 保证音频会话初始化逻辑只执行一次
    guard !didSetupAudioSession else { return }
    didSetupAudioSession = true

    do {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, mode: .default, options: [
            .duckOthers,
            .allowBluetoothA2DP,
            .allowAirPlay,
            .mixWithOthers,
            .defaultToSpeaker
        ])
        try session.setActive(true)
        
        // 注册路由变化监听
        setupRouteChangeObserver()
        // 主动检测初始耳机状态
        updateHeadphoneRelatedUI(isConnected: isHeadphoneConnected())
    } catch {
        print("音频会话初始化失败: \(error.localizedDescription)")
    }
}

func setupRouteChangeObserver() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleAudioRouteChange),
        name: AVAudioSession.routeChangeNotification,
        object: nil
    )
}

deinit {
    // 移除观察者,避免内存泄漏
    NotificationCenter.default.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: nil)
}

4. 简化通知回调逻辑

路由变更时直接调用通用检测方法,不要在回调内重复写状态判断逻辑,避免逻辑不一致:

@objc private func handleAudioRouteChange(notification: NSNotification) {
    guard let userInfo = notification.userInfo,
          let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
          let changeReason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
        return
    }
    
    // 仅处理设备接入、移除两类场景,其余路由变化忽略
    switch changeReason {
    case .newDeviceAvailable, .oldDeviceUnavailable:
        updateHeadphoneRelatedUI(isConnected: isHeadphoneConnected())
    default:
        break
    }
}
注意事项
  • 不要重复注册路由监听,原代码在viewDidLayoutSubviews和viewDidAppear中两次调用注册方法,会导致同一次路由变化触发多次回调,只需要在音频会话激活成功后注册一次即可。
  • 初始状态检测必须在AVAudioSession激活成功之后执行,否则拿到的currentRoute信息可能不准确。
  • 如果需要区分AirPods、有线耳机、蓝牙音箱等不同设备类型,只需要调整isHeadphoneConnected方法中的端口类型判断规则即可。

内容的提问来源于stack exchange,提问作者Lance Samaria

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 00:39:17