1. 程式人生 > >IOS 無限迴圈小視訊播放

IOS 無限迴圈小視訊播放

文/止於浮水(簡書作者)
原文連結:http://www.jianshu.com/p/43b29e121793
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。

其實這篇部落格是順著上一篇視訊錄製的部落格一起寫的,但是感覺放在一起太長了,不太好閱讀,所以把它分成兩部分了。

錄製完視訊後,我們想在錄製視訊的預覽層上無限迴圈播放我們的小視訊,是不是很炫酷,這時候我們就有三中選擇了:
1.MPMoviePlayerController
2.AVPlayer
3.AVAssetReader+AVAssetReaderTrackOutput

但是我們這個預覽層是自定義的喔,所以MPMoviePlayerController只能馬上給篩選掉了,所以用,那麼我們就要用到 AVPlayer

了,雖然上 AVPlayer 最多隻能建立16個,效能上不及用 AVAssetReader+AVAssetReaderTrackOutput 方法好,但是對於這麼個視訊小播放也是足夠的了。(PS:接下來博主會寫一篇關於如何用AVAssetReader+AVAssetReaderTrackOutput來實現播放視訊)

AVPlayer

AVPlayer本身並不能顯示視訊,而且它也不像MPMoviePlayerController有一個view屬性。如果AVPlayer要顯示必須建立一個播放器層AVPlayerLayer用於展示,播放器層繼承於CALayer, 
有了AVPlayerLayer之新增到控制器檢視的layer中即可。要使用AVPlayer
首先了解一下幾個常用的類: AVAsset:主要用於獲取多媒體資訊,是一個抽象類,不能直接使用。 AVURLAssetAVAsset的子類,可以根據一個URL路徑建立一個包含媒體資訊的AVURLAsset物件。 AVPlayerItem:一個媒體資源管理物件,管理者視訊的一些基本資訊和狀態,一個AVPlayerItem對應著一個視訊資源。

我們先來熟悉一下 AVPlayer 的這些類
那麼運用AVPlayer播放視訊的步驟如下:
1.建立 AVPlayerItem ,用來例項化AVPlayer並監控視訊的狀態

- (AVPlayer *)player
{
if (!_player ) {
    AVPlayerItem
*playerItem = [self getPlayItem]; _player = [[AVPlayer alloc]initWithPlayerItem:playerItem]; // 可以利用 AVPlayerItem 對這個視訊的狀態進行監控 } return _player; } - (AVPlayerItem *)getPlayItem { NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString *savePath=[cachePath stringByAppendingPathComponent:MOVIEPATH]; NSURL *saveUrl=[NSURL fileURLWithPath:savePath]; // 通過檔案 URL 來例項化 AVPlayerItem AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:saveUrl]; return playerItem; }

2.通過AVPlayer 建立預覽層(AVPlayerLayer)並新增到可視的圖層上播放

    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
     playerLayer.frame = _viewContrain.bounds;
     playerLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//視訊填充模式
     [_viewContrain.layer addSublayer:playerLayer];
     [self.player play];

3.通過 KVO 來監聽視訊的屬性,檢視是否播放完成,播放長度為多小,緩衝了多少等等

/** * 給播放器新增進度更新 */
-(void)addProgressObserver{ 
  AVPlayerItem *playerItem=self.player.currentItem; 
  UIProgressView *progress=self.progress; //這裡設定每秒執行一次 
  [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
    float current=CMTimeGetSeconds(time); 
    float total=CMTimeGetSeconds([playerItem duration]);
   NSLog(@"當前已經播放%.2fs.",current); 
  if (current) { 
        [progress setProgress:(current/total) animated:YES];
     } 
  }];
}

#pragma mark - KVO
  /** * 給AVPlayerItem新增監控 * 
      * @param playerItem AVPlayerItem物件 */
-(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{
//監控狀態屬性,注意AVPlayer也有一個status屬性,通過監控它的status也可以獲得播放狀態
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//監控網路載入情況屬性
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}

-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
[playerItem removeObserver:self forKeyPath:@"status"];
[playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}

/** * 通過KVO監控播放器狀態 *
     * @param keyPath 監控屬性 
     * @param object 監視器 
     * @param change 狀態改變 
     * @param context 上下文 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
    AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
    if(status==AVPlayerStatusReadyToPlay){
            NSLog(@"正在播放...,視訊總長度:%.2f",CMTimeGetSeconds(playerItem.duration));
        }
    }
    else if([keyPath isEqualToString:@"loadedTimeRanges"])
    {
        NSArray *array=playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩衝時間範圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩衝總長度
        NSLog(@"共緩衝:%.2f",totalBuffer);
    }
}

到此,我們就把 AVPlayer 視訊播放的方法建立完畢了。

那麼我們在視訊錄製完成的時候呼叫播放視訊的方法了

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
    NSLog(@"---- 錄製結束 ----");
}
- (void)completeHandle
{
    // 完成後不斷播放
    [_captureVideoPreviewLayer removeFromSuperlayer];

    // 播放視訊
    // 1.建立播放層
    // 這裡為什麼要呼叫延遲1.0秒呢,我們說過用 AVCaptureMovieFileOutput 來錄製視訊,是邊錄邊寫的,即使是錄製完成了,真實的是視訊還在寫,大概時間是延遲1.2秒左右。
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 在此方法呼叫建立 AVPlayer 播放視訊的第二部
            2.通過AVPlayer 建立預覽層(AVPlayerLayer)並新增到可視的圖層上播放
            [self addNotification];
      });
  }

新增視訊播放完後的監控來迴圈播放視訊

 /**
 *  新增播放器通知
 */
-(void)addNotification{
    //給AVPlayerItem新增播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}

-(void)removeNotification{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
/**
 *  播放完成通知
 *
 *  @param notification 通知物件
 */
-(void)playbackFinished:(NSNotification *)notification{
    NSLog(@"視訊播放完成.");


    // 播放完成後重複播放
    // 跳到最新的時間點開始播放
    [_player seekToTime:CMTimeMake(0, 1)];
    [_player play];
}

@end

心如止水,奮力前行