Skip to content

Session

ShenYj edited this page Nov 1, 2024 · 2 revisions

AVAudioSession

.

如果 App 中需要使用到音频播放类功能,往往需要使用到 AVAudioSession 来配合

通常情况下一个 App 中的音频行为可能都是固定的一次设置就可以了,比如独占这个 Session, 当从其他音频播放类的 App 切换到自己的应用时, 终端其他 App 的播放行为,然后播放我们自己的 App,若针对不同模块、不同的音频来源等想要达到不同的效果,就比如某些音频是独占的、不随着 App 进入后台、锁屏而中断播放,而某些音频当 App 进入后台、锁屏时中断,手机开启静音模式而静音时,就一定离不开 AudioSession 的设置,有关更多关于 AVAudioSession 的描述和介绍可以参考底部的资料

AVAudioSession 关键的三个参数

  • Category
  • Options
  • Mode

AVAudioSession.Category

ambient              = 'ambi',  --> 混音播放,可以与其他音频应用同时播放
soloAmbient          = 'solo',  --> 独占播放
playback             = 'medi',  --> 后台播放,独占
record               = 'reca',  --> 录音模式
playAndRecord        = 'plar',  --> 播放和录音,此时可以录音也可以播放
multiRoute           = 'proc',  --> 硬件解码音频,此时不能播放和录制

.ambient/.soloAmbient 锁屏都会静音, 区别是不支持混音的App在 .soloAmbient 下会中断, 而 .ambient 下不会
还有个 audioProcessing 已经过期废弃了
.playback 应该是用的最广泛的

AVAudioSession.CategoryOptions

mixWithOthers                           --> 是否可以和其他后台App进行混音   适用Category: playAndRecord, playback, or multiRoute
duckOthers                              --> 是否压低其他App声音           适用Category: playAndRecord, playback, or multiRoute
allowBluetooth                          --> 是否支持蓝牙耳机              适用Category: playAndRecord or record
defaultToSpeaker                        --> 是否默认用免提声音             适用Category: playAndRecord
interruptSpokenAudioAndMixWithOthers    --> 与其他音频混音播放             适用Category:  playAndRecord, playback, or multiRoute
allowBluetoothA2DP                      --> 支持蓝牙A2DP耳机
allowAirPlay                            --> 支持AirPlay
overrideMutedMicrophoneInterruption     -->    适用Category:   `@available(iOS 14.5, *)`

AVAudioSession.Mode

default         --> 默认
gameChat        --> 游戏录制,由GKVoiceChat自动设置,无需手动调用    适用Category: playAndRecord
measurement     --> 最小系统                                    适用Category: playback, record, or playAndRecord
moviePlayback   --> 视频播放                                    适用Category: playback
spokenAudio     --> 用于播放连续语音音频的应用程序,如播客或有声读物
videoChat       --> 视频通话                                    适用Category: playAndRecord or record
videoRecording  --> 视频录制                                    适用Category: record and playAndRecord
voiceChat       --> VoIP                                       适用Category: playAndRecord
voicePrompt     --> 文本到语音播放音频的模式

需要注意的是三个三处并不是随意搭配的,错误的搭配将不会起到作用

除了这三个关键参数外,某些方案下我们还需要配合中断通知进行一些处理

  • e.g.

    // 监听事件中断通知
    _ = NotificationCenter.default
        .rx.notification(AVAudioSession.interruptionNotification)
        .subscribe(with: self, onNext: { (owner, notification) in
            owner._interruption(Notification: notification)
        })
    
    // 其他App占用/解除占用AVAudioSession通知
    _ = NotificationCenter.default
        .rx.notification(AVAudioSession.silenceSecondaryAudioHintNotification)
        .subscribe(with: self, onNext: { (owner, notification) in
            owner._silenceSecondaryAudioHint(notification: notification)
        })
    // 路线改变通知(如,扬声器切换为耳机.也就是耳机插入设备)
    _ = NotificationCenter.default
        .rx.notification(AVAudioSession.routeChangeNotification)
        .subscribe(with: self, onNext: { (owner, notification) in
            log.verbose(" 音频控制 <<<<<<< 路线改变通知(如,扬声器切换为耳机.也就是耳机插入设备) \(#function)")
        })
    fileprivate func _interruption(Notification notification: Notification) {
        
        guard let userInfo = notification.userInfo else { return }
        
        /// AVAudioSession.InterruptionType
        guard let interruptionTypeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
        let interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionTypeValue)
        else { return }
        
        switch interruptionType {
            case .began:
            
            log.verbose(" 音频控制 <<<<<<< 中断开始 --> 暂停播放")
            var isAnotherAudioSuspend = false // 是否是被其他音频会话打断
            if #available(iOS 14.5, *) {
                // iOS 14.5之后使用InterruptionReasonKey
                guard let reasonKey = userInfo[AVAudioSessionInterruptionReasonKey] as? UInt else { return }
                let reason = AVAudioSession.InterruptionReason(rawValue: reasonKey)
                //log.debug(" 音频控制 <<<<<<< 中断开始 --> reason: \(reason)")
                switch reason {
                    case .default:
                    log.debug(" 音频控制 <<<<<<< 中断开始 --> 因为另一个会话被激活,音频中断")
                    isAnotherAudioSuspend = true
                    case .appWasSuspended:
                    log.debug(" 音频控制 <<<<<<< 中断开始 --> 由于APP被系统挂起,音频中断")
                    case .builtInMicMuted:
                    log.debug(" 音频控制 <<<<<<< 中断开始 --> 音频因内置麦克风静音而中断(例如iPad智能关闭套iPad's Smart Folio关闭)")
                    default:
                    log.debug(" 音频控制 <<<<<<< 中断开始 --> default")
                }
            } else {
                // iOS 10.3-14.5,InterruptionWasSuspendedKey为true表示中断是由于系统挂起,false 是被另一音频打断
                if let suspendedNumber = userInfo[AVAudioSessionInterruptionWasSuspendedKey] as? NSNumber {
                    isAnotherAudioSuspend = !suspendedNumber.boolValue
                }
            }
            
            if isAnotherAudioSuspend { log.debug(" 音频控制 <<<<<<< 因被其他会话打断") }
            
            case .ended:
            guard let interruptionOptionValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
            let interruptionOption = AVAudioSession.InterruptionOptions(rawValue: interruptionOptionValue)
            
            switch interruptionOption {
                case .shouldResume:
                log.verbose(" 音频控制 <<<<<<< 中断结束 --> shouldResume ")
                default:
                log.verbose(" 音频控制 <<<<<<< 另一个音频会话的中断已结束,但应用程序此时不满足恢复其音频会话 ")
            }
            
            @unknown default:   log.error(" 音频控制 <<<<<<< 未知情况")
        }
    }
    fileprivate func _silenceSecondaryAudioHint(notification: Notification) {
    
    guard let userInfo = notification.userInfo,
    let typeValue = userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
    let type = AVAudioSession.SilenceSecondaryAudioHintType(rawValue: typeValue) else { return }
    
    switch type {
        case .begin:
        // Other app audio started playing - mute secondary audio
        log.debug(" 音频控制 <<<<<<< silenceSecondaryAudioHintNotification  silenceSecondaryAudioHintNotification begin")
        case .end:
        // Other app audio stopped playing - restart secondary audio
        log.debug(" 音频控制 <<<<<<< silenceSecondaryAudioHintNotification  silenceSecondaryAudioHintNotification end")
        @unknown default:
        log.debug(" 音频控制 <<<<<<< silenceSecondaryAudioHintNotification  silenceSecondaryAudioHintNotification @unknown")
    }
    }

    这部分代码不多,唯一让我头疼的是刚从 OC 切 Swift 时, 换成 Swift 接收通知通过 Key 取值后接收类型的时候是让多花了点时间,还有需要注意的是在查看这部分资料时,针对不同的技术方案中断恢复的处理上有些不同,这块还没有验证实践过,目前使用过的类型只有 .playback.playAndRecord.soloAmbient

外部资料

Getting Started

Social

Clone this wiki locally