JJPlayerView
AVPlayer依賴于AVFoundation,另外AVPlayer不包含UI,想要完成播放功能绍载,需要自己寫UI诡宗。
項目中往往會用到視頻播放功能,GitHub上面也有很多star很高的庫也能滿足需求击儡,但是當(dāng)我們需要高度自定義UI的時候塔沃,他們卻不能滿足要求。
所以對于要求較高的播放功能曙痘,我們需要自己封裝一個播放器芳悲。
今天我們就來實現(xiàn)一個簡單的播放器,目的是了解播放器的使用边坤。
iOS開發(fā)中會有音視頻播放的需求名扛。目前常用的音視頻播放器有 AVPlayer,AVPlayer支持本地播放茧痒,分步下載肮韧,在線流媒體播放。
?? ?? ?? ?? ?? ?? ??
AVPlayer基礎(chǔ)知識簡介
- AVPlayer是用于管理媒體資源播放的控制器旺订,它提供了播放器的諸多功能:播放弄企、暫停、倍速等区拳。AVPlayer一次只能播放一個資源文件(本地或者遠(yuǎn)程)拘领。
AVQueuePlayer可以播放一個列表,是AVPlayer的子類樱调。
然后我們根據(jù)視頻資源约素,創(chuàng)建我們需要的AVPlayer。系統(tǒng)為我們也提供了幾個初始化方法笆凌。
+ (instancetype)playerWithURL:(NSURL *)URL;
+ (instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item;
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithPlayerItem:(nullable AVPlayerItem *)item;
- AVPlayerItem代表了一個播放資源圣猎,每播放一個視頻需要一個數(shù)據(jù)資源,我們需要初始化一個AVPlayerItem乞而。
系統(tǒng)提供了幾個初始化AVPlayerItem
的方法送悔。
/// - AVPlayerItem的初始化方法
+ (instancetype)playerItemWithURL:(NSURL *)URL;
+ (instancetype)playerItemWithAsset:(AVAsset *)asset;
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithAsset:(AVAsset *)asset;
- 單純使用AVPlayer是無法顯示視頻的,需要將視頻添加至AVPlayerLayer上爪模,然后添加到我們自己創(chuàng)建的視圖layer上面欠啤。
封裝JJPlayerView
封裝播放器可以分為兩個部分:
- 第一是播放視頻的功能
- 第二個是UI交互。
兩個部分不是完全分開的屋灌,UI就是調(diào)用這個方法洁段,達(dá)到播放視頻的功能。
- 創(chuàng)建一個JJPalyerView声滥,作為承載視圖。
- 創(chuàng)建必要的屬性
AVPlayer
,AVPlayerItem
,AVPlayerLayer
等
/// 播放器
@property (nonatomic, strong) AVPlayer *player;
/// 播放器item
@property (nonatomic, strong) AVPlayerItem *playerItem;
/// 播放器layer
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
/// 控件的原始frame
@property (nonatomic, assign) CGRect customFrame;
/// 父類控件
@property (nonatomic, strong) UIView *fatherView;
/// 視圖拉伸模式
@property (nonatomic, copy) NSString *fillMode;
/// 是否處于全屏狀態(tài)
@property (nonatomic, assign) BOOL isFullScreen;
/// 工具條是否隱藏
@property (nonatomic, assign) BOOL isDisappear;
/// 用戶播放標(biāo)記
@property (nonatomic, assign) BOOL isUserPlay;
/// 點擊最大化標(biāo)記
@property (nonatomic, assign) BOOL isUserTapMaxButton;
/// 是否播放完畢
@property (nonatomic, assign) BOOL isFinish;
/// 是否在調(diào)節(jié)音量:YES為音量,NO為屏幕亮度
@property (nonatomic, assign) BOOL isVolume;
/// 是否在拖拽
@property (nonatomic, assign) BOOL isDragged;
/// 緩沖
@property (nonatomic, assign) BOOL isBuffering;
/// 用來保存快進(jìn)的總時長
@property (nonatomic, assign) CGFloat sumTime;
/// 播放器配置信息
@property (nonatomic, strong) JJPlayerConfigure *playerConfigure;
/// 視頻播放控制面板(遮罩)
@property (nonatomic, strong) JJPlayerMaskView *playerMaskView;
/// 滑動方向
@property (nonatomic, assign) JJPanDirection panDirection;
/// 音量滑桿
@property (nonatomic, strong) UISlider *volumeViewSlider;
/// 點擊屏幕定時器
@property (nonatomic, strong) NSTimer *tapTimer;
/// 播放器的播放狀態(tài)
@property (nonatomic, assign) JJPlayerState playerState;
/// 記錄點擊屏幕定時器的時間
@property (nonatomic, assign) NSInteger tapTimeCount;
/// 是否已經(jīng)移除了KVO
@property (nonatomic, assign) BOOL isRemoveObserver;
- 創(chuàng)建播放完成和返回按鈕回調(diào)事件
/// 返回按鈕回調(diào)
@property (nonatomic, copy) void(^BackBlock) (UIButton *backButton);
/// 播放完成回調(diào)
@property (nonatomic, copy) void(^EndBlock)(void);
- 添加通知,監(jiān)聽播放器的各種狀態(tài)
//開啟
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
/// 監(jiān)聽橫豎屏的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
/// 進(jìn)入后臺
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil];
/// 進(jìn)入前臺
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil];
// 添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// 添加打斷播放的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionComing:) name:AVAudioSessionInterruptionNotification object:nil];
// 添加插拔耳機(jī)的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
- 將AVPlayerLayer添加到視圖上
定義視頻在AVPlayerLayer中的顯示方式落塑,默認(rèn)方式AVLayerVideoGravityResizeAspect
;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.frame = frame;
playerLayer.backgroundColor = [UIColor blackColor].CGColor;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[view.layer addSublayer: playerLayer];
我們看到了纽疟,需要設(shè)置AVPlayerLayer
的frame
,還需要通過videoGravity
設(shè)置資源填充展示的樣式.
但是這里有更好的寫法,創(chuàng)建一個PlayerLayerView憾赁,將這個PlayerLayerView的layer返回class污朽,設(shè)置為AVPlayerLayer類型,這樣就不用設(shè)置layer的frame了龙考,直接設(shè)置view的frame就可以蟆肆,更加直觀方便。
@interface JJPlayerLayer : UIView
@end
@implementation JJPlayerLayer
+ (Class)layerClass {
return AVPlayerLayer.class;
}
@end
/// 播放器layer
@property (nonatomic, strong) JJPlayerLayer *playerLayer;
- AVPlayer提供了音視頻的播放和暫停功能晦款,與之對應(yīng)的方法:
play
炎功、pause
。
/// 開始播放
- (void)play{
// 這里不只是播放缓溅,點擊播放的時候還要設(shè)置相關(guān)UI蛇损,判斷當(dāng)前狀態(tài)是否可以播放。并不是直接播放就可以坛怪。
// 判斷當(dāng)前是否播放完成淤齐,播放完成后要從頭開始播放
self.playerMaskView.playButton.selected = YES;
// [self.layer insertSublayer:self.playerLayer atIndex:0];
if (self.isFinish && self.playerMaskView.slider.value == 1) {
_isFinish = NO;
[_player seekToTime:CMTimeMake(0, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
} else {
[self.player play];
}
}
/// 暫停
- (void)pause{
/// 設(shè)置暫停相關(guān)UI樣式。
[self.player pause];
}
- 播放器一次只能給你管理一個播放資源袜匿,但我們需要切換資源的時候更啄,可以調(diào)用相關(guān)
replaceCurrentItemWithPlayerItem
方法。AVFoundation框架還提供了一個專門的類:AVQueuePlayer
,用來管理資源列表居灯,我們暫時不討論祭务,后面再延伸.
- (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
但是我們?yōu)榱烁奖闶褂茫?dāng)切換資源的時候穆壕,重置播放器待牵。也就是銷毀原來的資源,重新創(chuàng)建喇勋。
#pragma mark - 重置播放器
- (void)resetPlayer{
//重置狀態(tài)
self.playerState = JJPlayerStatePause;
_isUserPlay = YES;//用戶點擊標(biāo)志
_isDisappear = NO;//工具條隱藏標(biāo)記
//移除之前的
[self pause];//先暫停
[self.playerLayer removeFromSuperlayer];
self.playerLayer = nil;
self.player = nil;
//還原進(jìn)度條和緩沖條
self.playerMaskView.slider.value = 0;
self.playerMaskView.progressView.progress = 0;
//重置時間
self.playerMaskView.currentTimeLabel.text = @"00:00";
self.playerMaskView.totalTimeLabel.text = @"00:00";
[self destoryToolBarTimer];
//重置Toolbar
[UIView animateWithDuration:0.25 animations:^{
self.playerMaskView.topToolBar.alpha = 1.0;
self.playerMaskView.bottomToolBar.alpha = 1.0;
}];
//重新添加工具條消失定時器
[self resetTooBarDisappearTimer];
self.playerMaskView.failButton.hidden = YES;
//開始轉(zhuǎn)子動化
[self.playerMaskView.loadingView startAnimation];
}
然后再重新添加新的資源
self.playerItem = [AVPlayerItem playerItemWithURL:_url];
//創(chuàng)建播放器
_player = [AVPlayer playerWithPlayerItem:_playerItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
_playerLayer.videoGravity = _fillMode;
[self.layer insertSublayer:_playerLayer atIndex:0];
- 監(jiān)聽資源的狀態(tài)
我們可以使用KVO監(jiān)聽playerItem的幾個屬性status
缨该,loadedTimeRanges
,playbackBufferEmpty
川背。
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
只有當(dāng)status發(fā)生改變贰拿,并且值為AVPlayerItemStatusReadyToPlay的時候,說明視頻資源準(zhǔn)備完成熄云,才可以播放膨更。
結(jié)果是AVPlayerItemStatusFailed的時候,說明解析失敗缴允,需要展示相關(guān)UI和文案荚守。loadedTimeRanges可以計算緩沖進(jìn)度
playbackBufferEmpty說明當(dāng)前緩沖為空珍德,需要做暫停處理,加載一會...........
- (void)setPlayerItem:(AVPlayerItem *)playerItem{
if (_playerItem == playerItem) {
return;
}
if (_playerItem) {
if (!self.isRemoveObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
[_playerItem removeObserver:self forKeyPath:@"status"];
[_playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[_playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
}
self.isRemoveObserver = YES;
//重置播放器
[self resetPlayer];
}
_playerItem = playerItem;
if (playerItem) {
self.isRemoveObserver = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
}
}
#pragma mark - kvo監(jiān)聽
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"status"]) {
if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {//加載完成,可以播放
//加載完成后,再添加平移手勢
//添加平移手勢,用來控制音量/亮度/快進(jìn)快退
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureDirection:)];
pan.delegate = self;
pan.maximumNumberOfTouches = 1; //一根手指
pan.delaysTouchesBegan = YES;
pan.delaysTouchesEnded = YES;
pan.cancelsTouchesInView = YES;
[self.playerMaskView addGestureRecognizer:pan];
self.player.muted = self.playerConfigure.isMute;
[self addPeriodicTimeObserver];
if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
self.playerState = JJPlayerStatePlaying;
}else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
self.playerState = JJPlayerStateFailed;
}
}else if (self.player.currentItem.status == AVPlayerItemStatusFailed){
// 解析失敗(播放失敗)
self.playerState = JJPlayerStateFailed;
}
}else if ([keyPath isEqualToString:@"loadedTimeRanges"]){
//計算緩沖進(jìn)度
NSTimeInterval timeInterval = [self calculateBufferProgress];
CMTime duration = self.playerItem.duration;
CGFloat totalDuratuon = CMTimeGetSeconds(duration);
[self.playerMaskView.progressView setProgress:(timeInterval / totalDuratuon) animated:NO];
}else if ([keyPath isEqualToString:@"playbackBufferEmpty"]){
//當(dāng)前緩沖時空的時候
if (self.playerItem.isPlaybackBufferEmpty) {
self.playerState = JJPlayerStateBuffering;
[self bufferingSomeSecond];//卡頓一會,緩沖幾秒
}
}
}
我們可以看到添加KVO之前矗漾。先移除锈候,是因為我們可能會替換資源,如果不移除,監(jiān)聽到的就是之前的資源信息。
- 添加通知監(jiān)聽其他狀態(tài)真屯,實現(xiàn)UI交互功能
其實這個時候其實就一個可以將視頻播放出來了,但是我們?yōu)榱烁玫伢w驗获列,需要獲取到播放器的狀態(tài):播放、加載蛔垢、播放完成等等击孩。
這里我們可以通過通知
來獲取當(dāng)前的具體狀態(tài)。
通過通知AVPlayerItemDidPlayToEndTimeNotification
可以接收到視頻播放完成通知.
通過通知AVAudioSessionInterruptionNotification
可以接收到打斷播放的通知.
通過通知AVAudioSessionRouteChangeNotification
可以接收到插拔耳機(jī)的通知.
// 添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// 添加打斷播放的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionComing:) name:AVAudioSessionInterruptionNotification object:nil];
// 添加插拔耳機(jī)的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
- AVPlayer 中播放進(jìn)度的獲取如下啦桌,通過回傳的time參數(shù)溯壶,可以更新進(jìn)度.
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
當(dāng)拿到回傳的時間,我們需要更新進(jìn)度指示器甫男。
@weakify(self);
[_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 10) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
@strongify(self);
[self sliderTimerAction];
}];
- (void)sliderTimerAction{
if (self.playerItem.duration.timescale != 0) {
self.playerMaskView.slider.maximumValue = 1;
CGFloat total = _playerItem.duration.value / _playerItem.duration.timescale;
self.playerMaskView.slider.value = CMTimeGetSeconds(self.playerItem.currentTime) / total;
//判斷是否正在播放
if (self.playerItem.isPlaybackLikelyToKeepUp && self.playerMaskView.slider.value > 0) {
self.playerState = JJPlayerStatePlaying;
}
//當(dāng)前時長
NSInteger proMin = (NSInteger)CMTimeGetSeconds([_player currentTime]) / 60;//當(dāng)前分鐘
NSInteger proSec = (NSInteger)CMTimeGetSeconds([_player currentTime]) % 60;//當(dāng)前秒
self.playerMaskView.currentTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)proMin, (long)proSec];
//總時長
NSInteger durMin = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale / 60;//總分鐘
NSInteger durSec = (NSInteger)_playerItem.duration.value / _playerItem.duration.timescale % 60;//總秒
self.playerMaskView.totalTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)durMin, (long)durSec];
}
}
- 有的時候用戶可以快進(jìn)且改,或者手動拖動到指定時間點播放資源:
- (void)seekToTime:(CMTime)time;
當(dāng)我們滑動播放器進(jìn)度的時候,或者活動屏幕的時候板驳,可以調(diào)用次方法又跛,改變播放進(jìn)度。
- 其他通知的監(jiān)聽
電話打斷播放若治,home鍵回到主頁面慨蓝,屏幕翻轉(zhuǎn)等等:
/// 添加打斷播放的通知
[[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(interruptionComing:) name: AVAudioSessionInterruptionNotification object:nil];
/// 監(jiān)聽橫豎屏的通知
[[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(orientationChanged:) name: UIDeviceOrientationDidChangeNotification object:nil];
/// 進(jìn)入后臺
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil]
/// 進(jìn)入前臺
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil];
- 創(chuàng)建一個UISlider,作為食品播放器的滑竿端幼。
系統(tǒng)的滑竿是非常好用的礼烈,但是UI不滿足要求,我們繼承系統(tǒng)UISlider婆跑,稍作修改:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JJSlider : UISlider
@end
NS_ASSUME_NONNULL_END
@implementation JJSlider
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setupUI];
}
return self;
}
- (void)setupUI {
UIImage *thumbImage = [UIImage imageWithName:@"JJRoundButton"];
[self setThumbImage:thumbImage forState:UIControlStateHighlighted];
[self setThumbImage:thumbImage forState:UIControlStateNormal];
}
@end
修改滑竿的按鈕圖片即可此熬。
當(dāng)拖動滑竿的時候改變視頻播放的進(jìn)度。
- 當(dāng)可以播放的時候滑进,通過參數(shù)
rate
可以獲取到當(dāng)前是在加載中還是播放中犀忱。也可以通過它設(shè)置播放速度。 - 我們需要創(chuàng)建一個遮罩層扶关,添加一些基本的功能阴汇,比如,胡奧明屏幕實現(xiàn)快進(jìn)节槐,快退功能搀庶,改變音量拐纱,改變屏幕亮度,還要添加一些標(biāo)題哥倔,播放按鈕等功能戳玫。
我們創(chuàng)建一個JJPlayerMaskView作為遮罩層。
遮罩層里面包含的屬性:標(biāo)題未斑,返回按鈕,播放按鈕币绩,暫停按鈕蜡秽,進(jìn)度條,緩沖條缆镣,播放失敗提示芽突,全屏按鈕等等。
頁面上需要添加手勢董瞻,點擊隱藏/顯示 這些控件寞蚌。
創(chuàng)建一個協(xié)議,將這些事件傳遞出去钠糊。
@protocol JJPlayerMaskViewDelegate <NSObject>
/// 返回按鈕點擊事件代理
- (void)jj_playerMaskViewBackButtonAction:(UIButton *_Nonnull)button;
/// 播放按鈕點擊事件代理
- (void)jj_playerMaskViewPlayButtonAction:(UIButton *_Nonnull)button;
/// 全屏按鈕點擊事件代理
- (void)jj_playerMaskViewFullButtonAction:(UIButton *_Nonnull)button;
/// 開始滑動
- (void)jj_playerMaskViewProgressSliderTouchBegan:(JJSlider *_Nullable)slider;
/// 滑動中
- (void)jj_playerMaskViewProgressSliderTouchChanged:(JJSlider *_Nullable)slider;
/// 滑動結(jié)束
- (void)jj_playerMaskViewProgressSliderTouchEnd:(JJSlider *_Nullable)slider;
/// 播放失敗按鈕事件
- (void)jj_playerMaskViewFailButtonAction:(UIButton *_Nonnull)button;
/// 代理事件
@property (nonatomic, weak) id<JJPlayerMaskViewDelegate > delegate;
/// 頂部工具條
@property (nonatomic, strong) UIView *topToolBar;
/// 標(biāo)題
@property (nonatomic, strong) UILabel *titleLabel;
/// 底部工具條
@property (nonatomic, strong) UIView *bottomToolBar;
/// 加載動畫
@property (nonatomic, strong, nullable) JJAnimationView *loadingView;
/// 頂部工具條返回按鈕(會和工具條一起消失顯示)
@property (nonatomic, strong) UIButton *backButton;
/// 頂部返回按鈕,一直顯示
@property (nonatomic, strong) UIButton *backOnlyButton;
@property (nonatomic, strong) UIButton *lockButton;
/// 底部工具條播放/暫停按鈕
@property (nonatomic, strong) UIButton *playButton;
/// 底部工具條全屏按鈕
@property (nonatomic, strong) UIButton *fullButton;
/// 底部工具條當(dāng)前時間
@property (nonatomic, strong) UILabel *currentTimeLabel;
/// 底部工具條總時間
@property (nonatomic, strong) UILabel *totalTimeLabel;
/// 緩沖進(jìn)度條
@property (nonatomic, strong) UIProgressView *progressView;
/// 播放進(jìn)度條
@property (nonatomic, strong) JJSlider *slider;
/// 加載失敗按鈕
@property (nonatomic, strong) UIButton *failButton;
- 判斷滑動方向挟秤,并記錄
水平滑動的時候
if (self.panDirection == JJPanDirectionHorizontalMoved) {
[self panHorizontalMoved:thePoint.x];
}
水平滑動的時候改變slider的UI,并調(diào)節(jié)進(jìn)度
#pragma mark - 水平滑動,調(diào)節(jié)進(jìn)度
- (void)panHorizontalMoved:(CGFloat)value{
//水平滑動進(jìn)度,邏輯多,多做一些判斷
if (value == 0) {
return;
}
//每次滑動時間需要疊加.
self.sumTime += value/200.0;
//需要給sumTime限制范圍
CMTime totalTime = self.playerItem.duration;
CGFloat totalMovieDuration = (CGFloat)(totalTime.value/totalTime.timescale);
if (self.sumTime > totalMovieDuration) {
self.sumTime = totalMovieDuration;
}
if (self.sumTime < 0) {
self.sumTime = 0;
}
self.isDragged = YES;
//計算出拖動的當(dāng)前秒數(shù)
CGFloat dragedSeconds = self.sumTime;
//滑桿進(jìn)度
CGFloat sliderValue = dragedSeconds/totalMovieDuration;
//設(shè)置滑桿
self.playerMaskView.slider.value = sliderValue;
//轉(zhuǎn)換成CMTime才能player來控制進(jìn)度
CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1);
[self.player seekToTime:dragedCMTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
NSInteger proMin = (NSInteger)CMTimeGetSeconds(dragedCMTime) / 60;//當(dāng)前秒
NSInteger proSec = (NSInteger)CMTimeGetSeconds(dragedCMTime) % 60;//當(dāng)前分鐘
self.playerMaskView.currentTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld",proSec,proMin];
}
- 創(chuàng)建音量滑竿
/// 音量滑桿
@property (nonatomic, strong) UISlider *volumeViewSlider;
當(dāng)垂直滑動的時候抄伍,以中心線為標(biāo)準(zhǔn)艘刚,判斷滑動位置,一側(cè)改變音量截珍,另一側(cè)改變屏幕亮度
if (self.panDirection == JJPanDirectionVerticalMoved){
[self panVerticalMoved:thePoint.y];
}
#pragma mark - 垂直滑動,調(diào)節(jié)音量和亮度
- (void)panVerticalMoved:(CGFloat)value{
if (self.isVolume) { //音量
self.volumeViewSlider.value -= value/10000.0;
}else{ //亮度
[UIScreen mainScreen].brightness -= value/10000.0;
}
}
- 全屏處理攀甚。
視頻播放一般有全屏播放的功能。
我們做法是岗喉,全屏的時候?qū)⒉シ牌魈砑又羕eyWindow上面秋度,并記錄原來父視圖,返回時钱床,添加到圓的父視圖上面荚斯。
點擊全屏的時候
- (UIWindow *)getKeyWindow{
if (@available(iOS 13.0, *)){
return [UIApplication sharedApplication].windows.lastObject;
}else{
return [UIApplication sharedApplication].keyWindow;
}
}
//添加到keyWindow上
UIWindow *keyWindow = [self getKeyWindow];
[keyWindow addSubview:self];
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationLandscapeRight] forKey:@"orientation"];
只設(shè)置翻轉(zhuǎn)一個方向即可!
還原的時候:
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationPortrait] forKey:@"orientation"];
self.frame = _customFrame;
// 還原到原有父類上
[_fatherView addSubview:self];
self.playerMaskView.fullButton.selected = NO;
- 其他細(xì)節(jié)處理
鎖屏功能诞丽,點擊后頁面不再處理事件鲸拥。
這個功能簡單,記錄鎖屏按鈕狀態(tài)僧免,處理各事件前刑赶,判斷鎖屏狀態(tài)《茫或者鎖屏的時候撞叨,將控件隱藏起來金踪。
彈幕功能,在遮罩上面的一部分區(qū)域牵敷,添加彈幕即可胡岔。