這次主要是總結(jié)和記錄下視頻播放遇到的坑舰讹,視頻播放采用的是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];
這里要監(jiān)聽一下AVPlayer的status屬性,當status的狀態(tài)變?yōu)锳VPlayerStatusReadyToPlay時,說明視頻就可以播放了,此時我們調(diào)用[self.player play];就好了。
如果是AVPlayerStatusFailed說明視頻加載失敗豺裆,這時可以通過self.player.error.description屬性來找出具體的原因。
當status變?yōu)锳VPlayerStatusReadyToPlay后号显,我們調(diào)用play方法真的就能保證視頻正常播放嗎臭猜?
眾所周知,AVPlayer支持的視頻押蚤、音頻格式非常廣泛蔑歌,拋開那些無法正常編解碼的情況,在某些情況下其可能就是無法正常播放揽碘。
AVPlayer在進行播放時次屠,會預先解碼一些內(nèi)容,而此時如果我們的App使用CPU過多雳刺,I/O讀寫過多時劫灶,有可能導致視頻播放聲/畫不同步,這點尤其在iPhone4上面表現(xiàn)更為明顯掖桦。
而如果是發(fā)生在AVPlayer初始化解碼視頻的時候本昏,有可能導致視頻直接無法播放,這時枪汪,我們再調(diào)用play或者seekToTime:方法都無法正常播放涌穆。
建議不要在CPU或者I/O很頻繁的情況下使用AVPlayer,例如剛登錄App加載各種數(shù)據(jù)的情況下,可以等App預熱以后再使用料饥。
答案是否定的岸啡,當發(fā)生上面所講的情況時,我打印了當前的rate情況赫编,是大于0的巡蘸,但是頁面上顯示的情況卻還是什么也沒有奋隶。
有時候我們?nèi)绻胍谝曨l一播放的時候去做一些事情,例如設(shè)置一下播放器的背景色悦荒,如果我們僅僅是監(jiān)聽這個rate可能無法100%保證有效唯欣,而如果我們真的要監(jiān)聽這種情況的話,有一個取巧的方法
id_timerObserver=[self.playeraddBoundaryTimeObserverForTimes:@[[NSValuevalueWithCMTime:CMTimeMake(1,30)]]queue:dispatch_get_main_queue()usingBlock:^{//do something}];
另外如果不需要監(jiān)聽播放進度的時候可以調(diào)
[self.playerremoveTimeObserver:_timerObserver];
當我們切換到后臺后搬味,這時AVPlayer通常會自動暫停境氢,當然如果設(shè)置了后臺播放音頻的話,是可以在后臺繼續(xù)播放聲音的碰纬,正如蘋果自己的WWDC這個App一樣萍聊。
如果我們想要在程序切回來前臺繼續(xù)播放的話,我們需要監(jiān)聽兩個通知
[[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;}}
等到切回前臺的時候再繼續(xù)播放
@try{[self.playerseekToTime:self.timetoleranceBefore:kCMTimeZerotoleranceAfter:kCMTimeZerocompletionHandler:^(BOOLfinished){if(finished){[self.playerplay];}}];}@catch(NSException*exception){[self.playerplay];}
這里如果我們只是調(diào)用[self.player play];悦析,則在繼續(xù)播放的時候可能會后退一定的時間寿桨,而如果我們想要精準地繼續(xù)播放則需要下面這個方法,toleranceBefore:與toleranceAfter:均設(shè)置成kCMTimeZero.
[self.playerseekToTime:self.timetoleranceBefore:kCMTimeZerotoleranceAfter:kCMTimeZerocompletionHandler:
當然這個方法也會有一些問題强戴,例如在剛啟動播放的時候亭螟,以及在播放到最后一幀的時候,首先是其有可能會出現(xiàn)異常并crash.
所以我們用了@try@catch來捕獲這個異常骑歹,當出現(xiàn)異常的時候直接調(diào)用play讓播放器自己決定播放的進度预烙。
另外一個問題是其在最后一幀的時候有可能會白屏,因為最后一幀的內(nèi)容有可能是空的陵刹,或者其它一些特殊的中間情況默伍,所以在視頻快要播放結(jié)束的時候建議,直接使用play方法衰琐。
iOS系統(tǒng)有如下幾種聲音播放模式
enum{kAudioSessionCategory_AmbientSound='ambi',kAudioSessionCategory_SoloAmbientSound='solo',kAudioSessionCategory_MediaPlayback='medi',kAudioSessionCategory_RecordAudio='reca',kAudioSessionCategory_PlayAndRecord='plar',kAudioSessionCategory_AudioProcessing='proc'};
App運行的時候通常只能使用一種聲音播放模式也糊,而如果我們在錄制視頻或者錄制聲音的時候,把模式設(shè)置成了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.");}
關(guān)于上面這幾個模式的作用這兒有比較詳細的解釋,如果我們需要在用戶靜音時狗热,不播放聲音钞馁,可以選擇kAudioSessionCategory_SoloAmbientSound.
如果用戶當時在后臺聽音樂,如QQ音樂匿刮,或者喜馬拉雅這些App僧凰,這個時候播放視頻后,其會被我們打斷熟丸,當我們不再播放視頻的時候训措,自然需要繼續(xù)這些后臺聲音的播放。
首先,我們需要先向設(shè)備注冊激活聲音打斷AudioSessionSetActive(YES);,當然我們也可以通過
[AVAudioSession sharedInstance].otherAudioPlaying;這個方法來判斷還有沒有其它業(yè)務的聲音在播放绩鸣。
當我們播放完視頻后怀大,需要恢復其它業(yè)務或App的聲音,這時我們可以調(diào)用如下方法:
OSStatusret=AudioSessionSetActiveWithFlags(NO,kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation);
1呀闻、在用戶插入和拔出耳機時化借,有可能也會導致視頻暫停。
其實插捡多、拔耳機是屬性改變聲音輸出設(shè)備的一種方式蓖康,其次還有修改為聽筒、揚聲器局服,或者其它藍牙設(shè)備輸出钓瞭。相關(guān)代碼如下:
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 喇叭.}
如果是輸出設(shè)備發(fā)生變化,我們?nèi)绻^續(xù)播放視頻的話淫奔,我們只需監(jiān)聽到設(shè)備變化時調(diào)用play就好了.
2山涡、性能問題
其實在UITableView中使用AVPlayer播放多個視頻時,是很容易出現(xiàn)性能問題的唆迁,當然這個時候我們也通常是靜音的鸭丛,不然多個視頻一起播聲音,沒有人會承受得了唐责。
當然你可以選擇muted以及把volume設(shè)置為0來達到目的鳞溉。在TableViewCell重用時,我們也可以使用pause方法來暫停之前的視頻鼠哥,并使用- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item方法來加載一個新的視頻熟菲。
使用這樣的一個套路,可能仍然無法解決切換視頻時帶來的卡頓朴恳,尤其在視頻內(nèi)容比較多的時候抄罕。關(guān)于這個問題,微信內(nèi)部自己寫了一個簡易版的AVAssetReader+AVAssetReaderTrackOutput組件于颖,在靜音模式下播放列表里面的視頻呆贿,同時也不用考慮播放模式了,微信博客鏈接為iOS小視頻優(yōu)化心得森渐。
3做入、內(nèi)存泄漏問題
當我們釋放一個正在播放的視頻時,需要先調(diào)用pause方法同衣,如果由于某些原因竟块,例如切前后臺時,導致又調(diào)用了play方法耐齐,那么有可能會hold住內(nèi)存空間而導致內(nèi)存泄漏浪秘。
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;}}