/SJBaseVideoPlayer

video player. rotation, volume, brightness, rate, play, pause, stop, seekToTime, present.

Primary LanguageObjective-CMIT LicenseMIT

SJBaseVideoPlayer

Installation

# Player with default control layer.
pod 'SJVideoPlayer'

# The base player, without the control layer, can be used if you need a custom control layer.
pod 'SJBaseVideoPlayer'

Contact


License

SJBaseVideoPlayer is available under the MIT license. See the LICENSE file for more info.


Documents

1 视图层次

2. 创建资源进行播放

3. 播放控制

4. 控制层的显示和隐藏

5. 设备亮度和音量

6. 旋转

7. 直接全屏而不旋转

8. 镜像翻转

9. 网络状态

10. 手势

11. 占位图

12. 显示提示文本

13. 一些固定代码

14. 截屏

15. 导出视频或GIF

16. 滚动相关

17. 自动播放 - 在 UICollectionView 或者 UITableView 中

18. 控制层数据源, 每个方法介绍

19. 控制层代理, 每个方法介绍











1. 视图层次

SJBaseVideoPlayer 播放的视频资源是通过 SJVideoPlayerURLAsset 进行初始化的. SJVideoPlayerURLAsset 由两部分组成:

  • 视图层次 (SJPlayModel)
  • 资源地址 (可以是本地资源/URL/AVAsset)

默认情况下, 创建了 SJVideoPlayerURLAsset , 赋值给播放器后即可播放. 如下示例:

SJVideoPlayerURLAsset *asset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL playModel:playModel];
_player.URLAsset = asset;

我们先来看 SJPlayModel, 我将以下视图层次封装到了 SJPlayModel 中, 使用它初始化对应层次即可.

1.1 在普通 View 上播放

在普通视图中播放时, 直接创建PlayModel即可.

SJPlayModel *playModel = [SJPlayModel new];

1.2 在 TableViewCell 上播放

--  UITableView
    --  UITableViewCell
        --  Player.superview
            --  Player.view
            
SJPlayModel *playModel = [SJPlayModel UITableViewCellPlayModelWithPlayerSuperviewTag:cell.coverImageView.tag atIndexPath:indexPath tableView:self.tableView];

1.3 在 TableHeaderView 或者 TableFooterView 上播放

--  UITableView
    --  UITableView.tableHeaderView 或者 UITableView.tableFooterView  
        --  Player.superview
            --  Player.view

SJPlayModel *playModel = [SJPlayModel UITableViewHeaderViewPlayModelWithPlayerSuperview:view.coverImageView tableView:self.tableView];

1.4 在 CollectionViewCell 上播放

--  UICollectionView
    --  UICollectionViewCell
        --  Player.superview
            --  Player.view

SJPlayModel *playModel = [SJPlayModel UICollectionViewCellPlayModelWithPlayerSuperviewTag:cell.coverImageView.tag atIndexPath:indexPath collectionView:self.collectionView];

1.5 CollectionView 嵌套在 TableViewHeaderView 中, 在 CollectionViewCell 上播放

--  UITableView
    --  UITableView.tableHeaderView 或者 UITableView.tableFooterView  
        --  tableHeaderView.UICollectionView
            --  UICollectionViewCell
                --  Player.superview
                    --  Player.view

SJPlayModel *playModel = [SJPlayModel UICollectionViewNestedInUITableViewHeaderViewPlayModelWithPlayerSuperviewTag:cell.coverImageView.tag atIndexPath:indexPath collectionView:tableHeaderView.collectionView tableView:self.tableView];

1.6 CollectionView 嵌套在 TableViewCell 中, 在 CollectionViewCell 上播放

--  UITableView
    --  UITableViewCell
        --  UITableViewCell.UICollectionView
            --  UICollectionViewCell
                --  Player.superview
                    --  Player.view

SJPlayModel *playModel = [SJPlayModel UICollectionViewNestedInUITableViewCellPlayModelWithPlayerSuperviewTag:collectionViewCell.coverImageView.tag atIndexPath:collectionViewCellAtIndexPath collectionViewTag:tableViewCell.collectionView.tag collectionViewAtIndexPath:tableViewCellAtIndexPath tableView:self.tableView];

1.7 CollectionView 嵌套在 CollectionViewCell 中, 在 CollectionViewCell 上播放

--  UICollectionView
    --  UICollectionViewCell
        --  UICollectionViewCell.UICollectionView
            --  UICollectionViewCell
                --  Player.superview
                    --  Player.view

SJPlayModel *playModel = [SJPlayModel UICollectionViewNestedInUICollectionViewCellPlayModelWithPlayerSuperviewTag:collectionViewCell.coverImageView.tag atIndexPath:collectionViewCellAtIndexPath collectionViewTag:rootCollectionViewCell.collectionView.tag collectionViewAtIndexPath:collectionViewAtIndexPath rootCollectionView:self.collectionView];

1.8 在 UITableViewHeaderFooterView 上播放

--  UITableView
    --  UITableViewHeaderFooterView 
        --  Player.superview
            --  Player.view            

/// isHeader: 当在header中播放时, 传YES, 在footer时, 传NO.
SJPlayModel *playModel = [SJPlayModel UITableViewHeaderFooterViewPlayModelWithPlayerSuperviewTag:sectionHeaderView.coverImageView.tag inSection:section isHeader:YES tableView:self.tableView];

2. 创建资源进行播放

SJBaseVideoPlayer 播放的视频资源是通过 SJVideoPlayerURLAsset 进行初始化的. SJVideoPlayerURLAsset 由两部分组成:

  • 视图层次 (第一部分中的SJPlayModel)
  • 资源地址 (可以是本地资源/URL/AVAsset)

默认情况下, 创建了 SJVideoPlayerURLAsset , 赋值给播放器后即可播放. 如下示例:

SJVideoPlayerURLAsset *asset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL playModel:playModel];
_player.URLAsset = asset;

2.1 通过 URL 创建资源进行播放

_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL playModel:playModel];

2.2 通过 AVAsset 或其子类进行播放

_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithAVAsset:avAsset playModel:playModel];

2.3 指定开始播放的时间

_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL playModel:playModel];

NSTimeInterval secs = 20.0;
_player.URLAsset.specifyStartTime = secs;

2.4 续播. 进入下个页面时, 继续播放

在播放时, 我们可能需要切换界面, 而希望视频能够在下一个界面无缝的进行播放. 针对此种情况 SJVideoPlayerURLAsset 提供了便利的初始化方法. 请看片段:

/// otherAsset即为上一个页面播放的Asset
/// 除了需要一个otherAsset, 其他方面同以上的示例一模一样
_player.URLAsset = [SJVideoPlayerURLAsset initWithOtherAsset:otherAsset playModel:playModel]; 

2.5 销毁时的回调. 可在此时做一些记录工作, 如播放位置

我们有时候想存储某个视频的播放记录, 以便下次, 能够从指定的位置进行播放.

那什么时候存储合适呢? 最好的时机就是资源被释放时.

SJBaseVideoPlayer 提供了每个资源在 Dealloc 时的回调, 如下:

// 每个资源dealloc时的回调
_player.assetDeallocExeBlock = ^(__kindof SJBaseVideoPlayer * _Nonnull videoPlayer) {
    // .....
};

3. 播放控制

播放控制: 对播放进行的操作. 此部分的内容由 "id <SJMediaPlaybackController> playbackController" 提供支持.

大多数对播放进行的操作, 均在协议 SJMediaPlaybackController 进行了声明.

正常来说实现了此协议的任何对象, 均可赋值给 player.playbackController 来替换原始实现.

3.1 当前时间和时长

/// 当前时间
_player.currentTime

/// 时长
_player.totalTime

/// 字符串化, 
/// - 格式为 00:00(小于 1 小时) 或者 00:00:00 (大于 1 小时)
_player.currentTimeStr
_player.totalTimeStr

3.2 时间改变时的回调

_player.playTimeDidChangeExeBlok = ^(__kindof SJBaseVideoPlayer * _Nonnull videoPlayer) {
    /// ...
};

3.3 播放结束后的回调

_player.playDidToEndExeBlock = ^(__kindof SJBaseVideoPlayer * _Nonnull videoPlayer) {
    /// ...
};

3.4 播放状态 - 未知/准备/准备就绪/播放中/暂停的/不活跃的

播放状态有两个状态需要注意一下, 分别是 暂停和不活跃状态

当状态为暂停时, 目前有3种可能:

  • 正在缓冲
  • 主动暂停
  • 正在跳转

当状态为不活跃时, 目前有2种可能:

  • 播放完毕
  • 播放失败

/**
 当前播放的状态

 - SJVideoPlayerPlayStatusUnknown:      未播放任何资源时的状态
 - SJVideoPlayerPlayStatusPrepare:      准备播放一个资源
 - SJVideoPlayerPlayStatusReadyToPlay:  准备就绪, 可以播放
 - SJVideoPlayerPlayStatusPlaying:      播放中
 - SJVideoPlayerPlayStatusPaused:       暂停状态, 请通过`SJVideoPlayerPausedReason`, 查看暂停原因
 - SJVideoPlayerPlayStatusInactivity:   不活跃状态, 请通过`SJVideoPlayerInactivityReason`, 查看暂停原因
 */
typedef NS_ENUM(NSUInteger, SJVideoPlayerPlayStatus) {
    SJVideoPlayerPlayStatusUnknown,
    SJVideoPlayerPlayStatusPrepare,
    SJVideoPlayerPlayStatusReadyToPlay,
    SJVideoPlayerPlayStatusPlaying,
    SJVideoPlayerPlayStatusPaused,
    SJVideoPlayerPlayStatusInactivity,
};

3.5 暂停的原因 - 缓冲/跳转/暂停

/**
 暂停的理由

 - SJVideoPlayerPausedReasonBuffering:   正在缓冲
 - SJVideoPlayerPausedReasonPause:       被暂停
 - SJVideoPlayerPausedReasonSeeking:     正在跳转(调用seekToTime:时)
 */
typedef NS_ENUM(NSUInteger, SJVideoPlayerPausedReason) {
    SJVideoPlayerPausedReasonBuffering,
    SJVideoPlayerPausedReasonPause,
    SJVideoPlayerPausedReasonSeeking,
};

3.6 不活跃的原因 - 加载失败/播放完毕

/**
 不活跃的原因
 
 - SJVideoPlayerInactivityReasonPlayEnd:    播放完毕
 - SJVideoPlayerInactivityReasonPlayFailed: 播放失败
 */
typedef NS_ENUM(NSUInteger, SJVideoPlayerInactivityReason) {
    SJVideoPlayerInactivityReasonPlayEnd,
    SJVideoPlayerInactivityReasonPlayFailed,
};

3.7 播放状态改变的回调

对播放状态的判断我添加了一个便利的分类 `SJBaseVideoPlayer (PlayStatus)`

如需判断状态, 可导入头文件 #import "SJBaseVideoPlayer+PlayStatus.h" 使用.

/// 播放状态改变的回调
_player.playStatusDidChangeExeBlock = ^(__kindof SJBaseVideoPlayer * _Nonnull videoPlayer) {

};

/// 对播放状态的判断我添加了一个便利的分类
@interface SJBaseVideoPlayer (PlayStatus)

- (NSString *)getPlayStatusStr:(SJVideoPlayerPlayStatus)status;

- (BOOL)playStatus_isUnknown;

- (BOOL)playStatus_isPrepare;

- (BOOL)playStatus_isReadyToPlay;

- (BOOL)playStatus_isPlaying;

- (BOOL)playStatus_isPaused;

- (BOOL)playStatus_isPaused_ReasonBuffering;

- (BOOL)playStatus_isPaused_ReasonPause;

- (BOOL)playStatus_isPaused_ReasonSeeking;

- (BOOL)playStatus_isInactivity;

- (BOOL)playStatus_isInactivity_ReasonPlayEnd;

- (BOOL)playStatus_isInactivity_ReasonPlayFailed;

@end

3.8 是否自动播放 - 当资源初始化完成后

_player.autoPlayWhenPlayStatusIsReadyToPlay = YES;

3.9 刷新

在播放一个资源时, 可能有一些意外情况导致播放失败(如网络环境差).

此时当用户点击刷新按钮, 我们需要对当前的资源(Asset)进行刷新.

SJBaseVideoPlayer提供了直接的方法去刷新, 不需要开发者再重复的去创建新的Asset.

[_player refresh];

3.10 播放器的声音设置 & 静音

/// 默认值为 1.0, 最小为 0.0
_player.playerVolume = 1.0;

/// 设置静音
_player.mute = YES;

3.11 播放

[_player play];

3.12 暂停

[_player pause];

3.13 是否暂停 - 当App进入后台后

关于后台播放视频, 引用自: https://juejin.im/post/5a38e1a0f265da4327185a26

当您想在后台播放视频时:

  1. 需要设置 videoPlayer.pauseWhenAppDidEnterBackground = NO; (该值默认为YES, 即App进入后台默认暂停).

  2. 前往 TARGETS -> Capability -> enable Background Modes -> select this mode Audio, AirPlay, and Picture in Picture

_player.pauseWhenAppDidEnterBackground = NO; // 默认值为 YES, 即进入后台后 暂停.

3.14 停止

注意, 调用此方法后, 当前的 asset 将会被清空. 也就是说, 调用 play等播放操作将会无效.

[_player stop];

3.15 重播

从头开始重新播放

[_player replay];

3.16 跳转到指定的时间播放

NSTimeInterval secs = 20.0;
[_player seekToTime:secs completionHandler:^(BOOL finished) {
    // ....
}];

3.17 调速 & 速率改变时的回调

/// 默认值为 1.0
_player.rate = 1.0;


_player.rateDidChangeExeBlock = ^(__kindof SJBaseVideoPlayer * _Nonnull player) {
    /// .. 
}

3.18 接入别的视频 SDK, 自己动手撸一个 SJMediaPlaybackController, 替换作者原始实现

某些时候, 我们需要接入第三方的视频SDK, 但是又想使用 SJBaseVideoPlayer 封装的其他的功能.

这个时候, 我们可以自己动手, 将第三方的SDK封装一下, 实现 SJMediaPlaybackController 协议, 管理 SJBaseVideoPlayer 中的播放操作.

示例:

_player.playbackController = Your PlaybackController.

4. 控制层的显示和隐藏

控制层的显示和隐藏, 此部分的内容由 "id <SJControlLayerAppearManager> controlLayerAppearManager" 提供支持.

controlLayerAppearManager 内部存在一个定时器, 当控制层显示时, 会开启此定时器. 一定间隔后, 会尝试隐藏控制层.

其他相关操作, 请见以下内容.

4.1 让控制层显示

当控制层需要显示时, 可以调用下面方法.

此方法将会回调控制层的代理方法:

"- (void)controlLayerNeedAppear:(__kindof SJBaseVideoPlayer *)videoPlayer;"

代理将会对当前的控制层进行显示处理.

[_player controlLayerNeedAppear];

4.2 让控制层隐藏

当控制层需要隐藏时, 可以调用下面方法.

此方法将会回调控制层的代理方法:

"- (void)controlLayerNeedDisappear:(__kindof SJBaseVideoPlayer *)videoPlayer;"

代理将会对当前的控制层进行隐藏处理.

[_player controlLayerNeedDisappear];

4.3 控制层是否显示中

/// 是否显示, YES为显示, NO为隐藏
_player.controlLayerIsAppeared

4.4 是否在暂停时保持控制层显示

/// 默认为 NO, 即不保持显示
_player.pausedToKeepAppearState = YES;

4.5 是否自动显示控制层 - 资源初始化完成后

/// 默认为 NO, 即不显示
_player.controlLayerAutoAppearWhenAssetInitialized = YES;

4.6 控制层显示状态改变的回调

@property (nonatomic, copy, nullable) void(^controlLayerAppearStateDidChangeExeBlock)(__kindof SJBaseVideoPlayer *player, BOOL state);

4.7 禁止管理控制层的显示和隐藏

有时候, 我们可能不需要对控制层的显示和隐藏进行管理. 这个时候可以设置如下属性, 来禁止管理类的操作.

@property (nonatomic) BOOL disabledControlLayerAppearManager; // default value is NO.

4.8 自己动手撸一个 SJControlLayerAppearManager, 替换作者原始实现

同样的, 协议 "SJControlLayerAppearManager" 定义了一系列的操作, 只要实现了这些协议方法的对象, 就可以管理控制层的显示和隐藏.

_player.controlLayerAppearManager = Your controlLayerAppearManager; 

5. 设备亮度和音量

设备亮度和音量的调整, 此部分的内容由 "id <SJDeviceVolumeAndBrightnessManager> deviceVolumeAndBrightnessManager" 提供支持.

5.1 调整设备亮度

_player.deviceBrightness = 1.0;

5.2 调整设备声音

_player.deviceVolume = 1.0;

5.3 亮度 & 声音改变后的回调

_observer = [_player.deviceVolumeAndBrightnessManager getObserver];

observer.volumeDidChangeExeBlock = ...;
observer.brightnessDidChangeExeBlock = ...;

5.4 禁止播放器设置

_player.disableBrightnessSetting = YES;
_player.disableVolumeSetting = YES;

5.5 自己动手撸一个 SJDeviceVolumeAndBrightnessManager, 替换作者原始实现

当需要对设备音量视图进行自定义时, 可以自己动手撸一个 SJDeviceVolumeAndBrightnessManager.

_player.deviceVolumeAndBrightnessManager = Your deviceVolumeAndBrightnessManager;

6. 旋转

此部分的内容由 "id <SJRotationManagerProtocol> rotationManager" 提供支持.

对于旋转, 我们开发者肯定需要绝对的控制, 例如: 设置自动旋转所支持方向. 能够主动+自动旋转, 而且还需要能在适当的时候禁止自动旋转. 旋转前后的回调等等... 放心这些功能都有, 我挨个给大家介绍.

另外旋转有两种方式:

  • 仅旋转播放器视图 (默认情况下)
  • 使 ViewController 也一起旋转

具体请看下面介绍.

6.1 自动旋转

先说说何为自动旋转. 其实就是当设备方向变更时, 播放器根据设备方向进行自动旋转.

6.2 设置自动旋转支持的方向

/// 设置自动旋转支持的方向
_player.supportedOrientation = SJAutoRotateSupportedOrientation_LandscapeLeft | SJAutoRotateSupportedOrientation_LandscapeRight;


/**
 自动旋转支持的方向
 
 - SJAutoRotateSupportedOrientation_Portrait:       竖屏
 - SJAutoRotateSupportedOrientation_LandscapeLeft:  支持全屏, Home键在右侧
 - SJAutoRotateSupportedOrientation_LandscapeRight: 支持全屏, Home键在左侧
 - SJAutoRotateSupportedOrientation_All:            全部方向
 */
typedef NS_ENUM(NSUInteger, SJAutoRotateSupportedOrientation) {
    SJAutoRotateSupportedOrientation_Portrait = 1 << 0,
    SJAutoRotateSupportedOrientation_LandscapeLeft = 1 << 1,  
    SJAutoRotateSupportedOrientation_LandscapeRight = 1 << 2, 
    SJAutoRotateSupportedOrientation_All = SJAutoRotateSupportedOrientation_Portrait | SJAutoRotateSupportedOrientation_LandscapeLeft | SJAutoRotateSupportedOrientation_LandscapeRight,
};

6.3 禁止自动旋转

这里有两点需要注意:

  • 合适的时候要记得恢复自动旋转.
  • 禁止自动旋转后, 主动调用旋转, 还是可以旋转的.

_player.disableAutoRotation = YES;

6.4 主动调用旋转

主动旋转. 当我们想主动旋转时, 大概分为以下三点:

  • 播放器旋转到用户当前的设备方向或恢复小屏.
  • 主动旋转到指定方向.
  • 主动旋转完成后的回调.

请看以下方法, 分别对应以上三点:

- (void)rotate;
- (void)rotate:(SJOrientation)orientation animated:(BOOL)animated;
- (void)rotate:(SJOrientation)orientation animated:(BOOL)animated completion:(void (^ _Nullable)(__kindof SJBaseVideoPlayer *player))block;

6.5 是否全屏

/// 如果为YES, 表示全屏
_player.isFullScreen

6.6 是否正在旋转

/// 如果为YES, 表示正在旋转中
_player.isTransitioning

6.7 当前旋转的方向

_player.orientation

6.8 旋转开始和结束的回调

_observer = [self.rotationManager getObserver];
_observer.rotationDidStartExeBlock = ^(id<SJRotationManagerProtocol>  _Nonnull mgr) {
    /// ...
};
    
_observer.rotationDidEndExeBlock = ^(id<SJRotationManagerProtocol>  _Nonnull mgr) {
    /// ...
};

6.9 使 ViewController 一起旋转

默认情况下, _player.rotationManager 使用的是 SJRotationManager 的实例. 它只会旋转播放器视图.

当我们需要 ViewController 也一起旋转时, 需要切换 旋转管理类为 SJVCRotationManager. 如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    _player.rotationManager = [[SJVCRotationManager alloc] initWithViewController:vc];
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    SJVCRotationManager *mgr = _player.rotationManager;
    [mgr vc_viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

- (BOOL)shouldAutorotate {
    SJVCRotationManager *mgr = _player.rotationManager;
    return [mgr vc_shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    SJVCRotationManager *mgr = _player.rotationManager;
    return [mgr vc_supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    SJVCRotationManager *mgr = _player.rotationManager;
    return [mgr vc_preferredInterfaceOrientationForPresentation];
}

6.10 自己动手撸一个 SJRotationManager, 替换作者原始实现

正如使用 6.9 使 ViewController 一起旋转 中使用 SJVCRotationManager 替换 SJRotationManager 一样, 当你想替换原始实现时, 可以实现 SJRotationManagerProtocol 中定义的方法.


7. 直接全屏而不旋转

直接全屏, 或者说充满屏幕, 但不旋转.

7.1 全屏和恢复

_player.fitOnScreen = YES;

[_player setFitOnScreen:NO animated:NO];

[_player setFitOnScreen:YES animated:YES completionHandler:^(__kindof SJBaseVideoPlayer * _Nonnull player) {
    /// ...
}];

7.2 开始和结束的回调

@property (nonatomic, copy, nullable) void(^fitOnScreenWillBeginExeBlock)(__kindof SJBaseVideoPlayer *player);
@property (nonatomic, copy, nullable) void(^fitOnScreenDidEndExeBlock)(__kindof SJBaseVideoPlayer *player);;

7.3 是否是全屏

/// YES 为充满屏幕 
_player.isFitOnScreen

7.4 自己动手撸一个 SJFitOnScreenManager, 替换作者原始实现

该部分管理类的协议定义在 SJFitOnScreenManagerProtocol 中, 实现该协议的任何对象, 均可赋值给播放器, 替换原始实现.


8. 镜像翻转

此部分内容由 id<SJFlipTransitionManager> flipTransitionManager 提供支持

目前镜像翻转只写了 水平翻转, 未来可能会加入更多的翻转类型.

typedef enum : NSUInteger {
    SJViewFlipTransition_Identity,
    SJViewFlipTransition_Horizontally, // 水平翻转
} SJViewFlipTransition;

8.1 翻转和恢复

/// 当前的翻转类型
_player.flipTransition

/// 翻转相关方法
[_player setFlipTransition:SJViewFlipTransition_Horizontally];
[_player setFlipTransition:SJViewFlipTransition_Horizontally animated:YES];
[_player setFlipTransition:SJViewFlipTransition_Identity animated:YES completionHandler:^(__kindof SJBaseVideoPlayer * _Nonnull player) {
    /// ...
}];

8.2 开始和结束的回调

@property (nonatomic, copy, nullable) void(^flipTransitionDidStartExeBlock)(__kindof SJBaseVideoPlayer *player);
@property (nonatomic, copy, nullable) void(^flipTransitionDidStopExeBlock)(__kindof SJBaseVideoPlayer *player);

8.3 自己动手撸一个 SJFlipTransitionManager, 替换作者原始实现

该部分管理类的协议定义在 SJFlipTransitionManagerProtocol 中, 实现该协议的任何对象, 均可赋值给播放器, 替换原始实现.


9. 网络状态

此部分内容由 id<SJReachability> reachability 提供支持

默认的 reachability 是个单例, 在App生命周期中, 仅创建一次. 因此每个播放器对象持有的 reachability 都是相同的.

9.1 当前的网络状态

@property (nonatomic, readonly) SJNetworkStatus networkStatus;

9.2 网络状态改变的回调

@property (nonatomic, copy, nullable) void(^networkStatusDidChangeExeBlock)(__kindof SJBaseVideoPlayer *player);

9.3 自己动手撸一个 SJReachability, 替换作者原始实现

该部分管理类的协议定义在 SJNetworkStatus 中, 实现该协议的任何对象, 均可赋值给播放器, 替换原始实现.


10. 手势

此部分内容由 id<SJPlayerGestureControl> gestureControl 提供支持

播放器默认存在四种手势, 每个手势触发的回调均定义在 SJPlayerGestureControl 中, 当想改变某个手势的处理时, 可以直接修改对应手势触发的 block 即可.

具体请看以下部分.

10.1 单击手势

当用户单击播放器时, 播放器会调用 显示或隐藏控制层的操作

以下为默认实现:

__weak typeof(self) _self = self;
_gestureControl.singleTapHandler = ^(id<SJPlayerGestureControl>  _Nonnull control, CGPoint location) {
    __strong typeof(_self) self = _self;
    if ( !self ) return ;
    /// 让控制层显示或隐藏
    [self.controlLayerAppearManager switchAppearState];
};

10.2 双击手势

双击会触发暂停或播放的操作

__weak typeof(self) _self = self;
_gestureControl.doubleTapHandler = ^(id<SJPlayerGestureControl>  _Nonnull control, CGPoint location) {
    __strong typeof(_self) self = _self;
    if ( !self ) return ;
    if ( [self playStatus_isPlaying] )
        [self pause];
    else
        [self play];
};

10.3 移动手势

  • 垂直滑动时, 默认情况下如果在屏幕左边, 则会触发调整亮度的操作, 并显示亮度提示视图. 如果在屏幕右边, 则会触发调整声音的操作, 并显示系统音量提示视图
  • 水平滑动时, 会触发控制层相应的代理方法
__weak typeof(self) _self = self;
_gestureControl.panHandler = ^(id<SJPlayerGestureControl>  _Nonnull control, SJPanGestureTriggeredPosition position, SJPanGestureMovingDirection direction, SJPanGestureRecognizerState state, CGPoint translate) {
    __strong typeof(_self) self = _self;
    if ( !self ) return ;
    /// ....
};

10.4 捏合手势

当用户做放大或收缩触发该手势时, 会设置播放器显示模式`Aspect`或`AspectFill`.

__weak typeof(self) _self = self;
_gestureControl.pinchHandler = ^(id<SJPlayerGestureControl>  _Nonnull control, CGFloat scale) {
    __strong typeof(_self) self = _self;
    if ( !self ) return ;
    self.playbackController.videoGravity = scale > 1 ?AVLayerVideoGravityResizeAspectFill:AVLayerVideoGravityResizeAspect;
};

10.5 禁止某些手势

当需要禁止某个手势时, 可以像如下设置:

/// 此处为禁止单击和双击手势
_player.disabledGestures = SJPlayerGestureType_SingleTap | SJPlayerGestureType_DoubleTap;  

typedef enum : NSUInteger {
    SJPlayerGestureType_SingleTap,
    SJPlayerGestureType_DoubleTap,
    SJPlayerGestureType_Pan,
    SJPlayerGestureType_Pinch,
} SJPlayerGestureType;

10.6 自定义某个手势的处理

/// 例如 替换单击手势的处理
__weak typeof(self) _self = self;
_player.gestureControl.singleTapHandler = ^(id<SJPlayerGestureControl>  _Nonnull control, CGPoint location) {
    __strong typeof(_self) self = _self;
    if ( !self ) return ;
    /// .....你的处理
};

10.7 自己动手撸一个 SJPlayerGestureControl, 替换作者原始实现

该部分管理类的协议定义在 SJPlayerGestureControlProtocol 中, 实现该协议的任何对象, 均可赋值给播放器, 替换原始实现.


11. 占位图

资源在初始化时, 由于暂时没有画面可以呈现, 会出现短暂的黑屏. 在此期间, 建议大家设置一下占位图.

11.1 设置本地占位图

_player.placeholderImageView.image = [UIImage imageNamed:@"..."];

11.2 设置网络占位图

[_player.placeholderImageView sd_setImageWithURL:URL placeholderImage:img];

11.3 是否隐藏占位图 - 播放器准备好显示时

/// 播放器准备好显示时, 是否隐藏占位图
/// - 默认为YES
@property (nonatomic) BOOL hiddenPlaceholderImageViewWhenPlayerIsReadyForDisplay;

12. 显示提示文本

目前提示文本支持 NSString 以及 NSAttributedString.

12.1 显示文本及持续时间 - (NSString or NSAttributedString)

/// duration 如果为 -1, 则会一直显示 
- (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;

/// 隐藏
- (void)hiddenTitle;

12.2 配置提示文本

/// Update
_player.prompt.update(^(SJPromptConfig * _Nonnull config) {
    config.font = [UIFont systemFontOfSize:12];
});

/// 所有属性如下: 
@interface SJPromptConfig : NSObject

/// default is UIEdgeInsetsMake( 8, 8, 8, 8 ).
@property (nonatomic, assign) UIEdgeInsets insets;

/// default is 8.
@property (nonatomic, assign) CGFloat cornerRadius;

/// default is black.
@property (nonatomic, strong) UIColor *backgroundColor;

/// default is systemFont( 14 ).
@property (nonatomic, assign) UIFont *font;

/// default is white.
@property (nonatomic, strong) UIColor *fontColor;

/// default is ( superview.width * 0.6 ).
@property (nonatomic, assign) CGFloat maxWidth;

- (void)reset;

@end

13. 一些固定代码

接入播放器的 ViewController 中, 会写一些固定的代码, 我将这些固定代码(例如 进入下个页面时, 需要当前页面的播放器暂停), 都封装在了以下方法中.

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [_player vc_viewDidAppear];
}

在适当的时候直接调用即可, 以下为内部实现:

13.1 - (void)vc_viewDidAppear;

当 ViewController 的 viewDidAppear 调用时, 恢复播放

实现如下:

- (void)vc_viewDidAppear {
    if ( !self.isPlayOnScrollView || (self.isPlayOnScrollView && self.isScrollAppeared) ) {
    /// 恢复播放
        [self play];
    }
    
    /// 标识vc已显示 
    /// vc_isDisappeared 是自动旋转触发的条件之一, 如果控制器 disappear 了, 就不会触发旋转 
    self.vc_isDisappeared = NO;
}

13.2 - (void)vc_viewWillDisappear;

当 ViewController 的 viewWillDisappear 调用时, 设置标识为YES

实现如下:

- (void)vc_viewWillDisappear {
    /// 标识vc已显示 
    /// vc_isDisappeared 是自动旋转触发的条件之一, 如果控制器 disappear 了, 就不会触发旋转 
    self.vc_isDisappeared = YES;
}

13.3 - (void)vc_viewDidDisappear;

当 ViewController 的 viewDidDisappear 调用时, 暂停播放

实现如下:

- (void)vc_viewDidDisappear {
    [self pause];
}

13.4 - (BOOL)vc_prefersStatusBarHidden;

状态栏是否可以隐藏

实现如下:

- (BOOL)vc_prefersStatusBarHidden {
    if ( _tmpShowStatusBar ) return NO;         // 临时显示
    if ( _tmpHiddenStatusBar ) return YES;      // 临时隐藏
    if ( self.lockedScreen ) return YES;        // 锁屏时, 不显示
    if ( self.rotationManager.transitioning ) { // 旋转时, 不显示
        if ( !self.disabledControlLayerAppearManager && self.controlLayerIsAppeared ) return NO;
        return YES;
    }
    // 全屏播放时, 使状态栏根据控制层显示或隐藏
    if ( self.isFullScreen ) return !self.controlLayerIsAppeared;
    return NO;
}

13.5 - (UIStatusBarStyle)vc_preferredStatusBarStyle;

状态栏显示白色还是黑色

实现如下:

- (UIStatusBarStyle)vc_preferredStatusBarStyle {
    // 全屏播放时, 使状态栏变成白色
    if ( self.isFullScreen || self.fitOnScreen ) return UIStatusBarStyleLightContent;
    return UIStatusBarStyleDefault;
}

13.6 - 临时显示状态栏

有时候, 可能会希望临时显示状态栏, 例如全屏转回小屏时, 旋转之前, 需要将状态栏显示.

[_player needShowStatusBar]; 

13.7 - 临时隐藏状态栏

有时候, 可能会希望临时隐藏状态栏, 例如某个播放器控制层不需要显示状态栏.

[_player needHiddenStatusBar]; 

14. 截屏

14.1 当前时间截图

UIImage *img = [_player screenshot];

14.2 指定时间截图

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

/// 可以通过 _player.playbackController.presentationSize 来获取当前视频宽高
- (void)screenshotWithTime:(NSTimeInterval)secs
                      size:(CGSize)size
                completion:(void(^)(__kindof SJBaseVideoPlayer *videoPlayer, UIImage * __nullable image, NSError *__nullable error))block;

14.3 生成预览视图, 大约20张

/// 可以通过 _player.playbackController.presentationSize 来获取当前视频宽高
/// itemSize 应该尽可能的小一点, 这样处理的效率会更快
- (void)generatedPreviewImagesWithMaxItemSize:(CGSize)itemSize
                                   completion:(void(^)(__kindof SJBaseVideoPlayer *player, NSArray<id<SJVideoPlayerPreviewInfo>> *__nullable images, NSError *__nullable error))block;

15. 导出视频或GIF

15.1 导出视频

/**
 export session.
 
 @param beginTime           开始的位置, 单位是秒
 @param endTime             结束的位置, 单位是秒
 @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;

15.2 导出GIF

/**
生成GIF

@param beginTime 开始的位置, 单位是秒
@param duration  时长
@param progressBlock 进度回调
@param completion 完成的回调
@param failure 失败的回调
*/
- (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;

15.3 取消操作

/// 取消导出操作
/// 播放器 dealloc 时, 会调用一次 
- (void)cancelExportOperation;

/// 取消GIF操作
/// 播放器 dealloc 时, 会调用一次 
- (void)cancelGenerateGIFOperation;

16. 滚动相关

此部分的内容由 SJPlayModelPropertiesObserver 提供支持.

16.1 是否在 UICollectionView 或者 UITableView 中播放

/// 是否是在 UICollectionView 或者 UITableView 中播放
_player.isPlayOnScrollView

16.2 是否滚动显示

/// 是否滚动显示
_player.isScrollAppeared

16.3 播放器视图将要滚动显示和消失的回调

@property (nonatomic, copy, nullable) void(^playerViewWillAppearExeBlock)(__kindof SJBaseVideoPlayer *videoPlayer);
@property (nonatomic, copy, nullable) void(^playerViewWillDisappearExeBlock)(__kindof SJBaseVideoPlayer *videoPlayer);

17. 自动播放 - 在 UICollectionView 或者 UITableView 中

目前支持在 UICollectionViewCell 和 UITableViewCell 中自动播放.

使用之前, 请导入头文件 #import "UIScrollView+ListViewAutoplaySJAdd.h"

17.1 开启

/// 配置列表自动播放
[_tableView sj_enableAutoplayWithConfig:[SJPlayerAutoplayConfig configWithPlayerSuperviewTag:101 autoplayDelegate:self]];


/// Delegate method
- (void)sj_playerNeedPlayNewAssetAtIndexPath:(NSIndexPath *)indexPath {

}

17.2 配置

typedef NS_ENUM(NSUInteger, SJAutoplayScrollAnimationType) {
    SJAutoplayScrollAnimationTypeNone,
    SJAutoplayScrollAnimationTypeTop,
    SJAutoplayScrollAnimationTypeMiddle,
};

@interface SJPlayerAutoplayConfig : NSObject
+ (instancetype)configWithPlayerSuperviewTag:(NSInteger)playerSuperviewTag
                            autoplayDelegate:(id<SJPlayerAutoplayDelegate>)autoplayDelegate;

/// 滚动的动画类型
/// default is .Middle;
@property (nonatomic) SJAutoplayScrollAnimationType animationType;

@property (nonatomic, readonly) NSInteger playerSuperviewTag;
@property (nonatomic, weak, nullable, readonly) id<SJPlayerAutoplayDelegate> autoplayDelegate;
@end

@protocol SJPlayerAutoplayDelegate <NSObject>
- (void)sj_playerNeedPlayNewAssetAtIndexPath:(NSIndexPath *)indexPath;
@end

17.3 关闭

[_tableView sj_disenableAutoplay];

17.4 主动调用播放下一个资源

[_tableView sj_needPlayNextAsset];

18. 控制层数据源, 每个方法介绍

18.1 - (UIView *)controlView;

controlView 为控制层的视图, 它将会被添加到播放器中

18.2 - (void)installedControlViewToVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;

当播放器将controlView添加到播放器视图中后, 会回调这个方法.


19. 控制层代理, 每个方法介绍

19.1 - (void)controlLayerNeedAppear:(__kindof SJBaseVideoPlayer *)videoPlayer;

控制层需要显示的时候, 会回调这个方法. 你应该在这里做一些显示的工作.

当调用 [_player controlLayerNeedAppear] 时, 此时会立即回调这个方法

19.2 - (void)controlLayerNeedDisappear:(__kindof SJBaseVideoPlayer *)videoPlayer;

当控制层需要隐藏的时候, 会回调这个方法. 你应该在这里做一些隐藏的工作.

关于控制层的隐藏: 默认情况下(videoPlayer.enableControlLayerDisplayController==YES)

  • 当调用[videoPlayer controlLayerNeedDisappear]时, 此时会立即回调这个方法
  • 当控制层显示时, 默认会在3秒后, 自动调用这个方法, 隐藏控制层

19.3 - (BOOL)controlLayerOfVideoPlayerCanAutomaticallyDisappear:(__kindof SJBaseVideoPlayer *)videoPlayer;

控制层是否可以自动隐藏, 返回NO, 播放器将不会调用隐藏的代理方法.

19.4 - (void)videoPlayerWillAppearInScrollView:(__kindof SJBaseVideoPlayer *)videoPlayer;

滚动 scrollView 时, 播放器即将出现时会回调这个方法.

19.5 - (void)videoPlayerWillDisappearInScrollView:(__kindof SJBaseVideoPlayer *)videoPlayer;

滚动scrollView时, 播放器即将消失时会回调这个方法.


SJPlayStatusControlDelegate

19.6 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer prepareToPlay:(SJVideoPlayerURLAsset *)asset;

当播放器播放一个新的资源时, 会回调这个方法

19.7 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer statusDidChanged:(SJVideoPlayerPlayStatus)status;

当播放状态改变时, 会回调这个方法

19.8 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer currentTime:(NSTimeInterval)currentTime currentTimeStr:(NSString *)currentTimeStrtotalTime:(NSTimeInterval)totalTime totalTimeStr:(NSString *)totalTimeStr;

播放时间改变的回调

19.9 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer presentationSize:(CGSize)size;

播放器获取到视频宽高后, 会回调这个方法


SJVolumeBrightnessRateControlDelegate

19.10 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer muteChanged:(BOOL)mute;

设置静音时, 会回调这个方法

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

设置系统音量时, 会回调这个方法

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

设置系统亮度时, 会回调这个方法

19.13 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer rateChanged:(float)rate;

设置速率时, 会回调这个方法


SJBufferControlDelegate

19.14 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer bufferTimeDidChange:(NSTimeInterval)bufferTime;

缓冲时间改变的回调.

19.15 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer bufferStatusDidChange:(SJPlayerBufferStatus)bufferStatus;

缓冲状态改变的回调


SJRotationControlDelegate

19.16 - (BOOL)canTriggerRotationOfVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;

播放器是否可以出发自动旋转.

19.17 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer willRotateView:(BOOL)isFull;

开始旋转的回调

19.18 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer didEndRotation:(BOOL)isFull;

结束旋转的回调


SJFitOnScreenControlDelegate

19.19 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer willFitOnScreen:(BOOL)isFitOnScreen;

将要充满屏幕的回调

19.20 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer didCompleteFitOnScreen:(BOOL)isFitOnScreen;

完成后的回调


SJGestureControlDelegate

19.24 - (BOOL)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer gestureRecognizerShouldTrigger:(SJPlayerGestureType)type location:(CGPoint)location;

是否可以出发某个手势

19.25 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer panGestureTriggeredInTheHorizontalDirection:(SJPanGestureRecognizerState)state progressTime:(NSTimeInterval)progressTime;

水平方向拖动


SJNetworkStatusControlDelegate

19.26 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer reachabilityChanged:(SJNetworkStatus)status;

网络状态变更的回调.

SJLockScreenStateControlDelegate

19.27 - (void)tappedPlayerOnTheLockedState:(__kindof SJBaseVideoPlayer *)videoPlayer;

这是一个只有在播放器锁屏状态下, 才会回调的方法

当播放器锁屏后, 用户每次点击都会回调这个方法

19.28 - (void)lockedVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;

锁屏后的回调

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

解锁后的回调


SJSwitchVideoDefinitionControlDelegate

19.30 - (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer switchVideoDefinitionByURL:(NSURL *)URL statusDidChange:(SJMediaPlaybackSwitchDefinitionStatus)status;

切换分辨率的回调