AVPlayer自定制视频播放器——耳机线控中断以及AVAudioSession的使用
Posted _Unique_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVPlayer自定制视频播放器——耳机线控中断以及AVAudioSession的使用相关的知识,希望对你有一定的参考价值。
在上一篇博客中说到了使用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中获得响应的中断信息,然后根据终端信息,进行响应的操作。回调函数代码如下:
<span style="font-size:18px;">/**
* 中断处理函数
*
* @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];
</span>
<span style="font-size:18px;">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);</span>
上面就是这个枚举类型,也就是上面的handleInterruption:方法中的那个if语句。上面的代码表示,当中断开始的时候,也就是当type ==AVAudioSessionInterruptionTypeBegan时,暂停当前的视音频播放,也就是上面的[self pause]方法,该方法写在了上一篇博客中。如果中断类型为AVAudioSessionInterruptionTypeEnded,userInfo字典会包含一个AVAudioSessionInterruptionOption值,来表示音频会话是否已经重新激活以及是否可以再次播放,其实这也是一个枚举类型:
<span style="font-size:18px;">/* For use with AVAudioSessionInterruptionNotification */
typedef NS_OPTIONS(NSUInteger, AVAudioSessionInterruptionOptions)
AVAudioSessionInterruptionOptionShouldResume = 1
NS_AVAILABLE_IOS(6_0);</span>
细心地读者会发现,我在上面的方法中,不管是began还是ended中,都有代理方法:
if (self.delegate)
[self.delegate playbackStopped];
<span style="font-size:18px;"> if (self.delegate)
[self.delegate playbackBegin];
</span>
这个代理主要是方便父视图或者是controller进行相关的UI操作,协议定义如下:
<span style="font-size:18px;">//视频播放中断的代理以及相应的方法,controller刷新UI的方法写在这里
@protocol PlayerViewDelegate <NSObject>
//中断方法
- (void)playbackStopped;
//重新开始播放方法
- (void)playbackBegin;</span>
应用程序的视图控制器已经采用该协议,并将其设置为委托。这提供了一种简单的方法来更新应用程序的用户界面。其实,当视频中断开始或者中断结束继续播放的时候,也可以发送通知,在controller中注册通知,监听状态改变。但笔者参考了部分书籍和其他的一些视频播放器,都使用了代理的方式,所以这里推荐使用代理方式,来实现回调刷新UI的功能。
此外,还要做出对路线改变的响应。所谓路线改变,就是插上耳机、拔出耳机,因为在使用视频播放器的过程中,肯定会涉及到耳机的使用,因此,必须要对这种情况进行处理,保证应用程序对线路变换做出正确的响应。在iOS设备上添或移除音频输入、输出线路时,会发生线路改变。有多重原因导致线路的变化,比如用户插入耳机或者断开USB麦克风。当这些事件发生时,,音频会根据情况改变输入或者输出线路,同时,AVAudioSession会广播一个描述该改变的通知给所有的侦听器,为遵循Apple的Human Interface Guidelines(HIG)的相关定义,应用程序应该成为这些相关侦听器中的一员。
正常情况下,当我们点击开始播放视频时,并在播放期间插入耳机,音频输出路线变为耳机插孔并继续正常播放,这正是我们所期望的效果。保持音频处于播放状态,断开耳机连接,音频路线再次回到设备的内置扬声器,我们再次听到了声音。虽然路线变化通预期的一样,不过,按照苹果公司的相关文档,该音频应该处于静音状态,当用户插入耳机时,隐含的意思是用户不希望外界听到具体的音频内容,这就意味着当用户断开耳机时,播放的内容可能需要继续保密,所以,我们需要停止音频播放。
从上面说的内容可以知道,当拔出耳机,一定要停止音频播放,所以,一定要对相应的状态进行处理。看了上面的代码,相比很快就会想到,在这里也是需要注册AVAudioSession的发送的通知,这里用到的通知是AVAudioSessionRouteChangeNotification,和前面一样,也是从userInfo字典中取出相关的参数,通过判断参数来进行相应的处理。注册通知的方法如下:
<span style="font-size:18px;"> //添加耳机状态监听
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];</span>
<span style="font-size:18px;">/**
* 音频输出改变触发事件
*
* @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];
</span>
从userInfo中取出AVAudioSessionRouteChangeReasonKey的value值,并转成int类型,赋值changeReason变量,其实获取到的数据是一个枚举类型的,该枚举类型保存在AVAudioSession.h中,
<span style="font-size:18px;">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);</span>
然后对枚举类型进行判断,如果为AVAudioSessionRouteChangeReasonOldDeviceUnavailable,则表示旧设备不可用,也就是插入耳机后,外放不可用,或者拔出耳机后,耳机不可用,然后定义一个AVAudioSessionRouteDescription类型的变量,该变量表示的是播放的路线描述信息,这里取出路线之前所使用设备的路线描述信息,即dic[AVAudioSessionRouteChangePreviousRouteKey]。获取了路线描述信息后,还要根据路线描述信息,获取对应的输出端口描述信息,也就是AVAudioSessionPortDescription *portDescription = [routeDescription.outputsfirstObject];然后从端口的描述信息中取出端口的类型,也就是portDescription.portType,这个类型其实是一个字符串类型,可以对这个类型进行判断,如果为“HeadPhones”,则表示为耳机,这儿时候,表示旧设备的类型为耳机,此时是拔出了耳机,因此,要暂停当前的视音频播放。同时,要强行将AVAudioSession的输出设备设置成为speaker,也就是手机底部的外放,因为手机的音频播放有外放,还有打电话的那个声音输出口以及耳机,所以,要设置成speaker。到这里,基本上就完成了一个视音频播放器的自定制。
其实,在视频播放器创建的时候,最好还是在初始化的过程中,对AVAudioSession进行播放端口的设置,以防其他页面的视音频播放器对AVAudioSession进行了更改,造成音量播放问题。
<span style="font-size:18px;">//设置session,防止播放时没有声音,自动识别当前播放模式,是耳机还是外放
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:NULL];
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
[[AVAudioSession sharedInstance] setPreferredIOBufferDuration:audioRouteOverride error:nil];</span>
这里的第一个方法中的SetCategory传入的参数是AVAudioSessionCategoryPlayAndRecord,表示应用同时支持视音频播放和录音,这样,能够防止添加录音功能后,播放模式就不是speaker了,而变成了顶部打电话的那个播放器了(忘记叫什么名字了)。 后面设置的那个withOption,就是表示,默认情况下,音量播放是通过speaker进行播放的。如果在应用中,还有录音功能,当拔掉耳机后,即使不录音,视频播放也不会是speaker,即使前面硬改,还是没法实现speaker,因此,这里设置一下,就不会有问题了。下面两行代码,是设置播放模式为speaker,虽然这么设置,但是,如果打开视频前,就已经插入耳机了,仍然是耳机播放,不是外放。所以不必担心播放前插入耳机,造成声音外放。
写到这里,包括上一篇博客,基本上已经实现了一个完整的视频播放器了,而且已经将平时开发过程中能够遇到的问题都已经考虑进来了,感谢耐心读者花费这么长时间看完。如果博客中有什么错误的部分,希望大家批评指正,互相学习。
上一篇博客地址:AVPlayer自定制视频播放器(1)——视频播放器基本实现
以上是关于AVPlayer自定制视频播放器——耳机线控中断以及AVAudioSession的使用的主要内容,如果未能解决你的问题,请参考以下文章
耳机线控启动APP时如何查看iOS APP【音乐播放应用】是不是在后台
SSS1530用于TYPE-C耳机 Lingting耳机方案 线控耳机方案
SSS1530用于TYPE-C耳机 Lingting耳机方案 线控耳机方案