这次主要是总结和记录下视频播放遇到的坑,视频播放采用的是AVPlayer这个控件,语法大致如下:
NSURL*url=[NSURLfileURLWithPath:@"视频地址"];AVPlayerItem*playerItem=[AVPlayerItemplayerItemWithURL:url];self.player=[AVPlayerplayerWithPlayerItem:playerItem];[self.playeraddObserver:selfforKeyPath:@"status"options:NSKeyValueObservingOptionNewcontext:nil];self.player.actionAtItemEnd=AVPlayerActionAtItemEndNone;self.playerLayer=[AVPlayerLayerplayerLayerWithPlayer:self.player];self.playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;self.playerLayer.frame=self.view.bounds;[self.view.layeraddSublayer: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可能无法100%保证有效,而如果我们真的要监听这种情况的话,有一个取巧的方法
id_timerObserver=[self.playeraddBoundaryTimeObserverForTimes:@[[NSValuevalueWithCMTime:CMTimeMake(1,30)]]queue:dispatch_get_main_queue()usingBlock:^{//do something}];
另外如果不需要监听播放进度的时候可以调
[self.playerremoveTimeObserver:_timerObserver];
当我们切换到后台后,这时AVPlayer通常会自动暂停,当然如果设置了后台播放音频的话,是可以在后台继续播放声音的,正如苹果自己的WWDC这个App一样。
如果我们想要在程序切回来前台继续播放的话,我们需要监听两个通知
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(appBecomeActive:)name:UIApplicationDidBecomeActiveNotificationobject:nil];[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(appWillResignActive:)name:UIApplicationWillResignActiveNotificationobject:nil];
先在appWillResignActive:方法中记录当前播放的时间CMTime
-(void)appWillResignActive:(NSNotification*)notification{if(self.player){[self.playerpause];self.time=self.player.currentTime;}}
等到切回前台的时候再继续播放
@try{[self.playerseekToTime:self.timetoleranceBefore:kCMTimeZerotoleranceAfter:kCMTimeZerocompletionHandler:^(BOOLfinished){if(finished){[self.playerplay];}}];}@catch(NSException*exception){[self.playerplay];}
这里如果我们只是调用[self.player play];,则在继续播放的时候可能会后退一定的时间,而如果我们想要精准地继续播放则需要下面这个方法,toleranceBefore:与toleranceAfter:均设置成kCMTimeZero.
[self.playerseekToTime:self.timetoleranceBefore:kCMTimeZerotoleranceAfter:kCMTimeZerocompletionHandler:
当然这个方法也会有一些问题,例如在刚启动播放的时候,以及在播放到最后一帧的时候,首先是其有可能会出现异常并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或者其它合适的模式,切换模式的代码如下:
UInt32category=kAudioSessionCategory_MediaPlayback;UInt32size=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.
如果用户当时在后台听音乐,如QQ音乐,或者喜马拉雅这些App,这个时候播放视频后,其会被我们打断,当我们不再播放视频的时候,自然需要继续这些后台声音的播放。
首先,我们需要先向设备注册激活声音打断AudioSessionSetActive(YES);,当然我们也可以通过
[AVAudioSession sharedInstance].otherAudioPlaying;这个方法来判断还有没有其它业务的声音在播放。
当我们播放完视频后,需要恢复其它业务或App的声音,这时我们可以调用如下方法:
OSStatusret=AudioSessionSetActiveWithFlags(NO,kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation);
1、在用户插入和拔出耳机时,有可能也会导致视频暂停。
其实插、拔耳机是属性改变声音输出设备的一种方式,其次还有修改为听筒、扬声器,或者其它蓝牙设备输出。相关代码如下:
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,audioRouteChangeListenerCallback,(__bridgevoid*)self);voidaudioRouteChangeListenerCallback(void*inUserData,AudioSessionPropertyIDinPropertyID,UInt32inPropertyValueS,constvoid*inPropertyValue){UInt32propertySize=sizeof(CFStringRef);AudioSessionInitialize(NULL,NULL,NULL,NULL);CFStringRefstate=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=[[AVAssetImageGeneratoralloc]initWithAsset:self.source];if(imageGen){imageGen.appliesPreferredTrackTransform=YES;CMTimeactualTime;CGImageRefcgImage=[imageGencopyCGImageAtTime:CMTimeMakeWithSeconds(0,30)actualTime:&actualTimeerror:NULL];if(cgImage){UIImage*image=[UIImageimageWithCGImage:cgImage];CGImageRelease(cgImage);returnimage;}}