在上一篇博客中说到了使用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>

这里要对这个方法进行详细的讲解。这里引用《AV Foundation开发秘籍》中的部分内容进行讲解。书中的内容将用红色字体标出,以表示对作者版权的尊重。推送的通知中包含一个带有许多重要信息的userInfo字典根据这个字典可以确定采取哪些合适的操作。在handleInterruption:方法中,首先通过检索AVAudioSessionInterruptionTypeKey的值确定中断类型(type)。返回值是AVAudioSessionInterruptionType,这是用于表示中断开始或结束的么及类型。

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

前面的方法是先移除这个通知,后面是添加通知,有些资料中的写法是先移除可能存在的已经注册的通知,然后重新注册通知,其实也可以不写前面的那段代码,这部分,有个object对象,为nil也是可以的,因为可以通过单例来访问AVAudioSession对象。然后,就是对通知进行相关的处理,方法如下:

<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自定制视频播放器(2)——耳机线控、中断以及AVAudioSession的使用相关推荐

  1. AVPlayer自定制视频播放器(1)——视频播放器基本实现

    在iOS多媒体开发的过程中,经常会用到视频播放器,简单是视频播放器,直接使用苹果封装好的MPMoviePlayerController和MPMoviePlayerViewController就可以实现 ...

  2. iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控

    -- iOS事件全面解析 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的,这不愧为一项伟大的设计.今天我们就针对iOS的触摸事 ...

  3. iOS事件全面解析 (触摸事件、手势识别、摇晃事件、耳机线控)

    -- iOS事件全面解析 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的,这不愧为一项伟大的设计.今天我们就针对iOS的触摸事 ...

  4. iOS:触摸事件、手势识别、摇晃事件、耳机线控

    概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的,这不愧为一项伟大的设计.今天我们就针对iOS的触摸事件(手势操作).运动事件. ...

  5. 触摸事件、手势识别、摇晃事件、耳机线控

    概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的,这不愧为一项伟大的设计.今天我们就针对iOS的触摸事件(手势操作).运动事件. ...

  6. 转载大神IOS开发系列【9】--触摸事件、手势识别、摇晃事件、耳机线控

    转载自:http://www.cnblogs.com/kenshincui/p/3950646.html 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实是可以 ...

  7. Android耳机线控-播放/暂停/上一曲/下一曲

    起因 前一阵子完成了用有线耳机控制Android手机App的音频播放,具体实现了用耳机线的按键完成播放.暂停.上一曲.下一曲的功能.在网上查阅了一些资料,但不是特别尽如人意,记得有一篇写的很不错的这方 ...

  8. 聊聊iOS开发中耳机的那点事(监听耳机拔插、耳机线控)-b

    如果说一个项目出现的最重大的事故,那无疑就是开发人员使用了不可控的元素. 前言 iOS开发当中有关于视音频播放的开发不在少数,用户时常会使用到一种输出设备,那就是"耳机",这一篇博 ...

  9. ios版本 线控 Android,iOS 耳机线控

    最近适配耳机线控,记录一下问题 首先,耳机线控三要素: 1.开启接受耳机线控 ---- [[UIApplication sharedApplication] beginReceivingRemoteC ...

最新文章

  1. Fiddler抓取HTTPS包
  2. python比excel优势-python数据分析相对于bi和excel的优势是什么?
  3. 4道Python基础文件操作函数 练习题
  4. 前端学习(3184):ant-design的button介绍按钮属性
  5. Python怎么这么香(洛谷P2788题解,Java语言描述)
  6. Maven setting.xml 配置详解
  7. for for..in语句的基本结构 常用的内置对象和内置放法
  8. linux 安装npm
  9. Win10配置ssh密钥免密连接Linux服务器
  10. Gerrit代码检查工具
  11. PS学习笔记-----提示暂存盘满了怎么办???
  12. ADSL桥接模式和路由模式的区别
  13. HR套招的十大经典面试问题
  14. 父亲节华为P40软文营销广告
  15. uniapp公共测试证书签名
  16. R语言绘制箱体图举例图文版
  17. java autoconf_Centos7安装autoconf
  18. 文本检测算法性能对比
  19. 2019第四届新媒体千人峰会广州站将于6月正式开幕!
  20. CSS复仇者联盟立体盒子

热门文章

  1. mqtt服务器搭建php,MQTT 服务端
  2. php 自定义数组排序函数,PHP自定义数组排序
  3. LeetCode 131. 分割回文串【字符串,回溯算法】
  4. php keydown,JQuery中keyUp和keyDown的区别详解
  5. python colorbar范围_python-在matplotlib中设置colorbar范围
  6. Python 快速排序,代码实现
  7. CGB JAVA面试题 NOTE1
  8. hmr webpack 不编译_webpack - hmr热更新
  9. Sklearn(scikit-learn)
  10. Sklearn_工具--2SKlearn介绍