AVPlayer

作者:勇闯天涯茉莉花茶
链接:https://www.jianshu.com/p/29bfc7dc401a
來源:

为什么使用 AVPlayer

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


AVPlayer_第1张图片
视频播放常用框架.png

由此可以看出,如果我们不做直播功能 AVPlayer 就是一个最优的选择。
另外 AVPlayer 是一个可以播放任何格式的全功能影音播放器
支持视频格式: WMV,AVI,MKV,RMVB,RM,XVID,MP4,3GP,MPG等。
支持音频格式:MP3,WMA,RM,ACC,OGG,APE,FLAC,FLV等。

如何使用

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

几个播放相关的类

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

  • AVPlayer: 制播放器的播放,暂停,播放速度
  • AVPlayerItem: 管理资源对象,控制播放数据源
  • AVPlayerLayer: 负责显示视频,如果没有该类,只有声音没有画面
  • AVUrlAsset: AVAsset 子类,使用 url 创建,实例包括 url 对应视频的所有信息

最简单的播放器

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

NSURL *playUrl = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
self.player = [[AVPlayer alloc] initWithURL:playUrl];
[self.player play];

加上 AVPlayerLayer 增加画面。
加上 AVPlayerItem 来切换多个视频。
完善如下:

NSURL *playUrl = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
self.playerItem = [AVPlayerItem playerItemWithURL:playUrl];
//如果要切换视频需要调AVPlayer的replaceCurrentItemWithPlayerItem:方法
self.player = [AVPlayer playerWithPlayerItem:_playerItem];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = self.backgroundView.bounds;
//放置播放器的视图
[self.backgroundView.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:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

在监听回调中:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)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时进行播放。

2.视频的时间信息

在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);
}];
3.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);
}
4.playbackBufferEmpty

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

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

在KVO回调里:

if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
 
    //some code show loading   
}
5.playbackLikelyToKeepUp

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

[self.playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
/* ... */
if([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {    
    //由于 AVPlayer 缓存不足就会自动暂停,所以缓存充足了需要手动播放,才能继续播放  
    [_player play];      
}

AVURLAsset

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

播放相关通知

1.声音类:
//声音被打断的通知(电话打来)
AVAudioSessionInterruptionNotification
//耳机插入和拔出的通知
AVAudioSessionRouteChangeNotification

根据userInfo判断具体状态

2.播放类
AVPlayerItemDidPlayToEndTimeNotification
//播放失败
AVPlayerItemFailedToPlayToEndTimeNotification
//异常中断
AVPlayerItemPlaybackStalledNotification

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

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerMovieFinish:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
3.系统状态
//进入后台
UIApplicationWillResignActiveNotification
//返回前台
UIApplicationDidBecomeActiveNotification

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

你可能感兴趣的:(AVPlayer)