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

changsanjiang發表於2019-03-04

SJVideoPlayer

基於AVPlayer.

極速初始化, 不阻塞主執行緒.

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


使用

pod `SJVideoPlayer` 
複製程式碼

功能

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

全屏手勢返回

只需pod到專案中即可帶全屏手勢

為啥自定義手勢?

在使用原生手勢返回時, 當前播放的視訊會出現卡幀的問題. 原因我不太清楚. 我檢視了騰訊視訊和愛奇藝等App均為自己實現的手勢返回.

當時使用了一個全屏返回的三方庫. 是截圖返回的, 發現存在兩個問題: 一是截圖耗時, 二是視訊部分擷取不了(黑色的)
由於我們起步8.0, 我便使用了新的APIsnapshotViewAfterScreenUpdates:(這個介面其實7.0就有了)來替換截圖的方式, 直接使用檢視快照, 這個介面一舉兩得, 不僅速度快, 而且視訊播放也可以擷取到.
但是快照是一個view, 並不是image, 原來的三方庫截圖方式無法使用, 於是我重新擼了一個全屏手勢.

手勢使用

pod `SJFullscreenPopGesture`
複製程式碼

手勢功能介紹

  1. 可設定手勢型別: 全屏手勢 || 邊緣手勢.
// default is `SJFullscreenPopGestureType_EdgeLeft`.
typedef NS_ENUM(NSUInteger, SJFullscreenPopGestureType) {
    SJFullscreenPopGestureType_EdgeLeft,    // 預設, 螢幕左邊緣觸發手勢
    SJFullscreenPopGestureType_Full,        // 全屏觸發手勢
};
複製程式碼
  1. 可設定Pop返回時的動畫效果
    目前有兩種:

  2. 可在某個ViewController禁用手勢

  3. 可相容 WKWebView 手勢返回

  4. 可設定盲區, 在這個區域不觸發手勢

  5. 可設定手勢觸發過程中的回撥

/// 將要拖拽
@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

  2. DoubleTap
    雙擊手勢
    雙擊會觸發暫停或播放的操作

  3. Pan
    移動手勢
    當使用者水平滑動時, 會觸發控制層相應的代理方法. 見 controlLayerDelegate
    當使用者垂直滑動時, 如果在螢幕左邊, 則會觸發調整亮度的操作, 並顯示亮度提示檢視. 如果在螢幕右邊, 則會觸發調整聲音的操作, 並顯示系統音量提示檢視

  4. 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/changsanjiang/SJVideoPlayer

我的郵箱: changsanjiang@gmail.com

如果您有什麼建議, 望請聯絡我!

相關文章