AVPlayer 本地、網絡視頻播放相關

iOS開發常用的兩種視頻播放方式,一種是使用MPMoviePlayerController,還有就是使用AVPlayer。MPMoviePlayerController系統高度封裝使用起來很方便,但是如果要高度自定義播放器就比較麻煩。而AVPlayer則恰好相反,靈活性更強,使用起來也麻煩一點。本文將對AVPlayer的使用做個簡單的介紹。

1、AVPlayer加載播放視頻

AVPlayer繼承NSObject,所以單獨使用AVPlayer時無法顯示視頻的,必須將視頻圖層添加到AVPlayerLayer中方能顯示視頻。使用AVPlayer首先了解一下幾個常用的類:

AVAsset:AVAsset類專門用于獲取多媒體的相關信息,包括獲取多媒體的畫面、聲音等信息。
AVURLAsset:AVAsset的子類,可以根據一個URL路徑創建一個包含媒體信息的AVURLAsset對象。
AVPlayerItem:一個媒體資源管理對象,管理者視頻的一些基本信息和狀態,一個AVPlayerItem對應著一個視頻資源。

AVPlayer加載視頻的代碼如下:

    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@"http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4"]];
    //添加監聽
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    self.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
    //設置模式
    playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    playerLayer.contentsScale = [UIScreen mainScreen].scale;
    playerLayer.frame = CGRectMake(0, 100, self.view.bounds.size.width, 200);
    [self.view.layer addSublayer:playerLayer];



//監聽回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    AVPlayerItem *playerItem = (AVPlayerItem *)object;
    
    if ([keyPath isEqualToString:@"loadedTimeRanges"]){
        
    }else if ([keyPath isEqualToString:@"status"]){
        if (playerItem.status == AVPlayerItemStatusReadyToPlay){
            NSLog(@"playerItem is ready");
            [self.avPlayer play];
        } else{
            NSLog(@"load break");
        }
    }
}

此處代碼中添加了對AVPlayerItem的"loadedTimeRanges"和"status"屬性監聽,status枚舉值有 AVPlayerItemStatusUnknown,AVPlayerItemStatusReadyToPlay, AVPlayerItemStatusFailed。只有當status為AVPlayerItemStatusReadyToPlay是調用 AVPlayer的play方法視頻才能播放。

運行效果
D82C00B0-4D83-4B1A-82EC-B245F15F40E0.png

2、AVPlayer當前緩沖進度以及當前播放進度的處理

獲取視頻當前的緩沖進度:

通過監聽AVPlayerItem的"loadedTimeRanges",可以實時知道當前視頻的進度緩沖,計算方法如下:

- (NSTimeInterval)availableDurationWithplayerItem:(AVPlayerItem *)playerItem
{
    NSArray *loadedTimeRanges = [playerItem loadedTimeRanges];
    CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 獲取緩沖區域
    NSTimeInterval startSeconds = CMTimeGetSeconds(timeRange.start);
    NSTimeInterval durationSeconds = CMTimeGetSeconds(timeRange.duration);
    NSTimeInterval result = startSeconds + durationSeconds;// 計算緩沖總進度
    return result;
}
獲取視頻當前的播放進度:
    //視頻當前的播放進度
    NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
    //視頻的總長度
    NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);

AVPlayer提供了一個Block回調,當播放進度改變的時候回主動回調該Block,但是當視頻卡頓的時候是不會回調的,可以在該回調里面處理進度條以及播放時間的刷新,詳細方法如下:

     __weak __typeof(self) weakSelf = self;
    [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        //當前播放的時間
        NSTimeInterval current = CMTimeGetSeconds(time);
        //視頻的總時間
        NSTimeInterval total = CMTimeGetSeconds(weakSelf.avPlayer.currentItem.duration);
        //設置滑塊的當前進度
        weakSelf.slider.sliderPercent = current/total;
        NSLog(@"%f", weakSelf.slider.sliderPercent);
       //設置時間
        weakSelf.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [weakSelf formatPlayTime:current], [weakSelf formatPlayTime:total]];
    }];


//將時間轉換成00:00:00格式
- (NSString *)formatPlayTime:(NSTimeInterval)duration
{
    int minute = 0, hour = 0, secend = duration;
    minute = (secend % 3600)/60;
    hour = secend / 3600;
    secend = secend % 60;
    return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];
}
改變視頻當前的播放進度:

當滑塊滑動的時候需要改變當前視頻的播放進度,代碼如下:

//處理滑塊
- (void)progressValueChange:(AC_ProgressSlider *)slider
{
       //當視頻狀態為AVPlayerStatusReadyToPlay時才處理(當視頻沒加載的時候,直接禁止掉滑塊事件)
       if (self.avPlayer.status == AVPlayerStatusReadyToPlay) {
        NSTimeInterval duration = self.slider.sliderPercent* CMTimeGetSeconds(self.avPlayer.currentItem.duration);
        CMTime seekTime = CMTimeMake(duration, 1);

        [self.avPlayer seekToTime:seekTime completionHandler:^(BOOL finished) {

        }];
    }
}
播放進度控件的定制:

該控件應該包含4個部分,總進度、緩沖進度、當前播放進度還有一個滑塊。效果圖如下:


08D61DA5-5645-4B1D-A170-ABAAB6088B19.png

最簡單的實現方式是UIProgressView跟UISlider兩個控件疊加起來,效果不是太好。demo是自定義的UIControl,詳細實現方式請查看demo中的AC_ProgressSlider類。

當視頻卡頓的時候處理旋轉loading方法:

上面說過- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;該方法在卡頓的時候不會回調,所以只用該方法處理不了這種情況。我好像也沒找到相關的api,所以demo中采用的是開啟定時器,然后用一個lastTime保留當前的播放進度,當下次調用的時候用lastTime跟當前的進度進行比較,如果相等說明播放卡頓了,代碼如下:

    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(upadte)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

//更新方法
- (void)upadte
{
    NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
    NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
   //如果用戶在手動滑動滑塊,則不對滑塊的進度進行設置重繪
    if (!self.slider.isSliding) {
        self.slider.sliderPercent = current/total;
    }
    
    if (current!=self.lastTime) {
        [self.activity stopAnimating];
        self.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [self formatPlayTime:current], [self formatPlayTime:total]];
    }else{
        [self.activity startAnimating];
    }
    self.lastTime = current;
}

3、AVPlayer播放暫停的處理

這個比較簡單、 分別是pause和play方法

//播放暫停按鈕
- (void)playOrPauseAction:(UIButton *)sender
{
    sender.selected = !sender.selected;
    
    if (self.avPlayer.rate == 1) {
        [self.avPlayer pause];
        self.link.paused = YES;
        [self.activity stopAnimating];
    } else {
        [self.avPlayer play];
        self.link.paused = NO;
    }
}

4、AVPlayer播放完成的處理

添加通知即可

 //播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(moviePlayDidEnd)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:nil];

5、更換當前播放的AVPlayerItem

當視頻播放完時或者用戶切換不同的視頻時候就要更換當前的視頻,代碼如下:

//切換當前播放的內容
- (void)changeCurrentplayerItemWithAC_VideoModel:(AC_VideoModel *)model
{
    if (self.avPlayer) {
        
        //由暫停狀態切換時候 開啟定時器,將暫停按鈕狀態設置為播放狀態
        self.link.paused = NO;
        self.playButton.selected = NO;
        
        //移除當前AVPlayerItem對"loadedTimeRanges"和"status"的監聽
        [self removeObserveWithPlayerItem:self.avPlayer.currentItem];
        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:model.url];
        [self addObserveWithPlayerItem:playerItem];
        self.avPlayerItem = playerItem;
        //更換播放的AVPlayerItem
        [self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
        
        self.playButton.enabled = NO;
        self.slider.enabled = NO;
    }
}

感覺寫的有點亂,詳細的看demo吧,demo運行效果如下:

海賊王.gif

完整代碼7牛下載連接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容