iOS 分享一個功能很全的視頻播放器

SJVideoPlayer

基於AVPlayer.git

極速初始化, 不阻塞主線程.github

這個應該是目前基於AVPlayer的播放器中, 功能最全的一個吧.
_緩存

使用

pod 'SJVideoPlayer'

_ruby

功能

  1. 支持 全屏手勢返回
  2. 支持 網絡狀態變動提示
  3. 支持 爲所欲爲的控制旋轉
  4. 支持 導出片斷 + 截屏 + 生成GIF
  5. 支持 自定義控制層(實現協議方法便可)
  6. 支持 顯示提示文本(如: 現有的網路狀態切換提示)
  7. 支持 調整亮度/聲音/調速/靜音/捏合, 播放 本地/網絡視頻
  8. 支持 在TableView的HeaderView或者Cell上播放, 在CollectionView的Cell中播放
  9. 💖 續播, 跳入下一個界面能夠使用上一個界面的資源初始化
  10. 本地化處理. 支持簡體/繁體/英文
  11. 能夠 在後臺播放視頻
  12. 💖 無縫切換您自定義的控制層

_網絡

全屏手勢返回

只需pod到項目中便可帶全屏手勢session

爲啥自定義手勢?

在使用原生手勢返回時, 當前播放的視頻會出現卡幀的問題. 緣由我不太清楚. 我查看了騰訊視頻和愛奇藝等App均爲本身實現的手勢返回. app

當時使用了一個全屏返回的三方庫. 是截屏返回的, 發現存在兩個問題: 一是截屏耗時, 二是視頻部分截取不了(黑色的)
因爲咱們起步8.0, 我便使用了新的APIsnapshotViewAfterScreenUpdates:(這個接口其實7.0就有了)來替換截屏的方式, 直接使用視圖快照, 這個接口一箭雙鵰, 不只速度快, 並且視頻播放也能夠截取到.
可是快照是一個view, 並非image, 原來的三方庫截屏方式沒法使用, 因而我從新擼了一個全屏手勢.ide

手勢使用

pod 'SJFullscreenPopGesture'

手勢功能介紹

  1. 可設置手勢類型: 全屏手勢 || 邊緣手勢.
// default is `SJFullscreenPopGestureType_EdgeLeft`.
typedef NS_ENUM(NSUInteger, SJFullscreenPopGestureType) {
    SJFullscreenPopGestureType_EdgeLeft,    // 默認, 屏幕左邊緣觸發手勢
    SJFullscreenPopGestureType_Full,        // 全屏觸發手勢
};
  1. 可設置Pop返回時的動畫效果

目前有兩種:post

- [相似騰訊視頻返回.gif](https://upload-images.jianshu.io/upload_images/2318691-d5a992c40cfee5bb.gif?imageMogr2/auto-orient/strip)
- [在1的基礎上加上了一層遮罩.gif](https://upload-images.jianshu.io/upload_images/2318691-3dcd02f47b0dff4a.gif?imageMogr2/auto-orient/strip)
  1. 可在某個ViewController禁用手勢
  2. 可兼容 WKWebView 手勢返回
  3. 可設置盲區, 在這個區域不觸發手勢
  4. 可設置手勢觸發過程當中的回調
/// 將要拖拽
@property (nonatomic, copy, readwrite, nullable) void(^sj_viewWillBeginDragging)(__kindof UIViewController *vc);
/// 拖拽中
@property (nonatomic, copy, readwrite, nullable) void(^sj_viewDidDrag)(__kindof UIViewController *vc);
/// 結束拖拽
@property (nonatomic, copy, readwrite, nullable) void(^sj_viewDidEndDragging)(__kindof UIViewController *vc);
  1. 可設置返回界面的顯示模式, 目前有兩種: 1. 使用快照(也可稱截屏) 2. 使用原始視圖(默認)

等 ...
_字體

網絡狀態切換提示

網絡狀態提示就是當網絡狀態變動時, 播放器顯示一行字或者圖片等, 提示用戶網絡變動了.

對於默認提示的內容(可自定義), 我作了本地化處理, 根據 localLanguage 自動選擇一種語言提示(中文/繁體/英文), 以下:

網絡狀態提示-中文提示.gif

網絡狀態提示2-英文提示.gif

爲所欲爲的控制旋轉

關於旋轉我想吐槽一下: 大部分三方庫的旋轉是直接把播放器添加到window的中心, 再作一個transform動畫.. .
旋轉過程當中, 界面會閃一下, 這個體驗幾回眼就會累... 爲了解決這個問題, 我又寫了一個旋轉的庫在添加以前, 我作了相應的座標轉換, 使播放器添加到window上時, 還處於原始位置. 而後去作transform動畫並更新bounds和center.

播放器除了自動旋轉以外, 您能夠隨意的控制旋轉. 支持的方向以下:

/// 豎屏 || (左右)橫屏
/// Auto rotate supported orientation
typedef NS_ENUM(NSUInteger, SJAutoRotateSupportedOrientation) {
    SJAutoRotateSupportedOrientation_All,
    SJAutoRotateSupportedOrientation_Portrait = 1 << 0,
    SJAutoRotateSupportedOrientation_LandscapeLeft = 1 << 1,  // UIDeviceOrientationLandscapeLeft
    SJAutoRotateSupportedOrientation_LandscapeRight = 1 << 2, // UIDeviceOrientationLandscapeRight
};

關於旋轉的一些方法:

/**
 Autorotation. Animated.
 */
- (void)rotate;

/**
 Rotate to the specified orientation.
 
 @param orientation     Any value of SJOrientation.
 @param animated        Whether or not animation.
 */
- (void)rotate:(SJOrientation)orientation animated:(BOOL)animated;

/**
 Rotate to the specified orientation.
 
 @param orientation     Any value of SJOrientation.
 @param animated        Whether or not animation.
 @param block           The block invoked when player rotated.
 */
- (void)rotate:(SJOrientation)orientation animated:(BOOL)animated completion:(void (^ _Nullable)(__kindof SJBaseVideoPlayer *player))block;

旋轉的一些設置

/**
 The block invoked When player will rotate.
 
 readwrite.
 */
@property (nonatomic, copy, nullable) void(^willRotateScreen)(__kindof SJBaseVideoPlayer *player, BOOL isFullScreen);

/**
 The block invoked when player rotated.
 
 readwrite.
 */
@property (nonatomic, copy, nullable) void(^rotatedScreen)(__kindof SJBaseVideoPlayer *player, BOOL isFullScreen);

導出片斷 + 截屏 + 生成GIF

這幾個功能都已封裝在默認的控制層中, 而且相應的提示文本也作了本地化處理導出.gif
方法以下:

- (UIImage * __nullable)screenshot;

- (void)screenshotWithTime:(NSTimeInterval)time
                completion:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, UIImage * __nullable image, NSError *__nullable error))block;

- (void)screenshotWithTime:(NSTimeInterval)time
                      size:(CGSize)size
                completion:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, UIImage * __nullable image, NSError *__nullable error))block;

/**
 export session.
 
 @param beginTime           unit is sec.
 @param endTime             unit is sec.
 @param presetName             default is `AVAssetExportPresetMediumQuality`.
 @param progressBlock       progressBlock
 @param completion             completion
 @param failure             failure
 */
- (void)exportWithBeginTime:(NSTimeInterval)beginTime
                    endTime:(NSTimeInterval)endTime
                 presetName:(nullable NSString *)presetName
                   progress:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, float progress))progressBlock
                 completion:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, NSURL *fileURL, UIImage *thumbnailImage))completion
                    failure:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, NSError *error))failure;

- (void)cancelExportOperation;

- (void)generateGIFWithBeginTime:(NSTimeInterval)beginTime
                        duration:(NSTimeInterval)duration
                        progress:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, float progress))progressBlock
                      completion:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, UIImage *imageGIF, UIImage *thumbnailImage, NSURL *filePath))completion
                         failure:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, NSError *error))failure;

- (void)cancelGenerateGIFOperation;

_

顯示提示文本

目前提示文本支持 NSString(可自定義字體) 以及 NSAttributedString. 正如前面看到的網路狀態提示, 就是使用的這些方法, 方法以下:

/**
 The middle of the player view shows the specified title. duration default is 1.0.
 @param title       prompt.
 */
- (void)showTitle:(NSString *)title;

/**
 The middle of the view shows the specified title.
 @param title       prompt.
 @param duration    prompt duration. duration if value set -1, prompt will always show.
 */
- (void)showTitle:(NSString *)title duration:(NSTimeInterval)duration;

- (void)showTitle:(NSString *)title duration:(NSTimeInterval)duration hiddenExeBlock:(void(^__nullable)(__kindof SJBaseVideoPlayer *player))hiddenExeBlock;

- (void)showAttributedString:(NSAttributedString *)attributedString duration:(NSTimeInterval)duration;

- (void)showAttributedString:(NSAttributedString *)attributedString duration:(NSTimeInterval)duration hiddenExeBlock:(void(^__nullable)(__kindof SJBaseVideoPlayer *player))hiddenExeBlock;

/**
 Hidden Prompt.
 */
- (void)hiddenTitle;

_

 播放器的手勢介紹

  • 默認會存在四種手勢: Single Tap, double Tap, Pan, Pinch.

    1. SingleTap

單擊手勢
當用戶單擊播放器時, 播放器會調用顯示或隱藏控制層的相關代理方法. 見 controlLayerDelegate

  1. DoubleTap
    雙擊手勢
    雙擊會觸發暫停或播放的操做
  2. Pan
    移動手勢
    當用戶水平滑動時, 會觸發控制層相應的代理方法. 見 controlLayerDelegate
    當用戶垂直滑動時, 若是在屏幕左邊, 則會觸發調整亮度的操做, 並顯示亮度提示視圖. 若是在屏幕右邊, 則會觸發調整聲音的操做, 並顯示系統音量提示視圖
  3. Pinch
    捏合手勢
    當用戶作放大或收縮觸發該手勢時, 會設置播放器顯示模式AspectAspectFill.

_

關於自定義控制層的相關協議

下面介紹如何自定義一個控制層, 相關的方法後面, 我都會跟上一個相應實現.

數據源介紹

@protocol SJVideoPlayerControlLayerDataSource <NSObject>

@required
- (UIView *)controlView;
- (BOOL)controlLayerDisappearCondition;
- (BOOL)triggerGesturesCondition:(CGPoint)location;

@optional
- (void)installedControlViewToVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;

@end

代理介紹

控制層的顯示與隱藏
@required
/**Objective-C
 This method will be called when the control layer needs to be appear. You should do some appear work here.
 */
- (void)controlLayerNeedAppear:(__kindof SJBaseVideoPlayer *)videoPlayer;

/**
 This method will be called when the control layer needs to be disappear. You should do some disappear work here.
 */
- (void)controlLayerNeedDisappear:(__kindof SJBaseVideoPlayer *)videoPlayer;

播放器會接管控制層的顯示與隱藏, 也就是當SingleTap時播放器會調用controlLayerNeedAppear: 播放器會在一段時間後調用隱藏controlLayerNeedDisappear:.

在UITableView或者CollectionView中播放時的代理回調
- (void)videoPlayerWillAppearInScrollView:(__kindof SJBaseVideoPlayer *)videoPlayer;
- (void)videoPlayerWillDisappearInScrollView:(__kindof SJBaseVideoPlayer *)videoPlayer;
準備播放一個資源時以及播放狀態/播放進度的回調
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer prepareToPlay:(SJVideoPlayerURLAsset *)asset;

- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer stateChanged:(SJVideoPlayerPlayState)state;

- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer
        currentTime:(NSTimeInterval)currentTime currentTimeStr:(NSString *)currentTimeStr
          totalTime:(NSTimeInterval)totalTime totalTimeStr:(NSString *)totalTimeStr;
緩存的狀態回調
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer loadedTimeProgress:(float)progress;

- (void)startLoading:(__kindof SJBaseVideoPlayer *)videoPlayer;

- (void)cancelLoading:(__kindof SJBaseVideoPlayer *)videoPlayer;

- (void)loadCompletion:(__kindof SJBaseVideoPlayer *)videoPlayer;
鎖屏的回調以及一個只有在鎖屏狀態下才觸發的單擊手勢
- (void)lockedVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;

- (void)unlockedVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;

- (void)tappedPlayerOnTheLockedState:(__kindof SJBaseVideoPlayer *)videoPlayer;
屏幕旋轉的回調
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer willRotateView:(BOOL)isFull;

- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer didEndRotation:(BOOL)isFull;
靜音 / 音量 / 亮度 / 調速的回調
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer muteChanged:(BOOL)mute;

- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer volumeChanged:(float)volume;

- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer brightnessChanged:(float)brightness;

- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer rateChanged:(float)rate;
水平方向手勢觸發的回調
/// 水平方向開始拖動.
- (void)horizontalDirectionWillBeginDragging:(__kindof SJBaseVideoPlayer *)videoPlayer;

/**
 @param progress drag progress
 */
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer horizontalDirectionDidMove:(CGFloat)progress;

/// 水平方向拖動結束.
- (void)horizontalDirectionDidEndDragging:(__kindof SJBaseVideoPlayer *)videoPlayer;
Network
/// 網絡狀態變動
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer reachabilityChanged:(SJNetworkStatus)status;

Demo

項目地址: https://github.com/changsanji...

個人郵箱: changsanjiang@gmail.com

若是您有什麼建議, 望請聯繫我!

相關文章
相關標籤/搜索