zoukankan      html  css  js  c++  java
  • 使用AVPlayer制作一个播放器

    代码地址如下:
    http://www.demodashi.com/demo/11685.html

    AVPlayer 是一个强大的视频播放器,可以播放多种格式的视频,缺点是没有控制界面,需要自己去实现。

    一、效果图

    二、实现过程

    先看下它的结构

    overview
    首先初始化播放器,设置播放URL。

    self.avPlayerView = [[XYAVPlayerView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, VIDEO_HEIGHT)];
    self.avPlayerView.videoUrl = m3u;
    [self.view addSubview:self.avPlayerView];
    

    初始化方法,添加一个视频控制器。

    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if ([super initWithFrame:frame]) {
            self.backgroundColor = [UIColor blackColor];
            [self addSubview:self.playControlView];
        }
        return self;
    }
    

    设置视频URL

    
    - (void)setVideoUrl:(NSString *)videoUrl
    {
        _videoUrl = videoUrl.copy;
        [self createAVPlayer];
    
    }
    

    初始化 AVPlayer,给_playControlView 引用AVPlayer,方便进行控制, [_playControlView addObserver]; [_playControlView playerTimerAction];会在后面说明。

    
    - (void)createAVPlayer
    {
        
        NSURL *url = [NSURL URLWithString:self.videoUrl];
        if (!url) {
            return;
        }
      
    
        /**
         *  AVPlayer
         */
        _avPlayerItem = [AVPlayerItem playerItemWithURL:url];
        _avPlayer = [AVPlayer playerWithPlayerItem:_avPlayerItem];
        _avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
        _avPlayerLayer.frame = self.bounds;
        _avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;//
        
        
        [self.layer addSublayer:_avPlayerLayer];
        
        [_avPlayer play];
        
        self.autoresizesSubviews = YES; //子视图Size自适应
        
        _playControlView.avPlayer = self.avPlayer;
        _playControlView.avPlayerItem = self.avPlayerItem;
        _playControlView.avPlayerLayer = self.avPlayerLayer;
        
        [_playControlView addObserver];
        [_playControlView playerTimerAction];
        
        
        [self bringSubviewToFront:self.playControlView];
    }
    

    到这里只是创建了一个View,上面加载了一个AVPlayer,一个视频控制器视图。

    视频控制器代码:
    我用的xib创建的View,所以初始化方法是awakeFromNib

    
    - (void)awakeFromNib
    {
        [super awakeFromNib];
        
        [self configureVolume];
        
        
        UITapGestureRecognizer * screenTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(screenTap)];
        [self addGestureRecognizer:screenTap];
        
        
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRecognizerAction:)];
        pan.delegate = self;
        
        [_adjustView addGestureRecognizer:pan];
        
        
        [self.slider setThumbImage:[UIImage imageNamed:@"verify_code_button"] forState:UIControlStateNormal];
        
        // slider开始滑动事件
        [_slider addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown];
        // slider滑动中事件
        [_slider addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged];
        // slider结束滑动事件
        [_slider addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside];
        // slider 添加点击手势
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(sliderTap:)];
        [_slider addGestureRecognizer:tap];
        
        
        [self hiddenViews];
    
    }
    
    
    /**
     *  获取系统音量
     */
    - (void)configureVolume {
        
        MPVolumeView *volumeView = [[MPVolumeView alloc] init];
        _volumeSlider = nil;
        for (UIView *view in [volumeView subviews]){
            if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
                _volumeSlider = (UISlider *)view;
                break;
            }
        }
        
        // 使用这个category的应用不会随着手机静音键打开而静音,可在手机静音下播放声音
        NSError *setCategoryError = nil;
        BOOL success = [[AVAudioSession sharedInstance]
                        setCategory: AVAudioSessionCategoryPlayback
                        error: &setCategoryError];
        
        if (!success) { /* handle the error in setCategoryError */ }
        
    }
    

    第一个手势screenTap,是控制下方控制条的显示与隐藏的

    - (void)screenTap {
        self.controlStripView.hidden = !self.controlStripView.hidden;
    }
    

    第二个手势则是用来控制快进、快退、音量、亮度调节的

    - (void)panGestureRecognizerAction: (UIPanGestureRecognizer *)sender {
        
        //根据在view上Pan的位置,确定是调音量还是亮度
        CGPoint locationPoint = [sender locationInView:self];
        
        CGPoint veloctyPoint = [sender velocityInView:self];
        
        switch (sender.state) {
            case UIGestureRecognizerStateBegan:{
                // 使用绝对值来判断移动的方向
                CGFloat x = fabs(veloctyPoint.x);
                CGFloat y = fabs(veloctyPoint.y);
                
                if (x > y) { //水平移动
                    [self showViews];
                    [self playerPause];
                    self.pandirection = PanDirectionHorizontalMoved;
                }else { //垂直移动
                    self.pandirection = PanDirectionVerticalMoved;
                }
                break;
            }
            case UIGestureRecognizerStateChanged:{
                switch (_pandirection) {
                    case PanDirectionHorizontalMoved:{
                        [self horizontalMoved:veloctyPoint.x];
                        break;
                    }
                    case PanDirectionVerticalMoved:{
                        if (locationPoint.x > self.bounds.size.width / 2) {//音量调节-右侧
                            [self verticalMovedForVolume:veloctyPoint.y];
                        }else {//亮度调节-左侧
                            [self verticalMovedForBrightness:veloctyPoint.y];
                        }
                        break;
                    }
                }
                break;
            }
            case UIGestureRecognizerStateEnded:{
                switch (_pandirection) {
                    case PanDirectionHorizontalMoved:{
                        [self hiddenViews];
                        [self playerPlay];
                        break;
                    }
                    case PanDirectionVerticalMoved:{
                        break;
                    }
                }
                break;
            }
            default:break;
        }
    }
    

    对于slider的方法,在开始手势的时候暂停视频播放,显示时间label

    
    - (void)progressSliderTouchBegan: (UISlider *)sender {
        
        self.changeTimeLabel.hidden = NO;
        
        [self playerPause];
        
        
        NSLog(@" --- began touch");
    }
    

    在开始手势的时候seek到slider对应时间的时间点,然后开始视频播放,隐藏时间label

    
    - (void)progressSliderTouchEnded: (UISlider *)sender {
        CMTime durationTime = self.avPlayerItem.duration;
        
        NSTimeInterval currentTime = CMTimeGetSeconds(durationTime) * sender.value;
        
        [self seekWithTime:currentTime];
        
        self.changeTimeLabel.hidden = YES;
        
        [self playerPlay];
        
        
        NSLog(@" ---- end touch");
    }
    

    slider滑动的时候,只是改变label上显示的时间

    
    - (void)progressSliderValueChanged: (UISlider *)sender {
        
        CGFloat currentTime = sender.value * CMTimeGetSeconds(self.avPlayerItem.duration);
        
        NSString *tempCurrentTime = [self timeFormatterForServiceWithTimeStamp:currentTime];
        
        //当前播放时间
        self.currentTimeLabel.text = tempCurrentTime;
        
        //屏幕中间时间
        self.changeTimeLabel.text = tempCurrentTime;
        
        
        CGFloat sliderProgress = sender.value / sender.maximumValue;
        
        if (self.progressView.progress < sliderProgress) {
            self.progressView.progress = sender.value / sender.maximumValue;
        }
        
        
        
        NSLog(@" --- event touch  %f",sender.value);
        
        
    }
    

    slider的单击方法则是直接seek到对应时间点,AVPlayer会自动处理的。

    
    /**
     *  Slider Tap
     */
    - (void)sliderTap: (UITapGestureRecognizer *)sender {
        
        if ([sender.view isKindOfClass:[UISlider class]]) {
            
            UISlider *slider = (UISlider *)sender.view;
            CGPoint point = [sender locationInView:slider];
            CGFloat length = slider.frame.size.width;
            
            CGFloat tempValue = point.x / length;
            
            NSTimeInterval currentTime = CMTimeGetSeconds(self.avPlayerItem.duration) * tempValue;
            
            CGFloat progress = currentTime/CMTimeGetSeconds(self.avPlayerItem.duration);
            
            if (progress > slider.value) {
                self.progressView.progress = progress;
            }
            
            
            [self seekWithTime:currentTime];
        }
        
    }
    

    前面用的 addObserver方法,则是给播放器添加观察者,用来检测播放器状态,还有APP的状态。

    
    - (void)addObserver
    {
        //监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
        [self.avPlayerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
        //监控网络加载情况属性
        [self.avPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
        
        /**
         *  进入后台  暂停播放
         *
         */
        [kNotificationCenter addObserver:self selector:@selector(applicationDidEnterBackground_Notification) name:ApplicationDidEnterBackground_Notification object:nil];
        
        /**
         *  进入活跃状态  继续播放
         *
         */
        [kNotificationCenter addObserver:self selector:@selector(applicationDidBecomeActive_Notification) name:ApplicationDidBecomeActive_Notification object:nil];
        
        [kNotificationCenter addObserver:self selector:@selector(playerReset) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
        
    }
    
    
    
    /** * 通过KVO监控播放器状态 *
     * @param keyPath 监控属性
     * @param object 监视器
     * @param change 状态改变
     * @param context 上下文 */
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    {
        AVPlayerItem *playerItem=object;
        if ([keyPath isEqualToString:@"status"]) {
            AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
            if(status==AVPlayerStatusReadyToPlay){
                //总时长
                self.allTimeLabel.text = [self timeFormatterForServiceWithTimeStamp:CMTimeGetSeconds(playerItem.duration)];
                self.currentTimeLabel.text = @"00:00:00";
                
                NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
            }
        }
        else 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(@"共缓冲:%.2f",totalBuffer);
            double ableScale = totalBuffer / CMTimeGetSeconds(playerItem.duration);
            if (ableScale <= 1) {
                self.progressView.progress = ableScale;
            }
        }
    }
    

    playerTimerAction方法则是制定每秒进行一次返回,返回当前播放进度。

    
    - (void)playerTimerAction
    {
        __weak XYAVControlView * weakSelf = self;
        
        [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
            
            float current=CMTimeGetSeconds(time);
            if (current) {
                //当前播放进度
                weakSelf.currentTimeLabel.text = [weakSelf timeFormatterForServiceWithTimeStamp:CMTimeGetSeconds(weakSelf.avPlayerItem.currentTime)];
                //滑块进度
                double totalTempTime = CMTimeGetSeconds(weakSelf.avPlayerItem.duration);
                double scale = CMTimeGetSeconds(weakSelf.avPlayerItem.currentTime) / totalTempTime;
                weakSelf.slider.value = scale;
            }
        }];
    }
    

    最后在 ViewController 里面监控屏幕方向的变化,来处理全屏效果

    
    [kNotificationCenter  addObserver:self
                                 selector:@selector(onDeviceOrientationChange)
                                     name:UIDeviceOrientationDidChangeNotification
                                   object:nil];
    
    
    
    
    
    
    /**
     *  屏幕方向发生变化会调用这里
     */
    - (void)onDeviceOrientationChange
    {
        UIDeviceOrientation orientation             = [UIDevice currentDevice].orientation;
        UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
        switch (interfaceOrientation) {
            case UIInterfaceOrientationPortraitUpsideDown:
            case UIInterfaceOrientationPortrait:{
                self.view.frame = (CGRect){0,0,ScreenWidth,ScreenHeight};
                self.avPlayerView.frame = CGRectMake(0, 0,ScreenWidth,VIDEO_HEIGHT);
                self.avPlayerView.playControlView.isFullScreen = NO;
                break;
            }
            case UIInterfaceOrientationLandscapeLeft:
            case UIInterfaceOrientationLandscapeRight:{
                self.view.frame = (CGRect){0,0,ScreenWidth,ScreenHeight};
                self.avPlayerView.frame = self.view.bounds;
                self.avPlayerView.playControlView.isFullScreen = YES;
                break;
            }
            default:
                break;
        }
    }
    

    到这里一个简单的视频播放器就做完了。

    三、项目代码截图

    第一层目录结构

    第二层目录结构


    使用AVPlayer制作一个播放器

    代码地址如下:
    http://www.demodashi.com/demo/11685.html

    注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    ConcurrentHashMap的使用和原理
    记录下项目中常用到的JavaScript/JQuery代码一(大量实例)
    layer ui插件显示tips时,修改字体颜色
    flash上传文件,如何解决跨域问题
    ubuntu下的mv命令
    Semantic 3D
    shellnet运行train_val_seg.py
    Tensorflow的不足之处
    用pip命令把python包安装到指定目录
    ubuntu建立文件或者文件夹软链接
  • 原文地址:https://www.cnblogs.com/demodashi/p/8509528.html
Copyright © 2011-2022 走看看