zoukankan      html  css  js  c++  java
  • iOS:基于AVPlayer实现的视频播放器

    最近在学习AVFoundation框架的相关知识,写了一个基于AVPlayer的视频播放器,相关功能如下图:

     

     

    代码github:https://github.com/wzpziyi1/VideoPlayer

     

    AVFoundation都是围绕AVPlayer展开的,AVPlayer是一个用来播放基于时间的视听媒体的控制器对象。但它与我们通常理解的"控制器"不同,它不是一个视图或者窗口控制器,而是一个对播放和资源时间相关信息进行管理的对象,具体的用户界面是需要开发者自己进行编写的。

    AVPlayer是一个不可见的组件,如果需要有可视化的界面,那么需要使用AVPlayerLayer类。AVPlayerLayer构建于Core Animation之上,它扩展了Core Animation的CALayer类,并通过框架在屏幕上显示视频内容(它只是用作视频内容的渲染面,其他可视化界面需要自己编写)。创建AVPlayerLayer需要一个指向AVPlayer实例的指针,这就可以将图层和播放器绑定在一起了,保证了当播放器基于时间的方法出现时使二者保持同步。

     

    AVPlayerItem

      它会建立媒体资源动态视角的数据模型,并保存AVPlayer在播放时的呈现状态。
      例如seekToTime:方法:

       __weak typeof(self) tmp = self;
        [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
            if (finished)
            {
                tmp.transport.isFinishedJump = YES;
            }
        }];
    
    //跳转到哪个时间点进行播放
    

      AVPlayerItem有两个很重要的属性,一个是status,一个是loadedTimeRanges。使用kvo监听status属性值的改变,当status变为           
      AVPlayerStatusReadyToPlay的时候,表示可以开始播放了。而监听loadedTimeRanges属性,可以知道视频缓存到哪样一个时间点了:

    //kvo
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    {
        AVPlayerItem *item = (AVPlayerItem *)object;
        
        if ([keyPath isEqualToString:@"status"])
        {
            NSLog(@"%d", (int)[item status]);
            if ([item status] == AVPlayerStatusReadyToPlay)
            {
                
                [self monitorPlayingStatusWithItem:item];
                
            }
        }
        else if ([keyPath isEqualToString:@"loadedTimeRanges"])    //缓冲
        {
            self.bufferTime = [self availableBufferTime];
            
            if (!self.isFetchTotalDuration)
            {
                //获取视频总长度
                NSTimeInterval totalDuration = CMTimeGetSeconds(item.duration);
                self.transport.durationTime = totalDuration;
                self.isFetchTotalDuration = YES;
            }
            
            if (self.transport.currentPlayTime <= self.transport.durationTime - 12)
            {
                //如果缓冲不够
                if (self.bufferTime <= self.transport.currentPlayTime + 10)
                {
                    self.transport.isBuffering = YES;
                }
                else
                {
                    self.transport.isBuffering = NO;
                }
            }
            else
            {
                self.transport.isBuffering = NO;
            }
            
            self.transport.currentBufferTime = self.bufferTime;
            
        }
        else
        {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }

        在这段代码里面,我是设置了,如果当前播放时间+10s > 缓冲得到的总时间,那么就loading,直到缓冲时间总是大于当前播放时间10s,才开始播放。

      CMTime

        AVFoundation使用基于CMTime的数据结构来展示时间信息,它表现为分数值的方式,具体定义如下:

        

    /*!
    	@typedef	CMTime
    	@abstract	Rational time value represented as int64/int32.
    */
    typedef struct
    {
    	CMTimeValue	value;		/*! @field value The value of the CMTime. value/timescale = seconds. */
    	CMTimeScale	timescale;	/*! @field timescale The timescale of the CMTime. value/timescale = seconds.  */
    	CMTimeFlags	flags;		/*! @field flags The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */
    	CMTimeEpoch	epoch;		/*! @field epoch Differentiates between equal timestamps that are actually different because
    												 of looping, multi-item sequencing, etc.  
    												 Will be used during comparison: greater epochs happen after lesser ones. 
    												 Additions/subtraction is only possible within a single epoch,
    												 however, since epoch length may be unknown/variable. */
    } CMTime;


        在这个结构中,主要关注value和timescale,它们在时间呈现中分别作为分子和分母。

        CMTimeMake(1, 2);  //0.5s
            
        CMTimeMake(4, 1);  //4s
            
        CMTimeMakeWithSeconds(60, NSEC_PER_SEC); //表示将60秒转化为CMTime时间格式
    

     
      
    在这个demo中,可视化界面的编写主要在ZYOverlayView.h类里面,它负责用户的绝大部分交互事件。ZYOverlayView遵守ZYTransport协议,这个协议提供了与ViewController里面的AVPlayer进行通信的接口,相应代码如下:

    #import <UIKit/UIKit.h>
    #import <AVFoundation/AVFoundation.h>
    
    @protocol ZYTransportDelegate <NSObject>
    
    - (void)play;
    
    - (void)pause;
    
    - (void)stop;
    
    /**
     *  跳转到某个时间点播放
     *
     */
    - (void)jumpedToTime:(NSTimeInterval)time;
    
    
    /**
     *  视频横屏
     *
     *  @param flag YES为是
     */
    - (void)fullScreenOrNormalSizeWithFlag:(BOOL)flag;
    @end
    
    @protocol ZYTransport <NSObject>
    
    /**
     *  是否正在缓冲
     */
    @property (nonatomic, assign) BOOL isBuffering;
    
    /**
     *  是否完成跳转
     */
    @property (nonatomic, assign) BOOL isFinishedJump;
    
    @property (nonatomic, assign) NSTimeInterval durationTime;
    
    @property (nonatomic, weak) id<ZYTransportDelegate>delegate;
    
    /**
     *  设置当前播放的时间点
     */
    @property (nonatomic, assign) NSTimeInterval currentPlayTime;
    
    /**
     *  设置当前缓冲的时间点
     */
    @property (nonatomic, assign) NSTimeInterval currentBufferTime;
    
    /**
     *  视频播放完毕
     */
    - (void)playbackComplete;
    
    
    @end
    

    如果要视频可以显示,那么一定需要AVPlayeLayer来做视频内容的渲染面,上面提到了可视化界面的编写是在ZYPlayerView类里面实现的,所在我在这个类里面加载ZYOverlayView,以便实现相关功能的封装。

    #import <UIKit/UIKit.h>
    #import <AVFoundation/AVFoundation.h>
    #import "ZYTransport.h"
    
    
    @interface ZYPlayerView : UIView
    @property (nonatomic, strong) AVPlayer *player;
    
    @property (nonatomic, weak) id<ZYTransport>transport;
    
    @end
    
    
    #import "ZYPlayerView.h"
    #import "ZYOverlayView.h"
    
    @interface ZYPlayerView ()
    @property (nonatomic, strong) ZYOverlayView *overlayView;
    @end
    
    
    @implementation ZYPlayerView
    
    + (Class)layerClass
    {
        return [AVPlayerLayer class];
    }
    
    - (instancetype)init
    {
        if (self = [super init])
        {
            [self commitInit];
        }
        return self;
    }
    
    - (void)awakeFromNib
    {
        [super awakeFromNib];
        
        [self commitInit];
    }
    
    
    - (void)commitInit
    {
        self.overlayView = [ZYOverlayView overlayView];
        
        [self addSubview:self.overlayView];
        
        [self.overlayView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
    }
    
    - (AVPlayer *)player
    {
        return [(AVPlayerLayer *)[self layer] player];
    }
    
    - (void)setPlayer:(AVPlayer *)player
    {
        [(AVPlayerLayer *)[self layer] setPlayer:player];
    }
    
    - (id<ZYTransport>)transport
    {
        return self.overlayView;
    }
    
    
    @end
    

    ViewController主要是在做监听播放状态、缓冲状态,与可视化界面之间的通信,将监听到的当前播放时间、当前缓冲时间、播放结束等传递给遵守了ZYTransport协议的ZYOverlayView实例,在OverlayView实例里面进行相关操作。与可视化界面之间的通信主要表现为,暂停、播放、停止、全屏等,这些在ZYTransport协议里面都有相关的代理方法。

    #import "ZYPlayerVc.h"
    #import "ZYPlayerView.h"
    #import <AVFoundation/AVFoundation.h>
    #import "ZYTransport.h"
    
    @interface ZYPlayerVc () <ZYTransportDelegate>
    @property (strong, nonatomic) ZYPlayerView *playerView;
    
    @property (nonatomic, strong) AVPlayer *player;
    
    @property (nonatomic, strong) AVPlayerItem *playerItem;
    
    @property (nonatomic, weak) id<ZYTransport>transport;
    
    @property (nonatomic, assign) CGFloat scale;
    
    /**
     *  是否获取了视频长度
     */
    @property (nonatomic, assign) BOOL isFetchTotalDuration;
    
    @property (nonatomic, strong) id timeObserver;
    
    @property (nonatomic, assign) NSTimeInterval bufferTime;
    
    
    @end
    
    @implementation ZYPlayerVc
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view from its nib.
        
        self.isFetchTotalDuration = NO;
        
        NSURL *url = [NSURL URLWithString:@"http://v.jxvdy.com/sendfile/w5bgP3A8JgiQQo5l0hvoNGE2H16WbN09X-ONHPq3P3C1BISgf7C-qVs6_c8oaw3zKScO78I--b0BGFBRxlpw13sf2e54QA"];
        self.playerItem = [[AVPlayerItem alloc] initWithURL:url];
        
        //监听status属性
        [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
        
        [self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
        
        self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
        
        self.playerView.player = self.player;
        self.playerView.frame = CGRectMake(0, 30, kScreenW, kScreenW * kScreenW / kScreenH);
        self.scale = kScreenW / self.playerView.height;
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
        
    }
    
    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        
        [[UIApplication sharedApplication] setStatusBarHidden:YES];
    }
    
    - (ZYPlayerView *)playerView
    {
        if (_playerView == nil)
        {
            _playerView = [[ZYPlayerView alloc] init];
            _playerView.backgroundColor = [UIColor blackColor];
            self.transport = _playerView.transport;
            self.transport.isBuffering = YES;
            self.transport.delegate = self;
            [self.view addSubview:_playerView];
        }
        return _playerView;
    }
    
    - (void)moviePlayDidEnd:(NSNotification *)note
    {
        self.isFetchTotalDuration = NO;
        __weak typeof(self) tmp = self;
        
        [self.player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
            tmp.transport.currentPlayTime = 0;
            tmp.transport.currentBufferTime = 0;
            tmp.transport.durationTime = 0;
        }];
        
    }
    
    //kvo
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    {
        AVPlayerItem *item = (AVPlayerItem *)object;
        
        if ([keyPath isEqualToString:@"status"])
        {
            NSLog(@"%d", (int)[item status]);
            if ([item status] == AVPlayerStatusReadyToPlay)
            {
                
                [self monitorPlayingStatusWithItem:item];
                
            }
        }
        else if ([keyPath isEqualToString:@"loadedTimeRanges"])    //缓冲
        {
            self.bufferTime = [self availableBufferTime];
            
            if (!self.isFetchTotalDuration)
            {
                //获取视频总长度
                NSTimeInterval totalDuration = CMTimeGetSeconds(item.duration);
                self.transport.durationTime = totalDuration;
                self.isFetchTotalDuration = YES;
            }
            
            if (self.transport.currentPlayTime <= self.transport.durationTime - 7)
            {
                //如果缓冲不够
                if (self.bufferTime <= self.transport.currentPlayTime + 5)
                {
                    self.transport.isBuffering = YES;
                }
                else
                {
                    self.transport.isBuffering = NO;
                }
            }
            else
            {
                self.transport.isBuffering = NO;
            }
            
            self.transport.currentBufferTime = self.bufferTime;
            
        }
        else
        {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    
    /**
     *  监听播放状态
     *
     */
    - (void)monitorPlayingStatusWithItem:(AVPlayerItem *)item
    {
        __weak typeof(self) tmp = self;
        self.timeObserver = [self.playerView.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
            
            NSTimeInterval currentTime = CMTimeGetSeconds(time);
            
            tmp.transport.currentPlayTime = currentTime;
            
            
        }];
    }
    
    /**
     *  可用的播放时长(缓冲的时长)
     *
     */
    - (NSTimeInterval)availableBufferTime
    {
        NSArray *loadTimeRanges = [[self.player currentItem] loadedTimeRanges];
        
        //获取缓冲区域
        CMTimeRange range = [loadTimeRanges.firstObject CMTimeRangeValue];
        
        NSTimeInterval startTime = CMTimeGetSeconds(range.start);
        
        NSTimeInterval duration = CMTimeGetSeconds(range.duration);
        
        return startTime + duration;
    }
    
    #pragma mark ----ZYTransportDelegate
    
    - (void)play
    {
        [self.playerView.player play];
    }
    
    - (void)pause
    {
        [self.playerView.player pause];
    }
    
    - (void)stop
    {
        
        self.isFetchTotalDuration = NO;
        [self moviePlayDidEnd:nil];
    }
    
    /**
     *  跳转到某个时间点播放
     *
     */
    - (void)jumpedToTime:(NSTimeInterval)time
    {
        __weak typeof(self) tmp = self;
        [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
            if (finished)
            {
                tmp.transport.isFinishedJump = YES;
            }
        }];
    }
    
    /**
     *  视频横竖屏
     *
     *  @param flag YES为是
     */
    - (void)fullScreenOrNormalSizeWithFlag:(BOOL)flag
    {
        
        if (flag)
        {
            CGFloat moveY = self.view.center.y - self.playerView.center.y;
            CGAffineTransform tmpTransform = CGAffineTransformScale(CGAffineTransformMakeTranslation(0, moveY), self.scale, self.scale);
            CGAffineTransform transform = CGAffineTransformRotate(tmpTransform, - M_PI_2);
            
            [UIView animateWithDuration:0.25 animations:^{
                self.playerView.transform = transform;
            }];
        }
        else
        {
            [UIView animateWithDuration:0.25 animations:^{
                self.playerView.transform = CGAffineTransformIdentity;
            }];
        }
    }
    
    
    - (void)dealloc
    {
        [self.playerItem removeObserver:self forKeyPath:@"status"];
        [self.playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
        [self.playerView.player removeTimeObserver:self.timeObserver];
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    

     

    ZYOverlayView里面最主要处理的是一个拖动滑块的事件和5秒后如果没有接到触碰事件隐藏Tool。拖动滑块的话,我是弄了一个pan手势,然后在里面计算相关位置的问题。这里有一个巨坑,一开始我并没有给滑块设置layout,只是想通过改变滑块的center来做到位置随着拖动长度的改变而改变,却发现它在上下两个Tool View隐藏的时候,回到了它的初始位置,后来给它加上layout,这个bug消失,暂时没找到bug产生原因。

     

    基于这样一个bug,我本来是想把所有的布局改成frame布局的,但是改完之后发现,底部的Tool View根本不显示,又是一个未知原因的bug。找了很久,没明白导致这个bug产生的原因......所有我就都改成layout布局,并且未了防止总是刷新layout,在改变const的同时,将它们的frame也相应的改变了。

     

    没有接到触碰事件隐藏Tool,是基于定时器实现的。每隔五秒,如果在五秒内有接受到任意触碰事件,就让定时器先invalidate,在resetTimer,这样确保定时器时间可以实时处于更新状态。相应代码:

    #import <UIKit/UIKit.h>
    #import "ZYTransport.h"
    
    @interface ZYOverlayView : UIView <ZYTransport>
    
    @property (nonatomic, assign) NSTimeInterval durationTime;
    
    @property (nonatomic, weak) id<ZYTransportDelegate>delegate;
    
    /**
     *  是否正在缓冲
     */
    @property (nonatomic, assign) BOOL isBuffering;
    
    @property (nonatomic, assign) BOOL isFinishedJump;
    
    @property (nonatomic, assign) NSTimeInterval currentPlayTime;
    
    @property (nonatomic, assign) NSTimeInterval currentBufferTime;
    
    + (instancetype)overlayView;
    
    @end
    
    
    #import "ZYOverlayView.h"
    
    @interface ZYOverlayView ()
    
    /**
     *  总进度
     */
    @property (weak, nonatomic) IBOutlet UIView *totalProgressView;
    
    @property (weak, nonatomic) IBOutlet UIView *bufferProgressView;
    
    
    /**
     *  进度上面的显示时间
     */
    @property (weak, nonatomic) IBOutlet UIButton *progressTimeBtn;
    
    /**
     *  当前播放时间VIew
     */
    @property (weak, nonatomic) IBOutlet UILabel *currentTimeLabel;
    
    /**
     *  总进度View
     */
    @property (weak, nonatomic) IBOutlet UILabel *totalTimeLabel;
    
    
    
    /**
     *  滑块
     */
    @property (weak, nonatomic) IBOutlet UIImageView *sliderView;
    
    /**
     *  当前缓冲进度的宽度
     */
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *currentProgressConW;
    
    @property (weak, nonatomic) IBOutlet UIView *topView;
    @property (weak, nonatomic) IBOutlet UIView *bottomView;
    @property (weak, nonatomic) IBOutlet UIButton *playOrPauseBtn;
    
    
    @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *indicatorView;
    
    
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *topViewConTop;
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomViewConBottom;
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *sliderConLeft;
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *progressTimeConLeft;
    
    
    
    /**
     *  是否为横屏
     */
    @property (nonatomic, assign) BOOL isfullScreen;
    
    /**
     *  控制topottom View的隐藏定时器
     */
    @property (nonatomic, strong) NSTimer *timer;
    
    /**
     *  判断是否需要控制top/bottom View隐藏
     */
    @property (nonatomic, assign) BOOL isControlHidden;
    
    /**
     *  topottom View是否正在展示
     */
    @property (nonatomic, assign) BOOL isShowing;
    
    /**
     *  不是由于缓冲或者拖动滑块导致的暂停(也就是必然的暂停,交互时的暂停)
     */
    @property (nonatomic, assign) BOOL isCertainPause;
    
    /**
     *  是否正在拖动滑块
     */
    @property (nonatomic, assign) BOOL isDraggingSlider;
    @end
    
    @implementation ZYOverlayView
    
    + (instancetype)overlayView
    {
        return [[self alloc] init];
    }
    
    - (instancetype)init
    {
        if (self = [super init])
        {
            self = [[[NSBundle mainBundle] loadNibNamed:@"ZYOverlayView" owner:nil options:nil] lastObject];
            
            [self commitInit];
        }
        return self;
    }
    
    - (void)commitInit
    {
        self.isfullScreen = NO;
        self.isControlHidden = YES;
        self.isShowing = YES;
        self.indicatorView.hidden = YES;
        self.isCertainPause = NO;
        self.isDraggingSlider = NO;
        self.progressTimeBtn.hidden = YES;
        
        [self.indicatorView startAnimating];
        
        self.layer.masksToBounds = YES;
        self.sliderView.userInteractionEnabled = YES;
        UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(draggingSlider:)];
        [self.sliderView addGestureRecognizer:panRecognizer];
        
        [self resetTimer];
    }
    
    - (void)setDurationTime:(NSTimeInterval)durationTime
    {
        _durationTime = durationTime;
        
        self.totalTimeLabel.text = [self converTimeToStringWithTime:durationTime];
    }
    
    - (void)setIsBuffering:(BOOL)isBuffering
    {
        _isBuffering = isBuffering;
        
        if (self.isCertainPause) return;
        self.isCertainPause = NO;
        
        //由btn的tag来判断此次事件是交互的暂停,还是缓冲导致的暂停
        self.playOrPauseBtn.tag = 1;
        if (isBuffering)
        {
            self.indicatorView.hidden = NO;
            self.playOrPauseBtn.enabled = NO;
            
            
            if (self.playOrPauseBtn.selected)
            {
                
                [self clickPlayOrPauseBtn:self.playOrPauseBtn];
            }
        }
        else
        {
            self.indicatorView.hidden = YES;
            self.playOrPauseBtn.enabled = YES;
            
            if (!self.playOrPauseBtn.selected)
            {
                [self clickPlayOrPauseBtn:self.playOrPauseBtn];
            }
        }
        self.playOrPauseBtn.tag = 0;
    }
    
    - (void)setCurrentPlayTime:(NSTimeInterval)currentPlayTime
    {
        _currentPlayTime = currentPlayTime;
        
        if (!self.isDraggingSlider)
        {
            self.currentTimeLabel.text = [self converTimeToStringWithTime:currentPlayTime];
            
            self.sliderView.centerX =  (currentPlayTime / _durationTime) * self.totalProgressView.width;
            
            _sliderConLeft.constant = self.totalProgressView.x + self.sliderView.centerX - self.sliderView.width / 2;
        }
    }
    
    - (void)setCurrentBufferTime:(NSTimeInterval)currentBufferTime
    {
        _currentBufferTime = currentBufferTime;
        
        self.bufferProgressView.width = self.totalProgressView.width * currentBufferTime / _durationTime;
        self.currentProgressConW.constant = self.totalProgressView.width * currentBufferTime / _durationTime;
    }
    
    - (void)setIsFinishedJump:(BOOL)isFinishedJump
    {
        _isFinishedJump = isFinishedJump;
        if (isFinishedJump)
        {
            self.isDraggingSlider = NO;
        }
        _isFinishedJump = NO;
    }
    
    
    - (IBAction)clickFinishBtn:(id)sender
    {
        [self.timer invalidate];
        
        if ([self.delegate respondsToSelector:@selector(stop)])
        {
            [self.delegate stop];
        }
        
        [self resetTimer];
    }
    
    - (IBAction)clickFillScreenBtn:(id)sender
    {
        [self.timer invalidate];
        self.isfullScreen = !self.isfullScreen;
        
        if ([self.delegate respondsToSelector:@selector(fullScreenOrNormalSizeWithFlag:)])
        {
            [self.delegate fullScreenOrNormalSizeWithFlag:self.isfullScreen];
        }
        
        [self resetTimer];
    }
    
    - (IBAction)clickPlayOrPauseBtn:(UIButton *)sender
    {
        [self.timer invalidate];
        [self resetTimer];
        
        if (!sender.tag && sender.selected)
        {
            self.isCertainPause = YES;
        }
        else
        {
            self.isCertainPause = NO;
        }
        
        sender.selected = !sender.selected;
        
        if (sender.selected)
        {
            if ([self.delegate respondsToSelector:@selector(play)])
            {
                [self.delegate play];
            }
        }
        else
        {
            if ([self.delegate respondsToSelector:@selector(pause)])
            {
                [self.delegate pause];
            }
        }
        
        
    }
    
    /**
     *  拖动滑块的时候
     *
     */
    - (void)draggingSlider:(UIPanGestureRecognizer *)recognizer
    {
        [self.timer invalidate];
        
        CGPoint point = [recognizer translationInView:self.bottomView];
        
        [recognizer setTranslation:CGPointZero inView:self.bottomView];
        
        CGFloat x = point.x;
        self.isDraggingSlider = YES;
        if (recognizer.state == UIGestureRecognizerStateEnded)
        {
            [self resetTimer];
            
            self.progressTimeBtn.hidden = YES;
            
            CGFloat jumpedTime = self.durationTime * (self.sliderView.centerX - self.totalProgressView.x) / self.totalProgressView.width;
            
            if ([self.delegate respondsToSelector:@selector(jumpedToTime:)])
            {
                [self.delegate jumpedToTime:jumpedTime];
            }
        }
        else
        {
            self.progressTimeBtn.hidden = NO;
            self.sliderView.centerX  += x;
            CGPoint point = [self.bottomView convertPoint:self.sliderView.center toView:self];
            
            self.progressTimeBtn.centerY = point.y - 40;
            self.progressTimeBtn.centerX = point.x;
            
            if (self.sliderView.centerX > self.totalProgressView.x + self.totalProgressView.width)
            {
                self.sliderView.centerX = self.totalProgressView.x + self.totalProgressView.width;
            }
            
            if (self.sliderView.centerX < self.totalProgressView.x)
            {
                self.sliderView.centerX = self.totalProgressView.x;
            }
            //取消一切动画效果(一般来说,用来禁止隐式动画)
            [UIView setAnimationsEnabled:NO];
            CGFloat jumpedTime = self.durationTime * (self.sliderView.centerX - self.totalProgressView.x) / self.totalProgressView.width;
            NSString *timeStr = [self converTimeToStringWithTime:jumpedTime];
            self.progressTimeBtn.titleLabel.text = timeStr;
            [self.progressTimeBtn setTitle:timeStr forState:UIControlStateNormal];
            [UIView setAnimationsEnabled:YES];
            
            self.progressTimeConLeft.constant = self.sliderView.centerX - self.progressTimeBtn.width / 2;
            _sliderConLeft.constant = self.sliderView.centerX - self.sliderView.width / 2;
            
        }
    }
    
    #pragma mark ----NSTimer相关操作
    
    - (void)resetTimer
    {
        [self.timer invalidate];
        self.timer = nil;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(updateTimer) userInfo:nil repeats:NO];
    }
    
    - (void)updateTimer
    {
        if (!self.timer.isValid || !self.timer || !self.isControlHidden) return;
        
        [self hideTopAndBottomView];
    }
    
    #pragma mark ----控制topottom 隐藏or显示相关逻辑
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        [self showTopAndBottomView];
    }
    
    - (void)showTopAndBottomView
    {
        [self.timer invalidate];
        
        if (!self.isShowing)
        {
            self.topView.hidden = NO;
            self.bottomView.hidden = NO;
            self.topViewConTop.constant = 0;
            self.bottomViewConBottom.constant = 0;
            [UIView animateWithDuration:0.3 animations:^{
                [self layoutIfNeeded];
            }completion:^(BOOL finished) {
                self.isShowing = YES;
                self.isControlHidden = YES;
            }];
        }
        [self resetTimer];
    }
    
    - (void)hideTopAndBottomView
    {
        [self.timer invalidate];
        
        if (self.isShowing)
        {
            self.topViewConTop.constant = -50;
            self.bottomViewConBottom.constant = -50;
            [UIView animateWithDuration:0.3 animations:^{
                [self layoutIfNeeded];
            } completion:^(BOOL finished) {
                self.topView.hidden = YES;
                self.bottomView.hidden = YES;
                self.isShowing = NO;
                self.isControlHidden = NO;
            }];
        }
        
    }
    
    #pragma mark ----other
    
    - (NSString *)converTimeToStringWithTime:(NSTimeInterval)time
    {
        int hour = time / 60 / 60;
        int minute = (time - hour * 60 * 60) / 60;
        int second = (int)time % 60;
        
        return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, second];
    }
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
    }
    
    @end
    

        代码github地址:https://github.com/wzpziyi1/VideoPlayer     

     

     

     

     

  • 相关阅读:
    第一节,Django+Xadmin打造上线标准的在线教育平台—创建用户app,在models.py文件生成3张表,用户表、验证码表、轮播图表
    Tensorflow 错误:Unknown command line flag 'f'
    Python 多线程总结
    Git 强制拉取覆盖本地所有文件
    Hive常用函数 傻瓜学习笔记 附完整示例
    Linux 删除指定大小(范围)的文件
    Python 操作 HBase —— Trift Trift2 Happybase 安装使用
    梯度消失 梯度爆炸 梯度偏置 梯度饱和 梯度死亡 文献收藏
    Embedding 文献收藏
    深度学习在CTR预估中的应用 文献收藏
  • 原文地址:https://www.cnblogs.com/ziyi--caolu/p/5673160.html
Copyright © 2011-2022 走看看