美文网首页iOS之家time实用技术
iOS音视频学习7——AVPlayer视频播放

iOS音视频学习7——AVPlayer视频播放

作者: Realank | 来源:发表于2016-04-01 14:04 被阅读9027次

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

    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:0];
            _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(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];
            }
        }];
    }
    
    /**
     *  给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==0){ //说明时暂停
            [sender setTitle:@"pause" forState:UIControlStateNormal];
            [self.player play];
        }else if(self.player.rate==1){//正在播放
            [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, 具体使用方式今天就不再做详细介绍。

    相关文章

      网友评论

      • 年轻人的心情_爱我所爱:用这个AVPlayer 有些手机播放中会闪退,这个楼主遇到了没
        年轻人的心情_爱我所爱:@Realank 已经解决了,就是这样解决的,多谢了:smile:
        Realank:@年轻人的心情_爱我所爱 设置UIDevice的idletime
        年轻人的心情_爱我所爱:刚刚描述错了,不是闪退,是黑屏,然后手机自己就锁屏了
      • Mister志伟:AVPlayer 的
        -(void)seekToTime
        方法当time时间大小小于一秒的时候,AVPlayer就不会作同步的处理(如:快进,后退等),会依然保留在原来的位置。
        AVPlayerDemo里面,通过Slider去拖动时,也是当经过时间大与1的时候才会进行一次同步。
        想请问一下怎么让AVPlayer 快进/后退 1秒以下的时间?
      • Aacmr:楼主 请问怎么 让视频从上次播放的地方继续播放。我们后台返回了上次播放的值,我用了这个方法改的CMTime - (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block; 显示不正确,不知为什么? 应该在哪个方法里该呀?
      • 蛋壳儿:还是谢谢博主,感觉在简书上看的效果比网址上排版更舒服些,刚才那一条我删掉了
        Realank:@蛋壳儿 :smile::smile:客气客气,我就是在学人家文章的时候,顺便做个笔记,到时候翻自己的笔记比较省事
      • 168b66db4060:你好,我用的AVPlayer,通过网络加载MP4格式的视频,有的能正常播放而有的只有声音没有画面,你知道是什么原因造成的吗?
        168b66db4060:@知道自己不知道 是视频编码的问题,多数的mp4格式能播放,只有少数mp4的不能
        知道自己不知道:额, 你可能没有创建画布,也就是AVPLayerLayer,有这个画布 才能有画面
      • heiguoliangle:讲解很仔细哦,大爱
      • 4d4aa2a610a2:你好,为什么我用avplayer播放本地mov和avi格式的视频,播放不出来呢,mp4格式的可以播放
      • 张云龙:请问如何解决使用AVPlayer播放时,手机静音,播放的音频或者视频没有声音的问题?
        张云龙:@Realank 还有就算是播放音频,如何使用音乐播放啊?
        张云龙:@Realank 我使用AVPlayer播放网络视频,要自定义各种控件,貌似只能这样才能自定义视频播放器?这个怎么解决啊?
        Realank:@张云龙 策略问题,别用系统音效,用音乐播放
      • 再见代码: 顶一个 很有帮助

      本文标题:iOS音视频学习7——AVPlayer视频播放

      本文链接:https://www.haomeiwen.com/subject/tbrylttx.html