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下载