-
Notifications
You must be signed in to change notification settings - Fork 4
Session
如果 App 中需要使用到音频播放类功能,往往需要使用到 AVAudioSession 来配合
通常情况下一个 App 中的音频行为可能都是固定的一次设置就可以了,比如独占这个 Session, 当从其他音频播放类的 App 切换到自己的应用时, 终端其他 App 的播放行为,然后播放我们自己的 App,若针对不同模块、不同的音频来源等想要达到不同的效果,就比如某些音频是独占的、不随着 App 进入后台、锁屏而中断播放,而某些音频当 App 进入后台、锁屏时中断,手机开启静音模式而静音时,就一定离不开 AudioSession 的设置,有关更多关于 AVAudioSession
的描述和介绍可以参考底部的资料
- Category
- Options
- Mode
ambient = 'ambi', --> 混音播放,可以与其他音频应用同时播放
soloAmbient = 'solo', --> 独占播放
playback = 'medi', --> 后台播放,独占
record = 'reca', --> 录音模式
playAndRecord = 'plar', --> 播放和录音,此时可以录音也可以播放
multiRoute = 'proc', --> 硬件解码音频,此时不能播放和录制
.ambient/.soloAmbient
锁屏都会静音, 区别是不支持混音的App在.soloAmbient
下会中断, 而.ambient
下不会
还有个audioProcessing已经过期废弃了
.playback
应该是用的最广泛的
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, *)`
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
ShenYj.github.io - 简书地址 - 返回首页
-
Apple
Common
蓝牙
LBS
音视频
- AVAudioSessionchange_route
- 切换线路
- StreamingKit
- Audio Unit 基础
OC 与 Swift 混编
Object-C
- 代码混淆
- autoreleasepool
- 忽略编译器(clang)警告
- 定时器
- 锁
- RunLoop
- block
- NS_REFINED_FOR_SWIFT
- NS_CLOSED_ENUM
- NS_TYPED_ENUM、NS_STRING_ENUM
- NS_TYPED_EXTENSIBLE_ENUM、NS_EXTENSIBLE_STRING_ENUM
- 关键字nonnull和nullable
- class、objc_getClass和object_getclass方法区别
- isKindOfClass和isMemberOfClass
- 应用程序的加载
- non-lazy classes & lazy classes
- load方法
- initialize方法
- 方法的本质
- 类型编码
- self和super
- 类的内存分析
Swift
- precondition
- 权限控制
- Array常用Api
- String初始化、定义
- String常用Api
- String截取演练
- Set定义、创建
- Set访问和修改
- Dictionary操作
- Dictionary和KeyValuePairs
- Dictionary与String转换
- 常用高阶函数
- enum原始值
- enum关联值
- enum遍历
- 递归enum
- enum内存分配
- 指针
- for循环
- break跳出循环
- 变量名与关键字冲突
- 类的定义
- 类的继承和初始化
- 关键字: final
- 关键字: mutating
- 关键字: lazy
- 修饰类方法的关键字: static
- 关键字: final、dynamic、objc和_dynamicReplacement
- 关键字:@dynamicMemberLookup和@dynamicCallable
- 关键字: propertyWrapper
- 自定义运算符
- 下标: subscript
- 扩展: extension
- 协议: protocol
- 协议和扩展
- 为什么需要泛型
- 泛型函数定义
- 泛型类型
- 泛型的类型约束
- 关联类型
- 为泛型定义要求
- 泛型下标
- 多线程
- Attributes
- 错误处理
- Codable
- DispatchSourceTimer
- Swift 5.x 演练: 更多功能编辑页
- Swift 5.x 类库收集
- 单元测试笔记
- 实例对象内存结构
- 元类型、Type、Self
- frozen
- convention
- Swift(5.3.2)源码编译
- SQLite.Swift类库演练
- Swift 5.5 关键字: async/await
- Swift 5.5 新特性: Continuations
- Swift 5.5 新特性: Actor
- Swift 方法调度
- Swift Mirror
- Swift 关键字: @_silgen_name
- Swift 关键字: @_disfavoredOverload
- swiftmodule
- Swift 5.6 新特性: Type placeholders
- Swift 5.6 新特性: #unavailable
- Swift 5.6 新特性: CodingKeyRepresentable
- Swift 5.6 新特性: existential any
- Swift 5.7 新特性: if-let/guard 语法简化
- Swift 5.7 新特性: Multi-statement closure type inference
- Swift 5.8 新特性: @backDeployed
- Swift 5.9 新特性: if switch expressions
- Swift 6.0 新特性:@preconcurrency
RxSwift
macOS - AppKit
-
iOS Assembly(ARM64)
-
C++
C++ 基础
- cout、cin
- 函数重载
- 默认参数
- extern "C"
- pragma once
- inline function
- const
- Reference
- 汇编
- 类和对象
- 堆空间内存管理
- Constructor
- Destructor
- 成员变量初始化
- 声明与实现分离
- namespace
- 继承
- 访问权限
- 初始化列表
- 多态:虚函数
- 多态:虚函数实现原理
- 多态:虚析构函数
- 多态:纯虚函数
- 多态:抽象类
- 多继承
- static
- static: 单例模式
- const 成员
- 引用类型成员
- 拷贝构造函数
- 调用父类的拷贝构造函数
- 浅拷贝、深拷贝
- 对象型参数和返回值
- 匿名对象
- 隐式构造
- 编译器自动生成的构造函数
- 友元
- 内部类
- 局部类
- 运算符重载
- 模板
- 类型转换
- C++标准
- Lambda
- 异常
- 智能指针
-
Flutter
Dart
Flutter
-
Go
Go 基础
-
Ruby
Ruby 基础
-
React-Native
React-Native
-
工具篇
-
Swift Package Manager
-
自动化
-
TroubleShooting
-
扩展