iOS-AVPlayer視頻播放封裝

JJPlayerView

AVPlayer依賴于AVFoundation,另外AVPlayer不包含UI,想要完成播放功能绍载,需要自己寫UI诡宗。

項目中往往會用到視頻播放功能,GitHub上面也有很多star很高的庫也能滿足需求击儡,但是當(dāng)我們需要高度自定義UI的時候塔沃,他們卻不能滿足要求。

所以對于要求較高的播放功能曙痘,我們需要自己封裝一個播放器芳悲。
今天我們就來實現(xiàn)一個簡單的播放器,目的是了解播放器的使用边坤。

iOS開發(fā)中會有音視頻播放的需求名扛。目前常用的音視頻播放器有 AVPlayer,AVPlayer支持本地播放茧痒,分步下載肮韧,在線流媒體播放。

?? ?? ?? ?? ?? ?? ??

AVPlayer基礎(chǔ)知識簡介

  1. 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;
  1. AVPlayerItem代表了一個播放資源圣猎,每播放一個視頻需要一個數(shù)據(jù)資源,我們需要初始化一個AVPlayerItem乞而。
    系統(tǒng)提供了幾個初始化AVPlayerItem的方法送悔。
/// - AVPlayerItem的初始化方法
+ (instancetype)playerItemWithURL:(NSURL *)URL;
+ (instancetype)playerItemWithAsset:(AVAsset *)asset;
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithAsset:(AVAsset *)asset;
  1. 單純使用AVPlayer是無法顯示視頻的,需要將視頻添加至AVPlayerLayer上爪模,然后添加到我們自己創(chuàng)建的視圖layer上面欠啤。

封裝JJPlayerView

封裝播放器可以分為兩個部分:

  • 第一是播放視頻的功能
  • 第二個是UI交互。
    兩個部分不是完全分開的屋灌,UI就是調(diào)用這個方法洁段,達(dá)到播放視頻的功能。
  1. 創(chuàng)建一個JJPalyerView声滥,作為承載視圖。
  2. 創(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;
  1. 創(chuàng)建播放完成和返回按鈕回調(diào)事件
/// 返回按鈕回調(diào)
@property (nonatomic, copy) void(^BackBlock) (UIButton *backButton);
/// 播放完成回調(diào)
@property (nonatomic, copy) void(^EndBlock)(void);
  1. 添加通知,監(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];
  1. 將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è)置AVPlayerLayerframe,還需要通過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;
  1. 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];
}
  1. 播放器一次只能給你管理一個播放資源袜匿,但我們需要切換資源的時候更啄,可以調(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];
  1. 監(jiān)聽資源的狀態(tài)
    我們可以使用KVO監(jiān)聽playerItem的幾個屬性status缨该,loadedTimeRangesplaybackBufferEmpty川背。
[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)聽到的就是之前的資源信息。

  1. 添加通知監(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];
  1. 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];
    }
}
  1. 有的時候用戶可以快進(jìn)且改,或者手動拖動到指定時間點播放資源:
- (void)seekToTime:(CMTime)time;

當(dāng)我們滑動播放器進(jìn)度的時候,或者活動屏幕的時候板驳,可以調(diào)用次方法又跛,改變播放進(jìn)度。

  1. 其他通知的監(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];
  1. 創(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)度。

  1. 當(dāng)可以播放的時候滑进,通過參數(shù)rate可以獲取到當(dāng)前是在加載中還是播放中犀忱。也可以通過它設(shè)置播放速度。
  2. 我們需要創(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;
  1. 判斷滑動方向挟秤,并記錄
    水平滑動的時候
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];  
}
  1. 創(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;
    }
}
  1. 全屏處理攀甚。
    視頻播放一般有全屏播放的功能。
    我們做法是岗喉,全屏的時候?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;
  1. 其他細(xì)節(jié)處理
    鎖屏功能诞丽,點擊后頁面不再處理事件鲸拥。
    這個功能簡單,記錄鎖屏按鈕狀態(tài)僧免,處理各事件前刑赶,判斷鎖屏狀態(tài)《茫或者鎖屏的時候撞叨,將控件隱藏起來金踪。

彈幕功能,在遮罩上面的一部分區(qū)域牵敷,添加彈幕即可胡岔。

demo地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市枷餐,隨后出現(xiàn)的幾起案子靶瘸,更是在濱河造成了極大的恐慌,老刑警劉巖毛肋,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怨咪,死亡現(xiàn)場離奇詭異,居然都是意外死亡润匙,警方通過查閱死者的電腦和手機(jī)诗眨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孕讳,“玉大人匠楚,你說我怎么就攤上這事〕Р疲” “怎么了芋簿?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長璃饱。 經(jīng)常有香客問我益咬,道長,這世上最難降的妖魔是什么帜平? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任幽告,我火速辦了婚禮,結(jié)果婚禮上裆甩,老公的妹妹穿的比我還像新娘冗锁。我一直安慰自己,他們只是感情好嗤栓,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布冻河。 她就那樣靜靜地躺著,像睡著了一般茉帅。 火紅的嫁衣襯著肌膚如雪叨叙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天堪澎,我揣著相機(jī)與錄音擂错,去河邊找鬼。 笑死樱蛤,一個胖子當(dāng)著我的面吹牛钮呀,可吹牛的內(nèi)容都是我干的剑鞍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爽醋,長吁一口氣:“原來是場噩夢啊……” “哼蚁署!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚂四,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤光戈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遂赠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體田度,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年解愤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乎莉。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡送讲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惋啃,到底是詐尸還是另有隱情哼鬓,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布边灭,位于F島的核電站异希,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绒瘦。R本人自食惡果不足惜称簿,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惰帽。 院中可真熱鬧憨降,春花似錦、人聲如沸该酗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呜魄。三九已至悔叽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爵嗅,已是汗流浹背娇澎。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留睹晒,地道東北人九火。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓赚窃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岔激。 傳聞我的和親對象是個殘疾皇子勒极,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容