AVPlayer详解系列(一)参数设置

最近工做内容基本都是围绕视频播放展开的,从AVPlayer到IJKPlayer,期间遇到挺多问题,趟了不少bug,也总结了一些心得。对AVPlayer了解的更多一些,由于涉及点比较多,因此打算作一个系列详尽的写一下这部份内容。但愿你们多多支持,有问题的地方欢迎指正。git

思惟导图

先来一张思惟导图,做为这篇文章的目录索引: github

AVPlayer.png

为何使用AVPlayer:

首先在iOS平台使用播放视频,可用的选项通常有这四个,他们各自的做用和功能以下:缓存

使用环境 优势 缺点
MPMoviePlayerController MediaPlayer 简单易用 不可定制
AVPlayerViewController AVKit 简单易用 不可定制
AVPlayer AVFoundation 可定制度高,功能强大 不支持流媒体
IJKPlayer IJKMediaFramework 定制度高,支持流媒体播放 使用稍复杂

由此能够看出,若是咱们不作直播功能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>bash

几个播放相关的参数

在建立一个播放器以前咱们须要先了解一些播放器相关的类app

  • AVPlayer:控制播放器的播放,暂停,播放速度
  • AVURLAsset : AVAsset 的一个子类,使用 URL 进行实例化,实例化对象包换 URL 对应视频资源的全部信息。
  • AVPlayerItem:管理资源对象,提供播放数据源
  • AVPlayerLayer:负责显示视频,若是没有添加该类,只有声音没有画面

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

最简单的播放器

根据上面描述,咱们知道AVPlayer是播放的必要条件,因此咱们能够构建的极简播放器就是:ide

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错误是否是还要有播放失败的提示,还有播放完成的相关提示。 为完成这些,咱们须要对AVPlayerItemAVPlayerLayer进一步了解一下。 ###1、AVPlayer的控制 前面讲过该类是控制视频播放行为的,他的使用比较简单。 播放视频:ui

[self.player play];
复制代码

暂停视频:

[self.player pause];
复制代码

更改速度:

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

还有一下其余的控制,咱们能够调转到系统API进行查看

2、AVPlayerItem的控制

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

一、播放状态 status

typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {
    AVPlayerItemStatusUnknown,//未知
    AVPlayerItemStatusReadyToPlay,//准备播放
    AVPlayerItemStatusFailed//播放失败
};
复制代码

咱们使用KVO监测playItem.status,能够获取播放状态的变化

[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
复制代码

在监听回调中:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    if ([object isKindOfClass:[AVPlayerItem class]]) {
        if ([keyPath isEqualToString:@"status"]) {
            switch (_playerItem.status) {
                case AVPlayerItemStatusReadyToPlay:
                    //推荐将视频播放放在这里
                    [self play];
                    break;
                    
                case AVPlayerItemStatusUnknown:
                    NSLog(@"AVPlayerItemStatusUnknown");
                    break;
                    
                case AVPlayerItemStatusFailed:
                    NSLog(@"AVPlayerItemStatusFailed")
                    break;
                    
                default:
                    break;
            }
            
        }
    }
}
复制代码

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

二、视频的时间信息

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

typedef struct{
    CMTimeValue    value;     // 帧数
    CMTimeScale    timescale;  // 帧率(影片每秒有几帧)
    CMTimeFlags    flags;        
    CMTimeEpoch    epoch;    
} CMTime;
复制代码

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

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

float currentTime = self.playItem.currentTime.value/item.currentTime.timescale;
复制代码

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

float totalTime   = CMTimeGetSeconds(item.duration);
复制代码

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

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))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:nil usingBlock:^(CMTime time) {
    AVPlayerItem *item = WeakSelf.playerItem;
    NSInteger currentTime = item.currentTime.value/item.currentTime.timescale;
    NSLog(@"当前播放时间:%ld",currentTime);
}];
复制代码

三、loadedTimeRange 缓存时间

获取视频的缓存状况咱们须要监听playerItem的loadedTimeRanges属性

[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
复制代码

在KVO的回调里:

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(@"当前缓冲时间:%f",totalBuffer);
}
复制代码

四、playbackBufferEmpty

监听playbackBufferEmpty咱们能够获取当缓存不够,视频加载不出来的状况:

[self.playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; 
复制代码

在KVO回调里:

if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
   //some code show loading 
}
复制代码

五、playbackLikelyToKeepUp

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

[self.playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context: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:(nullable NSDictionary<NSString *, id> *)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,可是经多人测试项目上线不受影响。

播放相关通知

一、声音类:

//声音被打断的通知(电话打来)
AVAudioSessionInterruptionNotification
//耳机插入和拔出的通知
AVAudioSessionRouteChangeNotification
复制代码

根据userInfo判断具体状态

二、播放类

//播放完成
AVPlayerItemDidPlayToEndTimeNotification
//播放失败
AVPlayerItemFailedToPlayToEndTimeNotification
//异常中断
AVPlayerItemPlaybackStalledNotification
复制代码

对于播放完成的通知咱们能够这么写:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerMovieFinish:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
复制代码

三、系统状态

//进入后台
UIApplicationWillResignActiveNotification
//返回前台
UIApplicationDidBecomeActiveNotification
复制代码

提示:全部通知和KVO的使用咱们都要记得在不用时remove掉。

小结

视频播放相关的知识比较多,细节的方面须要一点一点去扣。暂且写这么多吧,之后有须要会及时补充。 参考: ZFPlayer AVPlayer那些坑 若是还有什么不理解的能够简书私信问我,或者查看我写的Demo,欢迎star- ( ゜- ゜)つロ乾杯~

相关文章
相关标签/搜索