kidd风的IOS日志之IOS9 视频播放控制器(AVPlayer)


在Xcode7.2中如果你使用MPMoviePlayerController,会报以下警告:



因为IOS9.0之后,就停止更新维护MPMoviePlayerController,但仍然是可以用的,不过系统建议我们使用AVKit下的AVPlayer来进行视频播放。


MPMoviePlayerController足够强大,几乎不用写几行代码就能完成一个播放器,但是正是由于它的高度封装使得要自定义这个播放器变得很复杂,甚至是不可能完成。例如有些时候需要自定义播放器的样式,那么如果要使用MPMoviePlayerController就不合适了,如果要对视频有自由的控制则可以使用AVPlayer。AVPlayer存在于AVFoundation中,它更加接近于底层,所以灵活性也更强:


AVFoundation_Framework


AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:

AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。

AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。

AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。

下面简单通过一个播放器来演示AVPlayer的使用,播放器的效果如下:


AVPlayer_Thumbnail


在这个自定义的播放器中实现了视频播放、暂停、进度展示和视频列表功能,下面将对这些功能一一介绍。

首先说一下视频的播放、暂停功能,这也是最基本的功能,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专门处理播放列表切换,有兴趣的朋友可以自行研究,这里不再赘述)。

下面附上代码:

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
/**
 *  播放器对象
 */
@property (nonatomic,strong) AVPlayer *player;

/**
 *  播放器容器
 */
@property (nonatomic,weak) IBOutlet UIView *container;

/**
 *  播放进度
 */
@property (weak, nonatomic) IBOutlet UIProgressView *progress;

/**
 *  播放或暂停按钮
 */
@property (weak, nonatomic) IBOutlet UIButton *playOrPause;


- (IBAction)playClick:(UIButton *)sender;

@end


#pragma mark - 控制器视图方法
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [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.frame;
    
    //视频填充模式
//    playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
    
    [self.view.layer addSublayer:playerLayer];
}


/**
 *  截图指定时间的视频缩略图
 */


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


/**
 *  根据对应的索引取得AVPlayerItem对象
 *
 *  @param videoIndex 视频顺序的索引
 *
 *  @return AVPlayerItem对象
 */
- (AVPlayerItem *)getPlayItem:(int)videoIndex
{
    NSURL *url = [[NSURL alloc] init];
    if (videoIndex == 0) {
        url = [[NSBundle mainBundle] URLForResource:@"promo_full.mp4" withExtension:nil];
    }
    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 *playItem = 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([playItem 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<NSString *,id> *)change context:(void *)context
{
    AVPlayerItem *playerItem = object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
        
        if (status == AVPlayerItemStatusReadyToPlay) {
            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 {
    
    if (self.player.rate == 0) {//说明是暂停
        
        sender.selected = NO;
        [self.player play];
        
    }else if (self.player.rate == 1){ // 说明正在播放
        [self.player pause];
        [sender setSelected:YES];
    }
}



/**
 *  切换集数,这里使用按钮的tag代表视频名称
 *
 *  @param sender 点击按钮对象
 */
- (IBAction)navigationButtonClick:(UIButton *)sender
{
    [self removeNotification];
    [self removeObserverFromPlayerItem:self.player.currentItem];
    
    AVPlayerItem *playerItem = [self getPlayItem:(int)sender.tag];
    [self addObserverToPlayerItem:playerItem];
    
    //切换视频
    [self.player replaceCurrentItemWithPlayerItem:playerItem];
    [self addNotification];
}



@end


具体效果请自行运行查看

<span style="background-color: rgb(255, 255, 255);"><span style="font-family:SimSun;font-size:14px;">到目前为止无论是MPMoviePlayerController还是AVPlayer来播放视频都相当强大,但是它也存在着一些不可回避的问题,那就是支持的视频编码格式很有限:H.264、MPEG-4,扩展名(压缩格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。但是无论是MPMoviePlayerController还是AVPlayer它们都支持绝大多数音频编码,所以大家如果纯粹是为了播放音乐的话也可以考虑使用这两个播放器。那么如何支持更多视频编码格式呢?目前来说主要还是依靠第三方框架,在iOS上常用的视频编码、解码框架有:<a target=_blank style="outline-style: none; text-decoration: none; color: rgb(61, 129, 238); border-bottom-width: 1px; border-bottom-style: dashed;" href="https://github.com/videolan/vlc">VLC</a>、<a target=_blank style="outline-style: none; text-decoration: none; color: rgb(61, 129, 238); border-bottom-width: 1px; border-bottom-style: dashed;" href="https://github.com/gabriel/ffmpeg-iphone-build">ffmpeg</a>, 具体使用方式今天就不再做详细介绍。</span></span>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值