AVFoundation(二)-AVAudioPlayer音频播放

我们首先先看几个相关概念

音频会话

音频会话在应用程序和操作系统之间扮演中间人的角色,提供简单的方法是OS得知应用程序应该如何与iOS音频环境进行交互

可以通过 AVAudioSession.sharedInstance() 来操作系统提供的音频会话

音频会话的分类

AVFoundation 有7中分类来描述应用程序所使用的音频行为

分类 作用 是否允许混音 音频输入 音频输出
Ambient 游戏、应用程序 ⭕ ⭕
Solo Ambient(默认) 游戏、应用程序 ⭕
Playback 音频和视频播放器 可选择 ⭕
Record 录音机、音频捕捉 ⭕ ⭕
Play and Record VoIP、语音聊天 可选 ⭕ ⭕
Audio Processing 离线会话和处理
Multi-Route 使用外部硬件的高级A/V 应用程序 ⭕ ⭕

其中一些分类可以通过 options和 modes 进行自定义开发

配置音频会话

音频会话在应用程序的声明周期中是可以修改的,但通常我们只对其进行一次配置. 通常在下面方法中进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
try audioSession.setActive(true)
} catch let error {
print("设置音频会话失败 \(error)")
}
return true
}

注意修改category之后需要调用 setActive来激活修改

AVAudioPlayer

AVAudioPlayer构建于Core Audio 中的 C-Based Audio Queue Services 的顶层,所以AVAudioPlayer可以提供所有 Audio Queue Services 的功能

除非从网络流中播放音频,需要访问原始音频样本或者需要非常低的延迟,否则AVAudioPlayer都能胜任

AVAudioPlayer的简单使用

1
2
3
4
5
6
7
8
9
10
11
let fileUrl = Bundle.main.url(forResource: "rock", withExtension: "mp3")
do {
player = try AVAudioPlayer(contentsOf: fileUrl)
} catch let error {
print("error with \(error)")
}
if let player = player {
player.prepareToPlay()
}

调用prepareToPlay方法会取得需要的音频硬件并预加载到Audio Queue 的缓冲区。调用play方法会隐性激活该方法。

AVAudioPlayer的播放控制

play、stop、prepareToPlay、pause

  • play方法可以立即播放音频
  • pause可以暂停播放
  • stop可以停止播放
  • stop方法会撤销调用prepareToPlay方法所做的设置

pan

立体播放声音修改,取值范围 -1.0(极左) —— 1.0(极右),0.0居中

volume

修改声音大小,取值范围 0.0——1.0

rate

修改播放速率(在不改变音调的情况下),取值范围 0.5——2.0 即 半速到两倍速

numberOfLoops

循环次数,设置大于0的值可以指定播放器循环n次,如果值为 -1 可以无限循环

设备锁定状态下继续播放音频

需要设置info.plist

info.plist设置

添加 Required background modes 并在其下添加 App plays audio or streams audio/video using AirPlay

处理FaceTime、电话等引起的中断

我们需要注册一个通知来监听 AVAudioSessionInterruption 。

1
2
3
4
NotificationCenter.default.addObserver(self,
selector: #selector(handleInterruption(notification:)),
name: Notification.Name.AVAudioSessionInterruption,
object: AVAudioSession.sharedInstance())

通知中userinfo字典会包含相关信息。我们可以通过其做一些判断。

首先通过 AVAudioSessionInterruptionTypeKey 可以取出中断类型 AVAudioSessionInterruptionType 的枚举值。

如果中断类型为 ended userinfo字典会包含一个 AVAudioSessionInterruptionOptions 值来表明音频会话是否已经重新激活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@objc private func handleInterruption(notification:Notification){
guard let info = notification.userInfo ,
let type = info[AVAudioSessionInterruptionTypeKey] as? AVAudioSessionInterruptionType else { return }
if type == .began {
stop()
if let delegate = self.delegate {
delegate.playbackStop()
}
}else{
guard let options = info[AVAudioSessionInterruptionOptionKey] as? AVAudioSessionInterruptionOptions else { return }
if options == .shouldResume{
play()
if let delegate = self.delegate {
delegate.playbackBegan()
}
}
}
}

处理线路改变引起的中断(耳机插拔)

在iOS设备上添加或移除音频输入、输出设备时 AVAudioSession会广播一个名为 AVAudioSessionRouteChange 的通知

同样可以通过info字典来判断用户行为。例如可以在耳机线断开后停止播放音乐等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NotificationCenter.default.addObserver(self,
selector: #selector(handleRouteChange(notification:)),
name: Notification.Name.AVAudioSessionRouteChange,
object: AVAudioSession.sharedInstance())
///处理线路改变
@objc private func handleRouteChange(notification:Notification){
guard let info = notification.userInfo ,
let reason = info[AVAudioSessionRouteChangeReasonKey] as? AVAudioSessionRouteChangeReason
else { return }
if reason == .oldDeviceUnavailable {
guard let previesRoute = info[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription,
let previousOutput = previesRoute.outputs.first else { return }
if previousOutput.portType == AVAudioSessionPortHeadphones {
stop()
if let delegate = self.delegate {
delegate.playbackStop()
}
}
}
}

演示demo下载