AVPlayer讲解

思维导图

image

为什么使用AVPlayer:

首先在iOS平台使用播放视频,可用的选项一般有这四个,他们各自的作用和功能如下:

image

由此可以看出,如果我们不做直播功能AVPlayer就是一个最优的选择。

另外AVPlayer是一个可以播放任何格式的全功能影音播放器

支持视频格式: WMV,AVI,MKV,RMVB,RM,XVID,MP4,3GP,MPG等。

支持音频格式:MP3,WMA,RM,ACC,OGG,APE,FLAC,FLV等。

支持视频格式: MP4,MOV,M4V,3GP,AVI等。

支持音频格式:MP3,AAC,WAV,AMR,M4A等。

详见AVPlayer支持的视频格式

如何使用

AVPlayer存在于AVFoundation框架,我们使用时需要导入:

import <AVFoundation/AVFoundation.h>

几个播放相关的参数

在创建一个播放器之前我们需要先了解一些播放器相关的类

AVPlayer:控制播放器的播放,暂停,播放速度

AVURLAsset : AVAsset 的一个子类,使用 URL 进行实例化,实例化对象包换 URL 对应视频资源的所有信息。

AVPlayerItem:管理资源对象,提供播放数据源

AVPlayerLayer:负责显示视频,如果没有添加该类,只有声音没有画面

我们这片文章就围绕这几个参数展开,光说这些你可能还有点不明白,那我们就围绕一个最简单的播放器做起,一点点扩展功能,在具体讲解这几个参数的作用。

最简单的播放器

根据上面描述,我们知道AVPlayer是播放的必要条件,所以我们可以构建的极简播放器就是:

NSURL*playUrl = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14573563182394.mp4"];

self.player = [[AVPlayer alloc] initWithURL:playUrl];

[self.player play];

是不是很简单,只有三行代码!

但是它太简单了,仅可以完成音频的播放,连画面都没有。回看上面播放相关类的介绍,是因为缺少AVPlayerLayer;作为一个播放器,我不能只播放一条视频啊,我还想根据需要切换视频,那我们就得把AVPlayerItem也加上。

加上这两个属性之后的播放器是这样的:

NSURL*playUrl = [NSURL URLWithString:@"http://baobab.wdjcdn.com/14573563182394.mp4"];

self.playerItem = [AVPlayerItem playerItemWithURL:playUrl];//如果要切换视频需要调AVPlayer的replaceCurrentItemWithPlayerItem:方法

self.player = [AVPlayer playerWithPlayerItem:_playerItem];

self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

self.playerLayer.frame = _videoView.bounds;//放置播放器的视图

[self.videoView.layer addSublayer:self.playerLayer];

[_player play];

现在的播放器稍微完整了一些,我们在自己创建的容器里可以看到画面了!

更多功能

但是它作为一个视频播放器,还是有很多不能让人满意的地方。例如:没有暂停、快进快退、倍速播放等,另外如果遇到url错误是不是还要有播放失败的提示,还有播放完成的相关提示。

为完成这些,我们需要对AVPlayerItem和AVPlayerLayer进一步了解一下。

一、AVPlayer的控制

前面讲过该类是控制视频播放行为的,他的使用比较简单。

播放视频:

[self.player play];

暂停视频:

[self.player pause];

更改速度:

self.player.rate =1.5;//注意更改播放速度要在视频开始播放之后才会生效

还有一下其他的控制,我们可以调转到系统API进行查看

二、AVPlayerItem的控制

AVPlayerItem作为资源管理对象,它控制着视频从创建到销毁的诸多状态。

1、播放状态 status

typedef NS_ENUM(NSInteger,AVPlayerItemStatus) {

                AVPlayerItemStatusUnknown,//未知

                AVPlayerItemStatusReadyToPlay,//准备播放

                AVPlayerItemStatusFailed//播放失败

};

我们使用KVO监测playItem.status,可以获取播放状态的变化

[self.playerItem addObserver:selfforKeyPath:@"status"options:NSKeyValueObservingOptionNewcontext:nil];

在监听回调中:

  • (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary )change context:(void)context{

      if([object isKindOfClass:[AVPlayerItemclass]]) {
    
              if([keyPath isEqualToString:@"status"]) {
    
                      switch(_playerItem.status) {
    
                                  caseAVPlayerItemStatusReadyToPlay://推荐将视频播放放这里                                                                                     [self play];
    
                                                      break;
    
                                  caseAVPlayerItemStatusUnknown:
    
                                                      NSLog(@"AVPlayerItemStatusUnknown");
    
                                                      break;
    
                                  caseAVPlayerItemStatusFailed:
    
                                                      NSLog(@"AVPlayerItemStatusFailed");
    
                                                      break;
    
                                      default:break;       
    
           }           
    

}}

虽然设置完播放配置我们可以直接调用[self.player play];进行播放,但是更稳妥的方法是在回调收到AVPlayerItemStatusReadyToPlay时进行播放

2、视频的时间信息

在AVPlayer中时间的表示有一个专门的结构体CMTime

typedef struct{

        CMTimeValuevalue;// 帧数

        CMTimeScaletimescale;// 帧率(影片每秒有几帧)

        CMTimeFlagsflags;

        CMTimeEpochepoch;    

}CMTime;

CMTime是以分数的形式表示时间,value表示分子,timescale表示分母,flags是位掩码,表示时间的指定状态。

获取当前播放时间,可以用value/timescale的方式:

floatcurrentTime =self.playItem.currentTime.value/item.currentTime.timescale;

还有一种利用系统提供的方法,我们用它获取视频总时间:

floattotalTime =CMTimeGetSeconds(item.duration);

如果我们想要添加一个计时的标签不断更新当前的播放进度,有一个系统的方法:

  • (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullabledispatch_queue_t)queue usingBlock:(void(^)(CMTimetime))block;

方法名如其意, “添加周期时间观察者” ,参数1 interal 为CMTime 类型的,参数2 queue为串行队列,如果传入NULL就是默认主线程,参数3 为CMTime 的block类型。

简而言之就是,每隔一段时间后执行 block。

比如:我们把interval设置成CMTimeMake(1, 10),在block里面刷新label,就是一秒钟刷新10次。

正常观察播放进度一秒钟一次就行了,所以可以这么写:

[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1,1) queue:nilusingBlock:^(CMTimetime) {

AVPlayerItem*item = WeakSelf.playerItem;

NSIntegercurrentTime = item.currentTime.value/item.currentTime.timescale;

NSLog(@"当前播放时间:%ld",currentTime);

}];

3、loadedTimeRange 缓存时间

获取视频的缓存情况我们需要监听playerItem的loadedTimeRanges属性

[self.playerItem addObserver:selfforKeyPath:@"loadedTimeRanges"options:NSKeyValueObservingOptionNewcontext:nil];

在KVO的回调里:

if([keyPath isEqualToString:@"loadedTimeRanges"]){

    NSArray*array = _playerItem.loadedTimeRanges;

    //本次缓冲时间范围

    CMTimeRangetimeRange = [array.firstObject CMTimeRangeValue];

     floatstartSeconds =CMTimeGetSeconds(timeRange.start);

    floatdurationSeconds =CMTimeGetSeconds(timeRange.duration);

    NSTimeIntervaltotalBuffer = startSeconds + durationSeconds;//缓冲总长度

    NSLog(@"当前缓冲时间:%f",totalBuffer);

}

4、playbackBufferEmpty

监听playbackBufferEmpty我们可以获取当缓存不够,视频加载不出来的情况:

[self.playerItem addObserver:selfforKeyPath:@"playbackBufferEmpty"options:NSKeyValueObservingOptionNewcontext:nil];

在KVO回调里:

if([keyPath isEqualToString:@"playbackBufferEmpty"]) {

//some code show loading

}

5、playbackLikelyToKeepUp

playbackLikelyToKeepUp和playbackBufferEmpty是一对,用于监听缓存足够播放的状态

[self.playerItem addObserver:selfforKeyPath:@"playbackLikelyToKeepUp"options:NSKeyValueObservingOptionNewcontext:nil];

/* ... */

if([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {

//由于 AVPlayer 缓存不足就会自动暂停,所以缓存充足了需要手动播放,才能继续播放 [_player play];

}

AVURLAsset

播放视频只需一个url就能进行这样太不安全了,别人可以轻易的抓包盗链,为此我们需要为视频链接做一个请求头的认证,这个功能可以借助AVURLAsset完成。

AVPlayerItem除了可以用URL初始化,还可以用AVAsset初始化,而AVAsset不能直接使用,我们看下AVURLAsset的一个初始化方法:

/*!

@param URL

        An instance of NSURL that references a media resource.

@param options

        An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above.

/+ (instancetype)URLAssetWithURL:(NSURL)URL options:(nullableNSDictionary *)options;

AVURLAssetPreferPreciseDurationAndTimingKey.这个key对应的value是一个布尔值, 用来表明资源是否需要为时长的精确展示,以及随机时间内容的读取进行提前准备。

除了这个苹果官方介绍的功能外,他还可以设置请求头,这个算是隐藏功能了,因为苹果并没有明说这个功能,我是费了很大劲找到的。

NSMutableDictionary* headers = [NSMutableDictionary dictionary];

[headers setObject:@"yourHeader"forKey:@"User-Agent"];

self.urlAsset = [AVURLAsset URLAssetWithURL:self.videoURL options:@{@"AVURLAssetHTTPHeaderFieldsKey": headers}];// 初始化playerItem

self.playerItem = [AVPlayerItem playerItemWithAsset:self.urlAsset];

补充:后来得知这个参数是非公开的API,但是经多人测试项目上线不受影响。

播放相关通知

1、声音类:

//声音被打断的通知(电话打来)

AVAudioSessionInterruptionNotification

//耳机插入和拔出的通知

AVAudioSessionRouteChangeNotification

根据userInfo判断具体状态

2、播放类

//播放完成

AVPlayerItemDidPlayToEndTimeNotification

//播放失败

AVPlayerItemFailedToPlayToEndTimeNotification

//异常中断

AVPlayerItemPlaybackStalledNotification

对于播放完成的通知我们可以这么写:

[[NSNotificationCenterdefaultCenter] addObserver:selfselector:@selector(playerMovieFinish:) name:AVPlayerItemDidPlayToEndTimeNotificationobject:[self.player currentItem]];

3、系统状态

//进入后台

UIApplicationWillResignActiveNotification

//返回前台

UIApplicationDidBecomeActiveNotification

提示:所有通知和KVO的使用我们都要记得在不用时remove掉。

作者:小白PK大牛
链接:https://www.jianshu.com/p/8bc9cd59a4d8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342