美文网首页
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.监听视频的播放情况

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