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     

     

     

     

     

  • 相关阅读:
    第一次冲刺04
    第一次冲刺03
    第一次冲刺02
    团队站立会议3(第二阶段)
    团队站立会议2(第二阶段)
    团队站立会议1(第二阶段)
    Alpha版总结会议
    “来用”alpha版使用说明书
    团队绩效评估计划
    第一阶段其他团队对我们的意见汇总
  • 原文地址:https://www.cnblogs.com/ziyi--caolu/p/5673160.html
Copyright © 2011-2022 走看看