在上一篇博客中说到了使用AVPlayer进行自定义视频播放器。这里讲继续讲述视频播放器的自定制。下面是上一篇博客的链接,本篇博客将承接上一篇博客进行讲解,如果有AVPlayer自定制视频播放器基础的同学,可以不必看上一篇博客,直接进入这篇。
AVPlayer自定义视频播放器(1)——视频播放器基本实现
相信你已经会使用AVPlayer进行视频播放器的自定制,并且,能够进行基本的开始、暂停、静音、快放等一些基本操作,这里主要讲解一些特殊的操作。主要讲解耳机线控、电话呼入中断和应用退到后台等操作。
首先将一个简单的电话呼入操作吧。其实,当有电话呼入的时候,系统会自动发送一个中断的通知给当前运行的各个应用,因此,在这里只要注册一下这个通知,然后在对应的方法中,对中断进行相关的处理,就可以做到暂停视音频的播放了。
/**
* 注册中断通知
*/
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
这里涉及到了一个AVAudioSessionInterruptionNotification,这个其实是视音频会话被打断的通知,AVAudioSession其实是视频、音频以及录音功能通用的一个会话,不要根据它的名字中写着Audio就以为只是音频的会话,这个其实是通用的。addObserver当然就是指定当前的页面为监听对象,我在项目中将Player放在了一个view,所以,这里指的是这个view对象。selector当然就是通知的回调方法。后面的object是要传入到回调方法中的参数,这里一定要将这个AVAudioSession传入进入,目的是在回调中获得session对象,然后从session中获得响应的中断信息,然后根据终端信息,进行响应的操作。回调函数代码如下:
/**
* 中断处理函数
*
* @param notification 通知对象
*/
- (void)handleInterruption:(NSNotification *)notification{
NSDictionary * info = notification.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
//中断开始和中断结束
if (type == AVAudioSessionInterruptionTypeBegan) {
//当被电话等中断的时候,调用这个方法,停止播放
[self pause];
if (self.delegate) {
[self.delegate playbackStopped];
}
} else {
/**
* 中断结束,userinfo中会有一个InterruptionOption属性,
* 该属性如果为resume,则可以继续播放功能
*/
AVAudioSessionInterruptionOptions option = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (option == AVAudioSessionInterruptionOptionShouldResume) {
[self resume];
[self.delegate playbackBegin];
}
}
}
typedef NS_ENUM(NSUInteger, AVAudioSessionInterruptionType)
{
AVAudioSessionInterruptionTypeBegan = 1, /* the system has interrupted your audio session */
AVAudioSessionInterruptionTypeEnded = 0, /* the interruption has ended */
} NS_AVAILABLE_IOS(6_0);
上面就是这个枚举类型,也就是上面的handleInterruption:方法中的那个if语句。上面的代码表示,当中断开始的时候,也就是当type ==AVAudioSessionInterruptionTypeBegan时,暂停当前的视音频播放,也就是上面的[self pause]方法,该方法写在了上一篇博客中。如果中断类型为AVAudioSessionInterruptionTypeEnded,userInfo字典会包含一个AVAudioSessionInterruptionOption值,来表示音频会话是否已经重新激活以及是否可以再次播放,其实这也是一个枚举类型:
/* For use with AVAudioSessionInterruptionNotification */
typedef NS_OPTIONS(NSUInteger, AVAudioSessionInterruptionOptions)
{
AVAudioSessionInterruptionOptionShouldResume = 1
} NS_AVAILABLE_IOS(6_0);
if (self.delegate) {
[self.delegate playbackStopped];
}
if (self.delegate) {
[self.delegate playbackBegin];
}
//视频播放中断的代理以及相应的方法,controller刷新UI的方法写在这里
@protocol PlayerViewDelegate
//中断方法
- (void)playbackStopped;
//重新开始播放方法
- (void)playbackBegin;
此外,还要做出对路线改变的响应。所谓路线改变,就是插上耳机、拔出耳机,因为在使用视频播放器的过程中,肯定会涉及到耳机的使用,因此,必须要对这种情况进行处理,保证应用程序对线路变换做出正确的响应。在iOS设备上添或移除音频输入、输出线路时,会发生线路改变。有多重原因导致线路的变化,比如用户插入耳机或者断开USB麦克风。当这些事件发生时,,音频会根据情况改变输入或者输出线路,同时,AVAudioSession会广播一个描述该改变的通知给所有的侦听器,为遵循Apple的Human Interface Guidelines(HIG)的相关定义,应用程序应该成为这些相关侦听器中的一员。
正常情况下,当我们点击开始播放视频时,并在播放期间插入耳机,音频输出路线变为耳机插孔并继续正常播放,这正是我们所期望的效果。保持音频处于播放状态,断开耳机连接,音频路线再次回到设备的内置扬声器,我们再次听到了声音。虽然路线变化通预期的一样,不过,按照苹果公司的相关文档,该音频应该处于静音状态,当用户插入耳机时,隐含的意思是用户不希望外界听到具体的音频内容,这就意味着当用户断开耳机时,播放的内容可能需要继续保密,所以,我们需要停止音频播放。
从上面说的内容可以知道,当拔出耳机,一定要停止音频播放,所以,一定要对相应的状态进行处理。看了上面的代码,相比很快就会想到,在这里也是需要注册AVAudioSession的发送的通知,这里用到的通知是AVAudioSessionRouteChangeNotification,和前面一样,也是从userInfo字典中取出相关的参数,通过判断参数来进行相应的处理。注册通知的方法如下:
//添加耳机状态监听
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];
/**
* 音频输出改变触发事件
*
* @param notification 通知
*/
- (void)routeChange:(NSNotification *)notification{
NSDictionary *dic = notification.userInfo;
int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue];
//等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示旧输出不可用
if (changeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *routeDescription = dic[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *portDescription = [routeDescription.outputs firstObject];
//原设备为耳机则暂停
if ([portDescription.portType isEqualToString:@"Headphones"]) {
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AVAudioSession * session = [AVAudioSession sharedInstance];
[session setPreferredIOBufferDuration:audioRouteOverride error:nil];
//如果视频正在播放,会自动暂停,这里用来设置按钮图标
if (self.playerState == playerViewPlaystatePlaying) {
[self pause];
[self.delegate playbackStopped];
}
}
}
}
typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
AVAudioSessionRouteChangeReasonUnknown = 0,
AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
AVAudioSessionRouteChangeReasonCategoryChange = 3,
AVAudioSessionRouteChangeReasonOverride = 4,
AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
} NS_AVAILABLE_IOS(6_0);
其实,在视频播放器创建的时候,最好还是在初始化的过程中,对AVAudioSession进行播放端口的设置,以防其他页面的视音频播放器对AVAudioSession进行了更改,造成音量播放问题。
//设置session,防止播放时没有声音,自动识别当前播放模式,是耳机还是外放
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:NULL];
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
[[AVAudioSession sharedInstance] setPreferredIOBufferDuration:audioRouteOverride error:nil];
写到这里,包括上一篇博客,基本上已经实现了一个完整的视频播放器了,而且已经将平时开发过程中能够遇到的问题都已经考虑进来了,感谢耐心读者花费这么长时间看完。如果博客中有什么错误的部分,希望大家批评指正,互相学习。
上一篇博客地址:AVPlayer自定制视频播放器(1)——视频播放器基本实现