美文网首页
2.监听视频的播放情况

2.监听视频的播放情况

作者: 豆丶浆油条 | 来源:发表于2018-08-01 19:42 被阅读113次

  上一篇文章实现了视频播放的功能,但是这些还远远不够。我们还需要视频缓冲和播放的情况等,这些都可以通过KVO实现。这些都属于视频的动态信息,所以都是由AVPlayerItem提供的。

1.AVPlayerItem的几个属性

1.视频资源加载的状态

@property (nonatomic, readonly) AVPlayerItemStatus status;

有三个值:
AVPlayerItemStatusUnknown(未知的)
AVPlayerItemStatusReadyToPlay(准备好了,马上开始播放)
AVPlayerItemStatusFailed (加载失败)

2.视频的尺寸

@property (nonatomic, readonly) CGSize presentationSize;
  1. 缓冲的情况
@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;
}
  1. 视频是否可以正常播放
@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。

Demo

相关文章

  • 2.监听视频的播放情况

      上一篇文章实现了视频播放的功能,但是这些还远远不够。我们还需要视频缓冲和播放的情况等,这些都可以通过KVO实现...

  • iOS 媒体播放

    1.播放器 2.监听播放状态 AVPlayerViewController 4.调用相机录制视频或者选择媒体库 5...

  • 手机影音03

    阅读原文 19-监听播放外界网络和本地视频&如何调起其他播放器 19.1监听播放外界网络和本地视频,参照Galle...

  • 获取MPMoviePlayerController当前播放状态

    使用MPMoviePlayerController播放视频时,需要监听当前视频的播放状态,从而做相应的逻辑判断,比...

  • 阿里巴巴前端工程师面试者,总结H5视频表现方式,成功拿下offe

    1. 同屏播放视频 2. 移动端视频预加载 由于移动端不能预加载视频,所以hack一种方案:监听WXJSBridg...

  • 监听网络状态

    监听网络状态 在日常开发中,我们经常会遇到这种情况,需要我们对手机的网络状态进行监听 比如:播放视频需要提示是4G...

  • 开发画中画遇到的一些问题

    1.如何获取画中画的暂停和播放状态。在AVPlayer中监听timeControlStatus 2.视频全屏的时候...

  • Android监听横竖屏切换

    偶然在项目中用到播放视频时,需要横屏将视频全屏播放,所以需要监听屏幕的横竖屏切换事件。 横竖屏切换监听效果: Co...

  • KVO在MemoryWarning时引起的crash

    业务场景: 播放的视频滑出屏幕,停止播放。 实现: 使用kvo监听collectionView的contentOf...

  • WKWebView对接h5问题合集

    1.视频播放 2.链接带空格字符 3.kvo监听title,url变化 监听回调 4.打印h5页面栈 5.js与原...

网友评论

      本文标题:2.监听视频的播放情况

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