当你在iPhone上点开一首歌曲,音频在内置扬声器中播放出来,此时有电话拨入,音乐会当即中止并处于暂停状态,此时听到的是手机呼叫的铃音。若是此时你挂掉电话,刚才的音乐声再次响起,当你插上耳机,音乐播放时音频输出到了耳机里。当听完这首音乐摘下耳机后,你会发现声音自动转回内置扬声器并处于暂停状态。objective-c
iOS系统提供了一个可管理的音频环境(managed audio environment),能够带给全部iOS用户很是好的用户体验,这一过程具体是如何实现的呢?这里会用到音频会话(audio session)。网络
音频会话在应用程序和操做系统之间扮演着中间人的角色。它提供了一种简单实用的方法使OS得知应用程序应该如何与iOS音频环境进行交互。你不须要了解与音频硬件交互的细节,只须要对应用程序的行为进行语义上的描述便可。这一点使得你能够指明应用程序的通常音频行为,并能够把对该行为的管理委托给音频会话,这样OS系统就能够对用户使用音频的体验进行适当的管理。session
全部iOS应用程序都具备音频会话,不管其是否使用。默认音频会话来自于如下一些预配置:app
默认音频会话提供了许多实用功能,可是在大多数状况下,你须要自定义音频会话来适配你本身应用程序的需求。oop
音频会话在应用程序的生命周期中是能够修改的,但一般咱们只对其配置一次,就是在应用程序启动时。那么,配置应用程序的最佳位置就是- (BOOL)application:didFinishLaunchingWithOptions:
方法。测试
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 回去音频会话单例
AVAudioSession *session = [AVAudioSession sharedInstance];
// 设置音频会话分类
if (![session setCategory:AVAudioSessionCategoryPlayback error:nil]) {
NSLog(@"设置音频会话失败");
}
// 激活音频会话
if (![session setActive:YES error:nil]) {
NSLog(@"激活音频会话失败");
}
return YES;
}
复制代码
音频播放时不少应用程序的常见需求,AV Foundation让这一功能的实现变得很是简单,这一点要归功于一个名为AVAudioPlayer的类。该类的实例提供了一种简单地从文本或内存中播放音频的方法。ui
AVAudioPlayer构建与Core Audio中的C-based Audio Queue Services的最顶层。因此它能够提供全部你在Audio Queue Services中所能找到的核心功能,好比播放、循环甚至音频计量,但使用的是Objective-C接口。除非你须要从网络中播放音频,须要访问原始音频样本或须要很是低的延时,不然AVAudioPlayer都能胜任。atom
有两种方法能够建立一个AVAudioPlayer,使用包含播放音频的内存版本的NSData或本地音频文件的NSURL。spa
@interface ViewController ()
@property (nonatomic, strong) AVAudioPlayer *player;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 从bundle中获取资源的NSURL实例
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"tqsh.mp3" withExtension:nil];
// 根据URL建立一个AVAudioPlayer实例
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (self.player) {
// 建议开发者,先调用这个方法
// 调用此方法将预加载缓冲区并获取音频硬件,这样作能够将调用play方法和听到输出声音之间的延时下降到最小
[self.player prepareToPlay];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.player play];
}
复制代码
播放实例包含了全部开发者指望的对播放行为进行控制的方法。调用play方法能够实现当即播放音频的功能,pause方法能够对播放暂停,stop方法能够中止播放行为。pause方法和stop方法在应用程序外面看来实现的功能都是中止当前的播放行为。下一时间咱们调用play方法,经过pause和stop方法中止的音频都会继续播放。这二者最主要的区别在于调用stop方法会撤销调用prepareToPlay时所作的设置,而调用pause方法不会。操作系统
除了前面描述的标准常规方法以外,开发者还可使用其余一些方法,以下:
需求:同步播放三个播放器,经过控制每一个播放器的音量等级和立体声方面的pan值将这些音乐混合,进而控制总体播放速率。
@interface AVAudioPlayerManager : NSObject
@property (nonatomic, assign, readonly, getter=isPlaying) BOOL playing;
- (void)play;
- (void)stop;
- (void)adjustRate:(CGFloat)rate;
- (void)adjustPan:(CGFloat)pan forPlayerAtIndex:(NSInteger)index;
- (void)adjustVolume:(CGFloat)volume forPlayerAtIndex:(NSInteger)index;
@end
复制代码
@interface AVAudioPlayerManager ()
@property (nonatomic, assign) BOOL playing;
@property (nonatomic, strong) NSArray *players;
@end
@implementation AVAudioPlayerManager
- (instancetype)init {
if (self = [super init]) {
AVAudioPlayer *guitarPlayer = [self createPlayerWithFileName:@"guitar"];
AVAudioPlayer *bassPlayer = [self createPlayerWithFileName:@"bass"];
AVAudioPlayer *drumsPlayer = [self createPlayerWithFileName:@"drums"];
_players = @[guitarPlayer, bassPlayer, drumsPlayer];
}
return self;
}
- (AVAudioPlayer *)createPlayerWithFileName:(NSString *)fileName {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:fileName withExtension:@"caf"];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (player) {
player.enableRate = YES;
player.numberOfLoops = -1;
[player prepareToPlay];
} else {
NSLog(@"建立player失败");
}
return player;
}
- (void)play {
if (!self.isPlaying) {
NSTimeInterval delayTime = [self.players[0] deviceCurrentTime] + 0.01;
for (AVAudioPlayer *player in self.players) {
[player playAtTime:delayTime];
}
self.playing = YES;
}
}
- (void)stop {
if (self.isPlaying) {
for (AVAudioPlayer *player in self.players) {
[player stop];
player.currentTime = 0.0;
}
self.playing = NO;
}
}
- (void)adjustPan:(CGFloat)pan forPlayerAtIndex:(NSInteger)index {
if ([self isValidIndex:index]) {
AVAudioPlayer *player = self.players[index];
player.pan = pan;
}
}
- (void)adjustVolume:(CGFloat)volume forPlayerAtIndex:(NSInteger)index {
if ([self isValidIndex:index]) {
AVAudioPlayer *player = self.players[index];
player.volume = volume;
}
}
- (void)adjustRate:(CGFloat)rate {
for (AVAudioPlayer *player in self.players) {
player.rate = rate;
}
}
- (BOOL)isValidIndex:(NSInteger)index {
return index == 0 || index < self.players.count;
}
复制代码
在上面这个例子中,咱们没有配置音频会话,因此咱们使用的系统默认的音频会话的配置。
以上两个操做并非咱们但愿的,咱们但愿切换响铃/静音开关继续播放音频而且锁屏后继续播放音频,因此咱们要设置音频会话。
- (BOOL)application:didFinishLaunchingWithOptions:
对音频会话进行配置,由于咱们的主要功能就是播放因此设置AVAudioSessionCategoryPlayback分类。- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
if (![audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]) {
NSLog(@"设置音频会话分类失败");
}
if (![audioSession setActive:YES error:nil]) {
NSLog(@"音频会话激活失败");
}
return YES;
}
复制代码
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
复制代码
中断在iOS设备中常常出现,在使用设备的过程当中常常会有诸如电话呼入、闹铃响起等状况。虽然iOS系统自己能够很好地处理这些事件。不过咱们仍须要针对这些状况作本身的处理。
按照上述的场景进行测试,你会发现,当中断发生时,播放中的音频会慢慢消失和暂停。这个效果是自动实现的,咱们没有作任何的处理。当另外一台手机的电话被挂断,会出现一些问题,播放/中止功能消失,音频也再也不继续播放。
- (instancetype)init {
if (self = [super init]) {
AVAudioPlayer *guitarPlayer = [self createPlayerWithFileName:@"guitar"];
AVAudioPlayer *bassPlayer = [self createPlayerWithFileName:@"bass"];
AVAudioPlayer *drumsPlayer = [self createPlayerWithFileName:@"drums"];
_players = @[guitarPlayer, bassPlayer, drumsPlayer];
// 注册音频会话中断通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
}
return self;
}
复制代码
- (void)handleInterruption:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
NSLog(@"%@", info);
// 获取音频会话打断类型
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
NSLog(@"开始打断");
[self stop];
// 中断中止 交给代理处理相关逻辑
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerManagerPlaybackStopped:)]) {
[self.delegate audioPlayerManagerPlaybackStopped:self];
}
} else {
NSLog(@"结束打断");
AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (options == AVAudioSessionInterruptionOptionShouldResume) { // 音频会话从新激活
[self play];
// 从新激活 交给代理 处理相关逻辑
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerManagerPlaybackBegan:)]) {
[self.delegate audioPlayerManagerPlaybackBegan:self];
}
}
}
}
复制代码
@protocol AVAudioPlayerManagerDelegate <NSObject>
@optional
/// 中断 -> 中止播放
- (void)audioPlayerManagerPlaybackStopped:(AVAudioPlayerManager *)manager;
/// 结束中断安 -> 开始播放
- (void)audioPlayerManagerPlaybackBegan:(AVAudioPlayerManager *)manager;
@end
复制代码
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码
在iOS设备上添加或移除音频输入、输出线路时,会发生线路改变。好比用户插入和拔出耳机。当这些事件发生时,音频会根据状况改变输入或输出线路,同时AVAudioSession会广播一个描述该变化的通知给全部相关的监听器。
对咱们的例子进行一个测试,开始播放,并在播放期间插入耳机。音频的输出线路变为耳机并继续正常播放,这是咱们所指望的结果。保持音频的播放状态,断开耳机的链接。音频线路再次回到设备的内置扬声器,咱们再次听到了声音。虽然线路变化同预期同样,可是有一个问题,用户插上耳机多是为了保持隐私性,耳机断开链接有可能须要继续保密,因此咱们须要耳机断开链接时候,音乐要中止播放。
当线路发生变化时要有通知,咱们须要注册AVAudioSession发送的通知,在init方法中。该通知为AVAdudioSessionRouteChangeNotification。
- (instancetype)init {
if (self = [super init]) {
AVAudioPlayer *guitarPlayer = [self createPlayerWithFileName:@"guitar"];
AVAudioPlayer *bassPlayer = [self createPlayerWithFileName:@"bass"];
AVAudioPlayer *drumsPlayer = [self createPlayerWithFileName:@"drums"];
_players = @[guitarPlayer, bassPlayer, drumsPlayer];
// 注册音频会话中断通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
// 注册线路变化通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];
}
return self;
}
复制代码
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
AVAudioSessionRouteChangeReason reason = [userInfo[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { // 线路回到手机端
AVAudioSessionRouteDescription *route = userInfo[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *output = route.outputs.firstObject;
AVAudioSessionPort portType = output.portType;
// 耳机 或 蓝牙音频设备
if ([portType isEqualToString:AVAudioSessionPortHeadphones] ||
[portType isEqualToString:AVAudioSessionPortBluetoothA2DP]) {
[self stop];
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerManagerPlaybackStopped:)]) {
[self.delegate audioPlayerManagerPlaybackStopped:self];
}
}
}
}
复制代码
如今,当咱们断开耳机,音频播放也会中止。以上就是使用AVAudioPlayer完成的一个简单地播放器功能。实际开发中,咱们只要注意处理咱们真正遇到的场景就能够了。