【轉】iOS AVPlayer的那些坑


這次主要是總結和記錄下視頻播放遇到的坑,視頻播放采用的是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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM