最近在學習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