這次主要是總結和記錄下視頻播放遇到的坑,視頻播放采用的是AVPlayer這個控件,語法大致如下:
NSURL * url = [NSURL fileURLWithPath:@"視頻地址"]; AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url]; self.player = [AVPlayer playerWithPlayerItem:playerItem]; [playerItem 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];
一般說來,這里要監聽AVPlayerItem的status屬性:
* *AVPlayerItem的三種狀態 *AVPlayerItemStatusUnknown, *AVPlayerItemStatusReadyToPlay, *AVPlayerItemStatusFailed */
如果是AVPlayerStatusFailed說明視頻加載失敗,這時可以通過self.player.error.description屬性來找出具體的原因。 問題一:當status變為AVPlayerStatusReadyToPlay后,我們調用play方法真的就能保證視頻正常播放嗎?
眾所周知,AVPlayer支持的視頻、音頻格式非常廣泛,拋開那些無法正常編解碼的情況,在某些情況下其可能就是無法正常播放。AVPlayer在進行播放時,會預先解碼一些內容,而此時如果我們的App使用CPU過多,I/O讀寫過多時,有可能導致視頻播放聲/畫不同步,這點尤其在iPhone4上面表現更為明顯,用戶反饋iOS9.3.2的系統上也很明顯。而如果是發生在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一樣。這個功能在我的另一篇文章iOS AVPlayer之后台連續播放視頻中解決了這個問題。
問題四:音頻通道的搶占引起的無法播放視頻問題
這個問題下周我會另開一篇博客專門講述,今兒就此略過。
問題五:其它App播放聲音打斷
如果用戶當時在后台聽音樂,如QQ音樂,或者喜馬拉雅這些App,這個時候播放視頻后,其會被我們打斷,當我們不再播放視頻的時候,自然需要繼續這些后台聲音的播放。
首先,我們需要先向設備注冊激活聲音打斷AudioSessionSetActive(YES);,當然我們也可以通過 [AVAudioSession sharedInstance].otherAudioPlaying;這個方法來判斷還有沒有其它業務的聲音在播放。 當我們播放完視頻后,需要恢復其它業務或App的聲音,這時我們可以在退到后台的事件中調用如下方法:
- (void)applicationDidEnterBackground:(UIApplication *)application { NSError *error =nil; AVAudioSession *session = [AVAudioSession sharedInstance]; // [session setCategory:AVAudioSessionCategoryPlayback error:nil]; BOOL isSuccess = [session setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error]; if (!isSuccess) { NSLog(@"__%@",error); }else{ NSLog(@"成功了"); } }
問題六:在用戶插入和拔出耳機時,導致視頻暫停,解決方法如下
//耳機插入和拔掉通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]]; //耳機插入、拔出事件 - (void)audioRouteChangeListenerCallback:(NSNotification*)notification { NSDictionary *interuptionDict = notification.userInfo; NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; switch (routeChangeReason) { case AVAudioSessionRouteChangeReasonNewDeviceAvailable: break; case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { //判斷為耳機接口 AVAudioSessionRouteDescription *previousRoute =interuptionDict[AVAudioSessionRouteChangePreviousRouteKey]; AVAudioSessionPortDescription *previousOutput =previousRoute.outputs[0]; NSString *portType =previousOutput.portType; if ([portType isEqualToString:AVAudioSessionPortHeadphones]) { // 拔掉耳機繼續播放 if (self.playing) { [self.player play]; } } } break; case AVAudioSessionRouteChangeReasonCategoryChange: // called at start - also when other audio wants to play break; } }
問題七:打電話等中斷事件
//中斷的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; //中斷事件 - (void)handleInterruption:(NSNotification *)notification{ NSDictionary *info = notification.userInfo; //一個中斷狀態類型 AVAudioSessionInterruptionType type =[info[AVAudioSessionInterruptionTypeKey] integerValue]; //判斷開始中斷還是中斷已經結束 if (type == AVAudioSessionInterruptionTypeBegan) { //停止播放 [self.player pause]; }else { //如果中斷結束會附帶一個KEY值,表明是否應該恢復音頻 AVAudioSessionInterruptionOptions options =[info[AVAudioSessionInterruptionOptionKey] integerValue]; if (options == AVAudioSessionInterruptionOptionShouldResume) { //恢復播放 [self.player play]; } } }
小提示:如果不起作用,請檢查退到后台事件中有什么其它的操作沒。因為電話來時,會調用退到后台的事件。
問題七:內存泄露問題 當我們釋放一個正在播放的視頻時,需要先調用pause方法,如果由於某些原因,例如切前后台時,導致又調用了play方法,那么有可能會hold不住內存空間而導致內存泄漏。其實更靠譜的方法是,還要移除player加載的資源。
總的來說,AVPlayer能滿足一般的需求,雖然坑不少。最后,再來安利一下我自己封裝的LYAVPlayer,簡單方便,支持cocoa pods,只需幾行代碼即可完成播放:
LYAVPlayerView *playerView =[LYAVPlayerView alloc]init]; playerView.frame =CGRectMake(0, 64, ScreenWidth,200); playerView.delegate =self;//設置代理 [self.view addSubview:playerView]; [playerView setURL:[NSURL URLWithString:VideoURL]];//設置播放的URL [playerView play];//開始播放
工程中pod 'LYAVPlayer','~> 1.0.1'
即可使用。有什么問題請Issues我。
from:https://www.jianshu.com/p/47c7144db817