2.监听视频的播放情况
2018-08-01 本文已影响113人
豆丶浆油条
上一篇文章实现了视频播放的功能,但是这些还远远不够。我们还需要视频缓冲和播放的情况等,这些都可以通过KVO实现。这些都属于视频的动态信息,所以都是由AVPlayerItem提供的。
1.AVPlayerItem的几个属性
1.视频资源加载的状态
@property (nonatomic, readonly) AVPlayerItemStatus status;
有三个值:
AVPlayerItemStatusUnknown(未知的)
AVPlayerItemStatusReadyToPlay(准备好了,马上开始播放)
AVPlayerItemStatusFailed (加载失败)
2.视频的尺寸
@property (nonatomic, readonly) CGSize presentationSize;
- 缓冲的情况
@property (nonatomic, readonly) NSArray<NSValue *> *loadedTimeRanges;
这是一个数组,里面的元素是CMTimeRange结构体,它表示视频缓冲到哪里了
// 获取缓存的进度
- (NSTimeInterval)loadedTime {
NSArray *timeRanges = _playerItem.loadedTimeRanges;
// 播放的进度
CMTime currentTime = _player.currentTime;
// 判断播放的进度是否在缓存的进度内
BOOL included = NO;
CMTimeRange firstTimeRange = {0};
if (timeRanges.count > 0) {
firstTimeRange = [[timeRanges objectAtIndex:0] CMTimeRangeValue];
if (CMTimeRangeContainsTime(firstTimeRange, currentTime)) {
included = YES;
}
}
// 存在返回缓存的进度
if (included) {
CMTime endTime = CMTimeRangeGetEnd(firstTimeRange);
NSTimeInterval loadedTime = CMTimeGetSeconds(endTime);
if (loadedTime > 0) {
return loadedTime;
}
}
return 0;
}
- 视频是否可以正常播放
@property (nonatomic, readonly, getter=isPlaybackLikelyToKeepUp) BOOL playbackLikelyToKeepUp;
这个属性是对视频是否可以继续播放的一种预测,如果为NO,视频就会暂停。视频不能继续播放的原因主要有两个,视频没有缓冲了和缓存的数据不能正确解码(视频播放器不支持视频的格式)。所以当playbackBufferEmpty为NO,playbackBufferFull(是否已经全部缓存)为YES时,playbackLikelyToKeepUp也有可能为NO。
5.缓冲是否为空
@property (nonatomic, readonly, getter=isPlaybackBufferEmpty) BOOL playbackBufferEmpty;
这个值为YES,视频就会暂停。当这个值为NO,视频也可能不能继续播放。具体原因参考上面的属性。
2.监听视频的播放情况
#import "FHSamplePlayerViewController.h"
// 导入AVFoundation框架
#import <AVFoundation/AVFoundation.h>
@interface FHSamplePlayerViewController ()
{
id _timeObserver;
id _itmePlaybackEndObserver;
}
// 播放资源:只包含媒体资源的静态信息
@property (nonatomic, strong) AVURLAsset *asset;
// 播放单元:包含媒体资源的动态信息:是否可以播放,播放进度,缓存进度,视屏的尺寸,是否播放完,缓冲情况(可以正常播放还是网络情况不好)
@property (nonatomic, strong) AVPlayerItem *playerItem;
// 播放器
@property (nonatomic, strong) AVPlayer *player;
// 播放器界面
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@end
@implementation FHSamplePlayerViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self initPlayer];
}
- (void)initPlayer
{
NSString *strURL = @"https://www.apple.com/105/media/cn/home/2018/da585964_d062_4b1d_97d1_af34b440fe37/films/behind-the-mac/mac-behind-the-mac-tpl-cn_848x480.mp4";
NSURL *url = [NSURL URLWithString:strURL];
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
self.asset = asset;
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
// 暂停的时候不能继续缓冲
item.canUseNetworkResourcesForLiveStreamingWhilePaused = NO;
if (@available(iOS 10.0, *)) {
// 提前缓冲1s
item.preferredForwardBufferDuration = 1.0;
}
self.playerItem = item;
AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
if (@available(iOS 10.0, *)) {
// 网络不好的时候,不允许降低播放速度
player.automaticallyWaitsToMinimizeStalling = NO;
}
self.player = player;
AVPlayerLayer *avLayer = [AVPlayerLayer playerLayerWithPlayer:player];
// 适配avLayer的时候,视频的长宽比例不能改变。这样如果视频和avLayer的长宽比例不一致,就会留空白。
avLayer.videoGravity = AVLayerVideoGravityResizeAspect;
avLayer.frame = self.view.bounds;
[self.view.layer addSublayer:avLayer];
self.playerLayer = avLayer;
// 播放视频
[player play];
[self addObserver];
}
- (void)stop
{
[self.player pause];
[self.player removeTimeObserver:_timeObserver];
_timeObserver = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
_itmePlaybackEndObserver = nil;
self.player = nil;
self.playerItem = nil;
self.asset = nil;
[self.playerLayer removeFromSuperlayer];
[self removeObserver];
}
- (void)addObserver
{
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[self.playerItem addObserver:self forKeyPath:@"presentationSize" options:NSKeyValueObservingOptionNew context:nil];
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[self.playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
[self.playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
CMTime interval = CMTimeMakeWithSeconds(0.5, NSEC_PER_SEC);
__weak FHSamplePlayerViewController *weakSelf= self;
// 增加播放进度的监听 每0.5秒调用一次
_timeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
if (!weakSelf) return;
NSArray *loadedRanges = weakSelf.playerItem.seekableTimeRanges;
if (loadedRanges.count > 0 && weakSelf.playerItem.duration.timescale != 0) {
NSLog(@"播放进度 = %.2f",CMTimeGetSeconds(time));
NSLog(@"视频总时长 = %.2f",CMTimeGetSeconds(weakSelf.playerItem.duration));
}
}];
// 增加播放结束的监听
_itmePlaybackEndObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"本视频播放结束了");
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"status"]) {
switch (self.playerItem.status) {
case AVPlayerItemStatusUnknown:
NSLog(@"未知的播放状态");
break;
case AVPlayerItemStatusReadyToPlay:
NSLog(@"马上可以播放了");
break;
case AVPlayerItemStatusFailed:
NSLog(@"发生错误:%@",self.player.error);
break;
default:
break;
}
}
if ([keyPath isEqualToString:@"presentationSize"]) {
NSLog(@"视频的尺寸:%@",NSStringFromCGSize(self.playerItem.presentationSize));
}
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSLog(@"缓冲进度:%.2f",[self loadedTime]);
}
if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
NSLog(@"%@可以正常播放",self.playerItem.playbackLikelyToKeepUp ? @"" : @"不");
}
if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
NSLog(@"%@有缓冲",self.playerItem.playbackBufferEmpty ? @"没": @"");
}
}
- (void)removeObserver
{
@try{
[self.playerItem removeObserver:self forKeyPath:@"status"];
[self.playerItem removeObserver:self forKeyPath:@"presentationSize"];
[self.playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[self.playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
[self.playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
} @catch(NSException *e){
NSLog(@"failed to remove observer");
}
}
// 获取缓存的进度
- (NSTimeInterval)loadedTime {
NSArray *timeRanges = _playerItem.loadedTimeRanges;
// 播放的进度
CMTime currentTime = _player.currentTime;
// 判断播放的进度是否在缓存的进度内
BOOL included = NO;
CMTimeRange firstTimeRange = {0};
if (timeRanges.count > 0) {
firstTimeRange = [[timeRanges objectAtIndex:0] CMTimeRangeValue];
if (CMTimeRangeContainsTime(firstTimeRange, currentTime)) {
included = YES;
}
}
// 存在返回缓存的进度
if (included) {
CMTime endTime = CMTimeRangeGetEnd(firstTimeRange);
NSTimeInterval loadedTime = CMTimeGetSeconds(endTime);
if (loadedTime > 0) {
return loadedTime;
}
}
return 0;
}
- (void)dealloc
{
[self stop];
NSLog(@"%@ dealloc",[self class]);
}
@end
通过KVO、通知和系统提供的方法,可以完美监测播放的缓冲情况、播放进度、播放结束等,这样我们就可以给视频增加播放进度条、缓冲进度条等UI。