天天看點

iOS音視訊AVPlayer視訊播放

MPMoviePlayerController足夠強大,幾乎不用寫幾行代碼就能完成一個播放器,但是正是由于它的高度封裝使得要自定義這個播放器變得很複雜,甚至是不可能完成。例如有些時候需要自定義播放器的樣式,那麼如果要使用MPMoviePlayerController就不合适了,如果要對視訊有自由的控制則可以使用AVPlayer。AVPlayer存在于AVFoundation中,它更加接近于底層,是以靈活性也更強

iOS音視訊AVPlayer視訊播放

iOS多媒體結構

AVPlayer本身并不能顯示視訊,而且它也不像MPMoviePlayerController有一個view屬性。如果AVPlayer要顯示必須建立一個播放器層AVPlayerLayer用于展示,播放器層繼承于CALayer,有了AVPlayerLayer之添加到控制器視圖的layer中即可。要使用AVPlayer首先了解一下幾個常用的類:

  • AVAsset:主要用于擷取多媒體資訊,是一個抽象類,不能直接使用。
  • AVURLAsset:AVAsset的子類,可以根據一個URL路徑建立一個包含媒體資訊的AVURLAsset對象。
  • AVPlayerItem:一個媒體資源管理對象,管理者視訊的一些基本資訊和狀态,一個AVPlayerItem對應着一個視訊資源。

下面簡單通過一個播放器來示範AVPlayer的使用

在這個自定義的播放器中實作了視訊播放、暫停、進度展示和視訊清單功能,下面将對這些功能一一介紹。

首先說一下視訊的播放、暫停功能,這也是最基本的功能,AVPlayer對應着兩個方法play、pause來實作。但是關鍵問題是如何判斷目前視訊是否在播放,在前面的内容中無論是音頻播放器還是視訊播放器都有對應的狀态來判斷,但是AVPlayer卻沒有這樣的狀态屬性,通常情況下可以通過判斷播放器的播放速度來獲得播放狀态。如果rate為0說明是停止狀态,1是則是正常播放狀态。

其次要展示播放進度就沒有其他播放器那麼簡單了。在前面的播放器中通常是使用通知來獲得播放器的狀态,媒體加載狀态等,但是無論是AVPlayer還是AVPlayerItem(AVPlayer有一個屬性currentItem是AVPlayerItem類型,表示目前播放的視訊對象)都無法獲得這些資訊。當然AVPlayerItem是有通知的,但是對于獲得播放狀态和加載狀态有用的通知隻有一個:播放完成通知AVPlayerItemDidPlayToEndTimeNotification。在播放視訊時,特别是播放網絡視訊往往需要知道視訊加載情況、緩沖情況、播放情況,這些資訊可以通過KVO監控AVPlayerItem的status、loadedTimeRanges屬性來獲得。當AVPlayerItem的status屬性為AVPlayerStatusReadyToPlay是說明正在播放,隻有處于這個狀态時才能獲得視訊時長等資訊;當loadedTimeRanges的改變時(每緩沖一部分資料就會更新此屬性)可以獲得本次緩沖加載的視訊範圍(包含起始時間、本次加載時長),這樣一來就可以實時獲得緩沖情況。然後就是依靠AVPlayer的

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block

方法獲得播放進度,這個方法會在設定的時間間隔内定時更新播放進度,通過time參數通知用戶端。相信有了這些視訊資訊播放進度就不成問題了,事實上通過這些資訊就算是平時看到的其他播放器的緩沖進度顯示以及拖動播放的功能也可以順利的實作。

最後就是視訊切換的功能,在前面介紹的所有播放器中每個播放器對象一次隻能播放一個視訊,如果要切換視訊隻能重新建立一個對象,但是AVPlayer卻提供了

- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item

方法用于在不同的視訊之間切換(事實上在AVFoundation内部還有一個AVQueuePlayer專門處理播放清單切換,有興趣的朋友可以自行研究,這裡不再贅述)。

下面附上代碼:

//  ViewController.m
//  AVPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@property (nonatomic,strong) AVPlayer *player;//播放器對象

@property (weak, nonatomic) IBOutlet UIView *container; //播放器容器
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕
@property (weak, nonatomic) IBOutlet UIProgressView *progress;//播放進度

@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self setupUI];
    [self.player play];
}

-(void)dealloc{
    [self removeObserverFromPlayerItem:self.player.currentItem];
    [self removeNotification];
}

#pragma mark - 私有方法
-(void)setupUI{
    //建立播放器層
    AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:self.player];
    playerLayer.frame=self.container.bounds;
    //playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//視訊填充模式
    [self.container.layer addSublayer:playerLayer];
}

/**
 *  截取指定時間的視訊縮略圖
 *
 *  @param timeBySecond 時間點
 */

/**
 *  初始化播放器
 *
 *  @return 播放器對象
 */
-(AVPlayer *)player{
    if (!_player) {
        AVPlayerItem *playerItem=[self getPlayItem:];
        _player=[AVPlayer playerWithPlayerItem:playerItem];
        [self addProgressObserver];
        [self addObserverToPlayerItem:playerItem];
    }
    return _player;
}

/**
 *  根據視訊索引取得AVPlayerItem對象
 *
 *  @param videoIndex 視訊順序索引
 *
 *  @return AVPlayerItem對象
 */
-(AVPlayerItem *)getPlayItem:(int)videoIndex{
    NSString *urlStr= [[NSBundle mainBundle]pathForResource:@"movie.mp4" ofType:nil];
//    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];
    return playerItem;
}
#pragma mark - 通知
/**
 *  添加播放器通知
 */
-(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(@"視訊播放完成.");
}

#pragma mark - 監控
/**
 *  給播放器添加進度更新
 */
-(void)addProgressObserver{
    AVPlayerItem *playerItem=self.player.currentItem;
    UIProgressView *progress=self.progress;
    //這裡設定每秒執行一次
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(, ) 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];
        }
    }];
}

/**
 *  給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 *)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);
        //
    }
}

#pragma mark - UI事件
/**
 *  點選播放/暫停按鈕
 *
 *  @param sender 播放/暫停按鈕
 */
- (IBAction)playClick:(UIButton *)sender {
    //    AVPlayerItemDidPlayToEndTimeNotification
    //AVPlayerItem *playerItem= self.player.currentItem;
    if(self.player.rate==){ //說明時暫停
        [sender setTitle:@"pause" forState:UIControlStateNormal];
        [self.player play];
    }else if(self.player.rate==){//正在播放
        [self.player pause];
        [sender setTitle:@"play" forState:UIControlStateNormal];
    }
}


/**
 *  切換選集,這裡使用按鈕的tag代表視訊名稱
 *
 *  @param sender 點選按鈕對象
 */
- (IBAction)navigationButtonClick:(UIButton *)sender {
    [self removeNotification];
    [self removeObserverFromPlayerItem:self.player.currentItem];
    AVPlayerItem *playerItem=[self getPlayItem:sender.tag];
    [self addObserverToPlayerItem:playerItem];
    //切換視訊
    [self.player replaceCurrentItemWithPlayerItem:playerItem];
    [self addNotification];
}

@end

           

到目前為止無論是MPMoviePlayerController還是AVPlayer來播放視訊都相當強大,但是它也存在着一些不可回避的問題,那就是支援的視訊編碼格式很有限:H.264、MPEG-4、擴充名(壓縮格式):.mp4、 .mov、 .m4v、 .m2v、 .3gp、 .3g2等。

但是無論是MPMoviePlayerController還是AVPlayer它們都支援絕大多數音頻編碼,是以大家如果純粹是為了播放音樂的話也可以考慮使用這兩個播放器。那麼如何支援更多視訊編碼格式呢?目前來說主要還是依靠第三方架構,在iOS上常用的視訊編碼、解碼架構有:VLC、ffmpeg, 具體使用方式今天就不再做詳細介紹。

繼續閱讀