iOS:基於AVPlayer實現的視頻播放器

最近在學習AVFoundation框架的相關知識,寫了一個基於AVPlayer的視頻播放器,相關功能以下圖:git

 

 

代碼github:https://github.com/wzpziyi1/VideoPlayergithub

 

AVFoundation都是圍繞AVPlayer展開的,AVPlayer是一個用來播放基於時間的視聽媒體的控制器對象。但它與咱們一般理解的"控制器"不一樣,它不是一個視圖或者窗口控制器,而是一個對播放和資源時間相關信息進行管理的對象,具體的用戶界面是須要開發者本身進行編寫的。緩存

AVPlayer是一個不可見的組件,若是須要有可視化的界面,那麼須要使用AVPlayerLayer類。AVPlayerLayer構建於Core Animation之上,它擴展了Core Animation的CALayer類,並經過框架在屏幕上顯示視頻內容(它只是用做視頻內容的渲染面,其餘可視化界面須要本身編寫)。建立AVPlayerLayer須要一個指向AVPlayer實例的指針,這就能夠將圖層和播放器綁定在一塊兒了,保證了當播放器基於時間的方法出現時使兩者保持同步。數據結構

 

AVPlayerItemapp

  它會創建媒體資源動態視角的數據模型,並保存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屬性,能夠知道視頻緩存到哪樣一個時間點了:less

//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,纔開始播放。ide

  CMTimeoop

    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;

/**
 *  控制top\bottom View的隱藏定時器
 */
@property (nonatomic, strong) NSTimer *timer;

/**
 *  判斷是否須要控制top/bottom View隱藏
 */
@property (nonatomic, assign) BOOL isControlHidden;

/**
 *  top\bottom 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 ----控制top\bottom 隱藏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     

相關文章
相關標籤/搜索