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

SJVideoPlayer

基於AVPlayer.
極速初始化, 不阻塞主線程.
這個應該是目前基於AVPlayer的播放器中, 功能最全的一個吧.

圖解使用請移步: https://www.jianshu.com/p/a60389f9acaf
模塊化控制層請移步:  https://www.jianshu.com/p/6a968ec24d3f


使用

 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章