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大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    B.Icebound and Sequence
    Educational Codeforces Round 65 (Rated for Div. 2) D. Bicolored RBS
    Educational Codeforces Round 65 (Rated for Div. 2) C. News Distribution
    Educational Codeforces Round 65 (Rated for Div. 2) B. Lost Numbers
    Educational Codeforces Round 65 (Rated for Div. 2) A. Telephone Number
    Codeforces Round #561 (Div. 2) C. A Tale of Two Lands
    Codeforces Round #561 (Div. 2) B. All the Vowels Please
    Codeforces Round #561 (Div. 2) A. Silent Classroom
    HDU-2119-Matrix(最大匹配)
    读书的感想!
  • 原文地址:https://www.cnblogs.com/demodashi/p/8509528.html
Copyright © 2011-2022 走看看