美文网首页音视频iOS图形处理相关临时收藏
AVPlayer 本地、网络视频播放相关

AVPlayer 本地、网络视频播放相关

作者: 阿聪o | 来源:发表于2016-06-21 09:42 被阅读13692次

    iOS开发常用的两种视频播放方式,一种是使用MPMoviePlayerController,还有就是使用AVPlayer。MPMoviePlayerController系统高度封装使用起来很方便,但是如果要高度自定义播放器就比较麻烦。而AVPlayer则恰好相反,灵活性更强,使用起来也麻烦一点。本文将对AVPlayer的使用做个简单的介绍。

    1、AVPlayer加载播放视频

    AVPlayer继承NSObject,所以单独使用AVPlayer时无法显示视频的,必须将视频图层添加到AVPlayerLayer中方能显示视频。使用AVPlayer首先了解一下几个常用的类:

    AVAsset:AVAsset类专门用于获取多媒体的相关信息,包括获取多媒体的画面、声音等信息。
    AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
    AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。

    AVPlayer加载视频的代码如下:

        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@"http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4"]];
        //添加监听
        [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
        [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
        self.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
        
        AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
        //设置模式
        playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        playerLayer.contentsScale = [UIScreen mainScreen].scale;
        playerLayer.frame = CGRectMake(0, 100, self.view.bounds.size.width, 200);
        [self.view.layer addSublayer:playerLayer];
    
    
    
    //监听回调
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    {
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        
        if ([keyPath isEqualToString:@"loadedTimeRanges"]){
            
        }else if ([keyPath isEqualToString:@"status"]){
            if (playerItem.status == AVPlayerItemStatusReadyToPlay){
                NSLog(@"playerItem is ready");
                [self.avPlayer play];
            } else{
                NSLog(@"load break");
            }
        }
    }
    

    此处代码中添加了对AVPlayerItem的"loadedTimeRanges"和"status"属性监听,status枚举值有 AVPlayerItemStatusUnknown,AVPlayerItemStatusReadyToPlay, AVPlayerItemStatusFailed。只有当status为AVPlayerItemStatusReadyToPlay是调用 AVPlayer的play方法视频才能播放。

    运行效果
    D82C00B0-4D83-4B1A-82EC-B245F15F40E0.png

    2、AVPlayer当前缓冲进度以及当前播放进度的处理

    获取视频当前的缓冲进度:

    通过监听AVPlayerItem的"loadedTimeRanges",可以实时知道当前视频的进度缓冲,计算方法如下:

    - (NSTimeInterval)availableDurationWithplayerItem:(AVPlayerItem *)playerItem
    {
        NSArray *loadedTimeRanges = [playerItem loadedTimeRanges];
        CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域
        NSTimeInterval startSeconds = CMTimeGetSeconds(timeRange.start);
        NSTimeInterval durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval result = startSeconds + durationSeconds;// 计算缓冲总进度
        return result;
    }
    
    获取视频当前的播放进度:
        //视频当前的播放进度
        NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
        //视频的总长度
        NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
    

    AVPlayer提供了一个Block回调,当播放进度改变的时候回主动回调该Block,但是当视频卡顿的时候是不会回调的,可以在该回调里面处理进度条以及播放时间的刷新,详细方法如下:

         __weak __typeof(self) weakSelf = self;
        [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
            //当前播放的时间
            NSTimeInterval current = CMTimeGetSeconds(time);
            //视频的总时间
            NSTimeInterval total = CMTimeGetSeconds(weakSelf.avPlayer.currentItem.duration);
            //设置滑块的当前进度
            weakSelf.slider.sliderPercent = current/total;
            NSLog(@"%f", weakSelf.slider.sliderPercent);
           //设置时间
            weakSelf.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [weakSelf formatPlayTime:current], [weakSelf formatPlayTime:total]];
        }];
    
    
    //将时间转换成00:00:00格式
    - (NSString *)formatPlayTime:(NSTimeInterval)duration
    {
        int minute = 0, hour = 0, secend = duration;
        minute = (secend % 3600)/60;
        hour = secend / 3600;
        secend = secend % 60;
        return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];
    }
    
    改变视频当前的播放进度:

    当滑块滑动的时候需要改变当前视频的播放进度,代码如下:

    //处理滑块
    - (void)progressValueChange:(AC_ProgressSlider *)slider
    {
           //当视频状态为AVPlayerStatusReadyToPlay时才处理(当视频没加载的时候,直接禁止掉滑块事件)
           if (self.avPlayer.status == AVPlayerStatusReadyToPlay) {
            NSTimeInterval duration = self.slider.sliderPercent* CMTimeGetSeconds(self.avPlayer.currentItem.duration);
            CMTime seekTime = CMTimeMake(duration, 1);
    
            [self.avPlayer seekToTime:seekTime completionHandler:^(BOOL finished) {
    
            }];
        }
    }
    
    播放进度控件的定制:

    该控件应该包含4个部分,总进度、缓冲进度、当前播放进度还有一个滑块。效果图如下:


    08D61DA5-5645-4B1D-A170-ABAAB6088B19.png

    最简单的实现方式是UIProgressView跟UISlider两个控件叠加起来,效果不是太好。demo是自定义的UIControl,详细实现方式请查看demo中的AC_ProgressSlider类。

    当视频卡顿的时候处理旋转loading方法:

    上面说过- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;该方法在卡顿的时候不会回调,所以只用该方法处理不了这种情况。我好像也没找到相关的api,所以demo中采用的是开启定时器,然后用一个lastTime保留当前的播放进度,当下次调用的时候用lastTime跟当前的进度进行比较,如果相等说明播放卡顿了,代码如下:

        self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(upadte)];
        [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    //更新方法
    - (void)upadte
    {
        NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
        NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
       //如果用户在手动滑动滑块,则不对滑块的进度进行设置重绘
        if (!self.slider.isSliding) {
            self.slider.sliderPercent = current/total;
        }
        
        if (current!=self.lastTime) {
            [self.activity stopAnimating];
            self.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [self formatPlayTime:current], [self formatPlayTime:total]];
        }else{
            [self.activity startAnimating];
        }
        self.lastTime = current;
    }
    

    3、AVPlayer播放暂停的处理

    这个比较简单、 分别是pause和play方法

    //播放暂停按钮
    - (void)playOrPauseAction:(UIButton *)sender
    {
        sender.selected = !sender.selected;
        
        if (self.avPlayer.rate == 1) {
            [self.avPlayer pause];
            self.link.paused = YES;
            [self.activity stopAnimating];
        } else {
            [self.avPlayer play];
            self.link.paused = NO;
        }
    }
    

    4、AVPlayer播放完成的处理

    添加通知即可

     //播放完成通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(moviePlayDidEnd)
                                                     name:AVPlayerItemDidPlayToEndTimeNotification
                                                   object:nil];
    

    5、更换当前播放的AVPlayerItem

    当视频播放完时或者用户切换不同的视频时候就要更换当前的视频,代码如下:

    //切换当前播放的内容
    - (void)changeCurrentplayerItemWithAC_VideoModel:(AC_VideoModel *)model
    {
        if (self.avPlayer) {
            
            //由暂停状态切换时候 开启定时器,将暂停按钮状态设置为播放状态
            self.link.paused = NO;
            self.playButton.selected = NO;
            
            //移除当前AVPlayerItem对"loadedTimeRanges"和"status"的监听
            [self removeObserveWithPlayerItem:self.avPlayer.currentItem];
            AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:model.url];
            [self addObserveWithPlayerItem:playerItem];
            self.avPlayerItem = playerItem;
            //更换播放的AVPlayerItem
            [self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
            
            self.playButton.enabled = NO;
            self.slider.enabled = NO;
        }
    }
    
    

    感觉写的有点乱,详细的看demo吧,demo运行效果如下:

    海贼王.gif
    完整代码7牛下载连接

    相关文章

      网友评论

      • MUYO_echo:你好,我想在播放视频之前,播放一个固定的广告视频,怎么实现啊
      • 月禅:[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        这儿不能使用NSDefaultRunLoopMode,要用NSRunLoopCommonModes.
      • 帅气的华子:应用竖屏,播放器怎样强制横屏播放呢,我把plist Landscape Right/left 去掉,播放页面闪退
      • Zszen:求助播放本地视频卡顿怎么办
        Zszen:加载时候总会愣几秒
      • 茄子_Apple:AVPlayer的播放状态status,在什么时候自动切换么?是否能实现边缓存边播放?
      • liyaoyao:你好 ,我用avplayer播放本地视频,用这个方法监听进度,addPeriodicTimeObserverForInterval ,但是有时候返回的当前进度是错误的,请问这个怎么回事 ?
      • Aacmr:楼主 请问怎么 让视频从上次播放的地方继续播放。我们后台返回了上次播放的值,我用了这个方法改的CMTime - (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block; 显示不正确,不知为什么? 应该在哪个方法里该呀?
        Aacmr:@阿聪o 谢谢 我就是用这个实现的,太感谢啦
        阿聪o:@Aacmr [self.avPlayer seekToTime:seekTime completionHandler:^(BOOL finished) {

        }];
      • KnightOrKing:感谢作者
      • MrJ的杂货铺:真机播放没有声音,怎么回事
      • 原野de呼唤:HTTPS 的网络视频怎么解决呢? 1月1日不是强制要求支持HTTPS吗
        知道自己不知道:这个官方文档里面有详细的说明 ,此次更新不包括网络视频资源!!
      • 277ccd5ab3f3:AVPlayer 怎么释放掉加载好的视频资源 比如某个视频我看到一半,想把前半段加载的释放掉,这个能实现么
      • FingertipFish:升级xcode8会有影响吗
      • 7emini:666
      • MrYudeJianShu:thank you

      本文标题:AVPlayer 本地、网络视频播放相关

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