细数AVPlayer的那些坑
http://ivanyuan.farbox.com/post/shi-pin-bo-fang-de-na-xie-keng
最近一直在做视频动态挂件以及一个视频播放的功能,在开始做之前,先学习了苹果的官方文档RosyWriter,熟悉了短视频拍摄、滤镜处理的一些小技巧,同学也学习了下GPUImage,最后在踩了很多坑以后才实现了视频挂件的处理。
这次主要是总结和记录下视频播放遇到的坑,视频播放采用的是AVPlayer
这个控件,语法大致如下:
NSURL * url = [NSURL fileURLWithPath:@"视频地址"];AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];self.player = [AVPlayer playerWithPlayerItem:playerItem];[self.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;self.playerLayer.frame = self.view.bounds;[self.view.layer addSublayer:self.playerLayer];
这里要监听一下AVPlayer
的status
属性,当status
的状态变为AVPlayerStatusReadyToPlay
时,说明视频就可以播放了,此时我们调用[self.player play];
就好了。
如果是AVPlayerStatusFailed
说明视频加载失败,这时可以通过self.player.error.description
属性来找出具体的原因。
当status
变为AVPlayerStatusReadyToPlay
后,我们调用play
方法真的就能保证视频正常播放吗?
众所周知,AVPlayer
支持的视频、音频格式非常广泛,抛开那些无法正常编解码的情况,在某些情况下其可能就是无法正常播放。
AVPlayer
在进行播放时,会预先解码一些内容,而此时如果我们的App使用CPU过多,I/O读写过多时,有可能导致视频播放声/画不同步,这点尤其在iPhone4上面表现更为明显。
而如果是发生在AVPlayer
初始化解码视频的时候,有可能导致视频直接无法播放,这时,我们再调用play
或者seekToTime:
方法都无法正常播放。
建议不要在CPU或者I/O很频繁的情况下使用AVPlayer,例如刚登录App加载各种数据的情况下,可以等App预热以后再使用。
当rate
属性的值大于0后,真的就在播放视频了吗?
答案是否定的,当发生上面所讲的情况时,我打印了当前的rate情况,是大于0的,但是页面上显示的情况却还是什么也没有。
有时候我们如果想要在视频一播放的时候去做一些事情,例如设置一下播放器的背景色,如果我们仅仅是监听这个rate可能无法100%保证有效,而如果我们真的要监听这种情况的话,有一个取巧的方法
id _timerObserver = [self.player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:CMTimeMake(1, 30)]] queue:dispatch_get_main_queue()usingBlock:^{//do something}];
另外如果不需要监听播放进度的时候可以调
[self.player removeTimeObserver:_timerObserver];
AVPlayer
前后台播放的那些问题
当我们切换到后台后,这时AVPlayer通常会自动暂停,当然如果设置了后台播放音频的话,是可以在后台继续播放声音的,正如苹果自己的WWDC这个App一样。
如果我们想要在程序切回来前台继续播放的话,我们需要监听两个通知
[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(appBecomeActive:)name:UIApplicationDidBecomeActiveNotificationobject:nil];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(appWillResignActive:)name:UIApplicationWillResignActiveNotificationobject:nil];
先在appWillResignActive:方法中记录当前播放的时间CMTime
- (void)appWillResignActive:(NSNotification *)notification {if (self.player) {[self.player pause];self.time = self.player.currentTime;} }
等到切回前台的时候再继续播放
@try {[self.player seekToTime:self.time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {if (finished) {[self.player play];}}];} @catch (NSException *exception) {[self.player play];}
这里如果我们只是调用[self.player play];
,则在继续播放的时候可能会后退一定的时间,而如果我们想要精准地继续播放则需要下面这个方法,toleranceBefore:
与toleranceAfter:
均设置成kCMTimeZero
.
[self.player seekToTime:self.time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:
当然这个方法也会有一些问题,例如在刚启动播放的时候,以及在播放到最后一帧的时候,首先是其有可能会出现异常并crash.
所以我们用了@try@catch来捕获这个异常,当出现异常的时候直接调用play
让播放器自己决定播放的进度。
另外一个问题是其在最后一帧的时候有可能会白屏,因为最后一帧的内容有可能是空的,或者其它一些特殊的中间情况,所以在视频快要播放结束的时候建议,直接使用play
方法。
音频通道的抢占引起的无法播放视频问题
iOS系统有如下几种声音播放模式
enum {kAudioSessionCategory_AmbientSound = 'ambi',kAudioSessionCategory_SoloAmbientSound = 'solo',kAudioSessionCategory_MediaPlayback = 'medi',kAudioSessionCategory_RecordAudio = 'reca',kAudioSessionCategory_PlayAndRecord = 'plar',kAudioSessionCategory_AudioProcessing = 'proc'};
App运行的时候通常只能使用一种声音播放模式,而如果我们在录制视频或者录制声音的时候,把模式设置成了kAudioSessionCategory_RecordAudio
,这个时候如果我们使用AVPlayer播放视频,可能就无法播放视频。
这个时候我们需要把模式切换成kAudioSessionCategory_MediaPlayback
或者其它合适的模式,切换模式的代码如下:
UInt32 category = kAudioSessionCategory_MediaPlayback;UInt32 size = sizeof(category);AudioSessionGetProperty(kAudioSessionProperty_AudioCategory, &size, &category);if (category != kAudioSessionCategory_MediaPlayback) {category = kAudioSessionCategory_MediaPlayback;AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, size, &category);QLog_Event(MODULE_IMPB_RICHMEDIA,"change route category to media play back.");}
关于上面这几个模式的作用这儿有比较详细的解释,如果我们需要在用户静音时,不播放声音,可以选择kAudioSessionCategory_SoloAmbientSound
.
其它App播放声音打断问题
如果用户当时在后台听音乐,如QQ音乐,或者喜马拉雅这些App,这个时候播放视频后,其会被我们打断,当我们不再播放视频的时候,自然需要继续这些后台声音的播放。
首先,我们需要先向设备注册激活声音打断AudioSessionSetActive(YES);
,当然我们也可以通过
[AVAudioSession sharedInstance].otherAudioPlaying;
这个方法来判断还有没有其它业务的声音在播放。
当我们播放完视频后,需要恢复其它业务或App的声音,这时我们可以调用如下方法:
OSStatus ret = AudioSessionSetActiveWithFlags(NO, kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation);
其它的坑
1、在用户插入和拔出耳机时,有可能也会导致视频暂停。
其实插、拔耳机是属性改变声音输出设备的一种方式,其次还有修改为听筒、扬声器,或者其它蓝牙设备输出。相关代码如下:
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audioRouteChangeListenerCallback, (__bridge void*)self);void audioRouteChangeListenerCallback (void *inUserData,AudioSessionPropertyID inPropertyID,UInt32 inPropertyValueS,const void *inPropertyValue) {UInt32 propertySize = sizeof(CFStringRef);AudioSessionInitialize(NULL, NULL, NULL, NULL);CFStringRef state = nil;//获取音频路线AudioSessionGetProperty(kAudioSessionProperty_AudioRoute,&propertySize,&state);//kAudioSessionProperty_AudioRoute:音频路线NSLog(@"%@",(NSString *)state);//Headphone 耳机 Speaker 喇叭. }
如果是输出设备发生变化,我们如果要继续播放视频的话,我们只需监听到设备变化时调用play
就好了.
2、性能问题
其实在UITableView中使用AVPlayer播放多个视频时,是很容易出现性能问题的,当然这个时候我们也通常是静音的,不然多个视频一起播声音,没有人会承受得了。
当然你可以选择muted
以及把volume
设置为0来达到目的。在TableViewCell重用时,我们也可以使用pause
方法来暂停之前的视频,并使用- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item
方法来加载一个新的视频。
使用这样的一个套路,可能仍然无法解决切换视频时带来的卡顿,尤其在视频内容比较多的时候。关于这个问题,微信内部自己写了一个简易版的AVAssetReader+AVAssetReaderTrackOutput组件,在静音模式下播放列表里面的视频,同时也不用考虑播放模式了,微信博客链接为iOS小视频优化心得。
3、内存泄漏问题
当我们释放一个正在播放的视频时,需要先调用pause
方法,如果由于某些原因,例如切前后台时,导致又调用了play
方法,那么有可能会hold住内存空间而导致内存泄漏。
4、获取视频缩略图
获取首帧视频截图的方法如下:
AVAssetImageGenerator *imageGen = [[AVAssetImageGenerator alloc] initWithAsset:self.source];if (imageGen) {imageGen.appliesPreferredTrackTransform = YES;CMTime actualTime;CGImageRef cgImage = [imageGen copyCGImageAtTime:CMTimeMakeWithSeconds(0, 30) actualTime:&actualTime error:NULL];if (cgImage) {UIImage *image = [UIImage imageWithCGImage:cgImage];CGImageRelease(cgImage);return image;}}
总结:
视频播放只是整个富媒体的一小部分,在拍摄短视频时,各种参数的应用,对于视频的后期美颜、滤镜、着色、人脸识别,视频压缩等技术这些才是真正的难点。
细数AVPlayer的那些坑相关推荐
- 细数ST-LINK的种种坑:ST-LINK Connection error,USB communication error,NO target connected等
一.正确配置ST-LINK 此部分可以帮助解决ST-LINK Connection error的问题 ST-LINK有两种接线方式,一种是JTAG,一种是SWD(SW),网上购买的ST-LINK(下图 ...
- 细数STM32F103的那些坑
1.串口时钟 GPIO外设时钟都挂载在APB1总线上 串口1的时钟挂在APB2上,而串口2.串口3则是挂在APB1上 所以,在初始化串口1时,我们可以使用以下代码: RCC_APB2PeriphClo ...
- 细数sass安装中遇到的坑
前言: 前两天打算清理电脑的时候,遇到了一点特殊的问题,打算重装一些东西,其中就有我一直用的顺手的SASS预编译工具. 但是在重装的时候,我发现我居然不会用了??? 靠,要不是我用了半年的Sass,我 ...
- 细数Qt开发的各种坑(欢迎围观)
1:Qt的版本多到你数都数不清,多到你开始怀疑人生.从4.6开始到5.8,从MSVC编译器到MINGW编译器,从32位到64位,从Windows到Linux到MAC.MSVC版本还必须安装对应的VS2 ...
- 百密一疏,防不胜防,细数那些大型数据库建设过程中绕不开的坑
构建大型数据库时,无论最开始的设计多么精妙,到后续操作的时候或多或少都会遇到一些问题,本文将来细数大型数据库中不可避免会遇到的问题. 原文标题:Feature Casualties of Large ...
- 细数近年来机器学习研究的几大怪现状
人工智能领域的发展离不开学者们的贡献,然而随着研究的进步,越来越多的论文出现了「标题党」.「占坑」.「注水」等现象,暴增的顶会论文接收数量似乎并没有带来更多技术突破.最近,来自卡耐基梅隆大学的助理教授 ...
- 年终盘点丨细数2017云栖社区20大热点话题(附100+话题清单)
2017,你在聚能聊里分享了多少内容?贡献了多少话题?又收获了多少呢?社区聚能聊不仅可以请教技术难题,探讨热点话题,也可以八卦日常生活,分享码农们的点点滴滴. 程序员的世界不止是眼前的代码,一样有诗和 ...
- 《八股文》细数Java线程、并发、锁,温故而知新
<八股文>细数Java线程.并发.锁,温故而知新 基础 1. 并行.并发有什么区别? 2. 说说什么是进程和线程? 3. 说说线程有几种创建方式? 4. 为什么调用start()方法时会执 ...
- 怼完Sophia怼深度学习!细数完大神Yann LeCun 这些年怼过的N件事,原来顶级高手是这样怼人的...
图片来源:PCmag.com 十多个小时前,深度学习大神Yann LeCun语出惊人,他宣布放弃"深度学习"这个词.因为媒体对这个词的炒作言过其实,混淆了大家真正的工作,而&quo ...
最新文章
- 使用Powershell批量为Azure资源添加Tags
- 安卓手机怎么查看iccid_安卓便签敬业签怎么查看日历月视图中一天所有的新增内容?...
- 【转】使用dos2unix批量转换文件
- 地理文本处理技术在高德的演进(下)
- Netty 服务 如何 接收新的连接
- 2017.9.29 数三角形 思考记录
- 量化干货:量化交易系统设计的六大细节
- compose yaml规则
- 东芝2000ac废粉盒怎么二次利用_阜新降级组件回收厂家,废太阳能板回收_振昌_光伏...
- 粉笔画粉笔字体样式_20多种很棒的粉笔字体可供下载
- 二维otsu算法python_OpenCV-Python系列之OTSU算法
- linux中如何从txt转为nc文件,【转】linux下nc的使用
- 关于邮箱显示已经回复,但是已发送邮件里面没有
- 率土之滨服务器维修,率土之滨征服赛季合服与转服功能详解
- 个人认为最low的10款鞋子
- mongodb数据库扩展名_MongoDB文件型数据库
- Java实现 LeetCode 417 太平洋大西洋水流问题
- 老九学堂 学习C++ 第六天
- STM32入门指南:了解STM32
- HDU1869:六度分离
热门文章
- android 模仿 弹性菜单
- string 转换int
- 自定义WP日志标题长度
- 【自然框架】 权限 的视频演示(二): 权限到字段、权限到记录
- 机器学习--线性回归2(共线性问题、岭回归、lasso算法)
- 考软件测试初学者眼影,Summer 大讲堂第一期:如何制作出版级的高分辨率图表?...
- vue项目中vue-router的使用
- houghlinesp找到多条直线_拿什么拯救焦虑的你,一个有勇气的人终将找到他的路...
- dataframe 排序_如何对Pandas DataFrame进行自定义排序
- tcpprep man 手册翻译