Getting Started
wiki: https://github.com/changsanjiang/SJVideoPlayer/wiki
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'
天朝
# 如果网络不行安装不了, 可改成以下方式进行安装
pod 'SJBaseVideoPlayer', :git => 'https://gitee.com/changsanjiang/SJBaseVideoPlayer.git'
pod 'SJVideoPlayer', :git => 'https://gitee.com/changsanjiang/SJVideoPlayer.git'
pod 'SJUIKit/Queues', :git => 'https://gitee.com/changsanjiang/SJUIKit.git'
$ pod update --no-repo-update (不要用 pod install 了, 用这个命令安装)
切换别的播放器SDK
本项目对播放控制默认封装的是 AVPlayer, 以下为切换别的播放器SDK:
- 播放控制切换为: ijkplayer
- 播放控制切换为: AliPlayer
- 播放控制切换为: AliyunVodPlayer
Example
_player = [SJVideoPlayer player];
_player.view.frame = CGRectMake(0, 0, 200, 200);
[self.view addSubview:_player.view];
// 设置资源进行播放
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL];
... 等等, 更多设置, 请查看头文件. 相应功能均为懒加载, 用到时才会创建.
Author
Email: changsanjiang@gmail.com
QQGroup: 930508201 (iOS 开发)
Documents
v2.6.5 往后的版本, 请配置旋转 !!
1. 视图层次结构
2. URLAsset
- 2.1 播放 URL(本地文件或远程资源)
- 2.2 播放 AVAsset 或其子类
- 2.3 从指定的位置开始播放
- 2.4 续播(进入下个页面时, 继续播放)
- 2.5 销毁时的回调. 可在此做一些记录工作, 如播放记录
3. 播放控制
- 3.1 播放
- 3.2 暂停
- 3.3 刷新
- 3.4 重播
- 3.5 停止
- 3.6 静音
- 3.7 调速
- 3.8 报错
- 3.9 跳转
- 3.10 切换清晰度
- 3.11 当前时间
- 3.12 总时长
- 3.13 缓冲时长
- 3.14 是否已播放完毕
- 3.15 是否调用过播放
- 3.16 是否调用过重播
- 3.17 设置新资源时, 是否自动播放
- 3.18 进入后台, 是否暂停播放
- 3.19 进入前台, 是否恢复播放
- 3.20 跳转完成, 是否恢复播放
- 3.21 资源准备状态
- 3.22 播放控制状态
- 3.23 播放等待的原因
- 3.24 监听状态改变?
- 3.25 已观看的时长(当前资源)
- 3.26 接入别的视频 SDK, 自己动手撸一个 SJVideoPlayerPlaybackController, 替换作者原始实现
4. 控制层的显示和隐藏
- 4.1 让控制层显示
- 4.2 让控制层隐藏
- 4.3 控制层是否显示中
- 4.4 是否在暂停时保持控制层显示
- 4.5 监听状态改变?
- 4.6 自己动手撸一个 SJControlLayerAppearManager, 替换作者原始实现
5. 设备亮度和音量
- 5.1 调整设备亮度
- 5.2 调整设备声音
- 5.3 监听状态改变?
- 5.4 禁止播放器设置
- 5.5 自己动手撸一个 SJDeviceVolumeAndBrightnessManager, 替换作者原始实现
6. 旋转
- 6.0 旋转的配置(必须)
- 6.1 自动旋转
- 6.2 设置自动旋转支持的方向
- 6.3 禁止自动旋转
- 6.4 主动调用旋转
- 6.5 是否全屏
- 6.6 是否正在旋转
- 6.7 当前旋转的方向
- 6.8 监听状态改变?
- 6.9 自己动手撸一个 SJRotationManager, 替换作者原始实现
7. 直接全屏而不旋转
8. 镜像翻转
9. 网络状态
10. 手势
11. 占位图
12. 显示提示文本
13. 一些固定代码
- 13.4 - (BOOL)vc_prefersStatusBarHidden;
- 13.5 - (UIStatusBarStyle)vc_preferredStatusBarStyle;
- 13.6 - 临时显示状态栏
- 13.7 - 临时隐藏状态栏
14. 截屏
15. 导出视频或GIF
16. 滚动相关
- 16.1 是否在 UICollectionView 或者 UITableView 中播放
- 16.2 是否已显示
- 16.3 播放器视图将要滚动显示和消失的回调
- 16.4 滚动出去后, 是否暂停
- 16.5 滚动进入时, 是否恢复播放
- 16.6 滚动出去后, 是否隐藏播放器视图
17. 自动播放 - 在 UICollectionView 或者 UITableView 中
18. 对控制层上的Item的操作
19. 对控制层上的Item的一些补充
20. SJEdgeControlLayer 的补充
- 20.1 是否竖屏时隐藏返回按钮
- 20.2 是否禁止网络状态变化提示
- 20.3 是否使返回按钮常驻
- 20.4 是否隐藏底部进度条
- 20.5 是否在loadingView上显示网速
- 20.6 自定义loadingView
- 20.7 调整边距
- 20.8 取消控制层上下视图的阴影
以下为详细介绍:
_player = [SJVideoPlayer player];
_player.view.frame = ...;
[self.view addSubview:_player.view];
// 设置资源进行播放
SJVideoPlayerURLAsset *asset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL];
_player.URLAsset = asset;
在普通视图中播放时, 不需要指定视图层次, 直接创建资源进行播放即可.
由于 UITableView 及 UICollectionView 的复用机制, 会导致播放器视图显示在错误的位置上, 为防止出现此种情况, 在创建资源时指定视图层次结构, 使得播放器能够定位具体的父视图, 依此来控制隐藏与显示.
-- UITableView
-- UITableViewCell
-- Player.superview
-- Player.view
_player = [SJVideoPlayer player];
UIView *playerSuperview = cell.coverImageView;
SJPlayModel *playModel = [SJPlayModel UITableViewCellPlayModelWithPlayerSuperviewTag:playerSuperview.tag atIndexPath:indexPath tableView:self.tableView];
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL playModel:playModel];
在 UITableViewCell 中播放时, 需指定 Cell 所处的 indexPath 以及播放器父视图的 tag.
在滑动时, 管理类将会通过这两个参数控制播放器视图的显示与隐藏.
-- UITableView
-- UITableView.tableHeaderView 或者 UITableView.tableFooterView
-- Player.superview
-- Player.view
UIView *playerSuperview = self.tableView.tableHeaderView;
// 也可以设置子视图
// playerSuperview = self.tableView.tableHeaderView.coverImageView;
SJPlayModel *playModel = [SJPlayModel UITableViewHeaderViewPlayModelWithPlayerSuperview:playerSuperview tableView:self.tableView];
-- UITableView
-- UITableView.tableHeaderView 或者 UITableView.tableFooterView
-- Player.superview
-- Player.view
UIView *playerSuperview = self.tableView.tableFooterView;
// 也可以设置子视图
// playerSuperview = self.tableView.tableFooterView.coverImageView;
SJPlayModel *playModel = [SJPlayModel UITableViewHeaderViewPlayModelWithPlayerSuperview:playerSuperview tableView:self.tableView];
-- 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];
在 UICollectionView 中播放时, 同 UITableView 中一样, 需指定视图层次, 使得播放器能够定位具体的父视图, 依此来控制隐藏与显示.
-- UICollectionView
-- UICollectionViewCell
-- Player.superview
-- Player.view
SJPlayModel *playModel = [SJPlayModel UICollectionViewCellPlayModelWithPlayerSuperviewTag:cell.coverImageView.tag atIndexPath:indexPath collectionView:self.collectionView];
嵌套的情况下, 传递的参数比较多, 不过熟悉了前面的套路, 下面的这些也不成问题. (会被复用的视图, 传 tag. 如果不会被复用, 则直接传视图)
-- 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];
-- 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];
-- 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];
播放器 播放的资源是通过 SJVideoPlayerURLAsset 创建的. SJVideoPlayerURLAsset 由两部分组成:
视图层次 (第一部分中的SJPlayModel)
资源地址 (可以是本地资源/URL/AVAsset)
默认情况下, 创建了 SJVideoPlayerURLAsset , 赋值给播放器后即可播放.
NSURL *URL = [NSURL URLWithString:@"https://...example.mp4"];
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL];
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithAVAsset:avAsset];
NSTimeInterval secs = 20.0;
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL specifyStartTime:secs]; // 直接从20秒处开始播放
我们可能需要切换界面时, 希望视频能够在下一个界面无缝的进行播放. 使用如下方法, 传入正在播放的资源, 将新的资源赋值给播放器播放即可.
// otherAsset 即为上一个页面播放的Asset
// 除了需要一个otherAsset, 其他方面同以上的示例一模一样
_player.URLAsset = [SJVideoPlayerURLAsset.alloc initWithOtherAsset:otherAsset];
// 每个资源dealloc时的回调
_player.assetDeallocExeBlock = ^(__kindof SJBaseVideoPlayer * _Nonnull videoPlayer) {
// .....
};
当资源销毁时, 播放器将会回调该 block.
大多数对播放进行的操作, 均在协议 SJMediaPlaybackController 进行了声明.
正常来说实现了此协议的任何对象, 均可赋值给 player.playbackController 来替换原始实现.
[_player play];
[_player pause];
此时当用户点击刷新按钮, 我们需要对当前的资源(Asset)进行刷新.
SJBaseVideoPlayer提供了直接的方法去刷新, 不需要开发者再重复的去创建新的Asset.
[_player refresh];
[_player replay];
[_player stop];
_player.muted = YES;
// 默认值为 1.0
_player.rate = 1.0;
当播放发生错误时, 可以通过它来获取错误信息
_player.error
///
/// 是否精确跳转, default value is NO.
///
@property (nonatomic) BOOL accurateSeeking;
///
/// 跳转到指定位置播放
///
- (void)seekToTime:(NSTimeInterval)secs completionHandler:(void (^ __nullable)(BOOL finished))completionHandler;
- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^ __nullable)(BOOL finished))completionHandler;
///
/// 切换清晰度
///
- (void)switchVideoDefinition:(SJVideoPlayerURLAsset *)URLAsset;
///
/// 当前清晰度切换的信息
///
@property (nonatomic, strong, readonly) SJVideoDefinitionSwitchingInfo *definitionSwitchingInfo;
/// 以下为设置 SJVideoPlayer.definitionURLAssets, 将会在清晰度切换控制层中显示这些资源项.
SJVideoPlayerURLAsset *asset1 = [[SJVideoPlayerURLAsset alloc] initWithURL:VideoURL_Level4];
asset1.definition_fullName = @"超清 1080P";
asset1.definition_lastName = @"超清";
SJVideoPlayerURLAsset *asset2 = [[SJVideoPlayerURLAsset alloc] initWithURL:VideoURL_Level3];
asset2.definition_fullName = @"高清 720P";
asset2.definition_lastName = @"AAAAAAA";
SJVideoPlayerURLAsset *asset3 = [[SJVideoPlayerURLAsset alloc] initWithURL:VideoURL_Level2];
asset3.definition_fullName = @"清晰 480P";
asset3.definition_lastName = @"480P";
_player.definitionURLAssets = @[asset1, asset2, asset3];
// 先播放asset1. (asset2 和 asset3 将会在用户选择后进行切换)
_player.URLAsset = asset1;
@property (nonatomic, readonly) NSTimeInterval currentTime; ///< 当前播放到的时间
@property (nonatomic, readonly) NSTimeInterval duration; ///< 总时长
@property (nonatomic, readonly) NSTimeInterval playableDuration; ///< 缓冲到的时间
@property (nonatomic, readonly) BOOL isPlayedToEndTime; ///< 当前资源是否已播放结束
@property (nonatomic, readonly) BOOL isPlayed; ///< 是否播放过当前的资源
@property (nonatomic, readonly) BOOL isReplayed; ///< 是否重播过当前的资源
@property (nonatomic) BOOL autoplayWhenSetNewAsset; ///< 设置新的资源后, 是否自动调用播放. 默认为 YES
当您想在后台播放视频时:
-
需要设置 videoPlayer.pauseWhenAppDidEnterBackground = NO; (该值默认为YES, 即App进入后台默认暂停).
-
前往
TARGETS
->Capability
-> enableBackground Modes
-> select this modeAudio, AirPlay, and Picture in Picture
_player.pauseWhenAppDidEnterBackground = NO; // 默认值为 YES, 即进入后台后 暂停.
@property (nonatomic) BOOL resumePlaybackWhenAppDidEnterForeground; ///< 进入前台时, 是否恢复播放. 默认为 NO
@property (nonatomic) BOOL resumePlaybackWhenPlayerHasFinishedSeeking; ///< 当`seekToTime:`操作完成后, 是否恢复播放. 默认为 YES
资源准备(或初始化)的状态
当未设置资源时, 此时 player.assetStatus = .unknown
当设置新资源时, 此时 player.assetStatus = .preparing
当准备好播放时, 此时 player.assetStatus = .readyToPlay
当初始化失败时, 此时 player.assetStatus = .failed
typedef NS_ENUM(NSInteger, SJAssetStatus) {
///
/// 未知状态
///
SJAssetStatusUnknown,
///
/// 准备中
///
SJAssetStatusPreparing,
///
/// 当前资源可随时进行播放(播放控制请查看`timeControlStatus`)
///
SJAssetStatusReadyToPlay,
///
/// 发生错误
///
SJAssetStatusFailed
};
暂停或播放的控制状态
当调用了暂停时, 此时 player.timeControlStatus = .paused
当调用了播放时, 此时 将可能处于以下两种状态中的任意一个:
-
player.timeControlStatus = .playing
正在播放中. -
player.timeControlStatus = .waitingToPlay
等待播放, 等待的原因请查看 player.reasonForWaitingToPlay
typedef NS_ENUM(NSInteger, SJPlaybackTimeControlStatus) {
///
/// 暂停状态(已调用暂停或未执行任何操作的状态)
///
SJPlaybackTimeControlStatusPaused,
///
/// 播放状态(已调用播放), 当前正在缓冲或正在评估能否播放. 可以通过`reasonForWaitingToPlay`来获取原因, UI层可以根据原因来控制loading视图的状态.
///
SJPlaybackTimeControlStatusWaitingToPlay,
///
/// 播放状态(已调用播放), 当前播放器正在播放
///
SJPlaybackTimeControlStatusPlaying
};
当调用了播放, 播放器未能播放处于等待状态时的原因
等待原因有以下3种状态:
1.未设置资源, 此时设置资源后, 当player.assetStatus = .readyToPlay
, 播放器将自动进行播放.
2.可能是由于缓冲不足, 播放器在等待缓存足够时自动恢复播放, 此时可以显示loading视图.
3.可能是正在评估缓冲中, 这个过程会进行的很快, 不需要显示loading视图.
///
/// 缓冲中, UI层建议显示loading视图
///
extern SJWaitingReason const SJWaitingToMinimizeStallsReason;
///
/// 正在评估能否播放, 处于此状态时, 不建议UI层显示loading视图
///
extern SJWaitingReason const SJWaitingWhileEvaluatingBufferingRateReason;
///
/// 未设置资源
///
extern SJWaitingReason const SJWaitingWithNoAssetToPlayReason;
///
/// 观察者
///
/// 可以如下设置block, 来监听某个状态的改变
/// 了解更多请前往头文件查看
/// player.playbackObserver.currentTimeDidChangeExeBlock = ...;
/// player.playbackObserver.durationDidChangeExeBlock = ...;
/// player.playbackObserver.timeControlStatusDidChangeExeBlock = ...;
///
@property (nonatomic, strong, readonly) SJPlaybackObservation *playbackObserver;
@property (nonatomic, readonly) NSTimeInterval durationWatched; ///< 已观看的时长(当前资源)
这个时候, 我们可以自己动手, 将第三方的SDK封装一下, 实现 SJVideoPlayerPlaybackController 协议, 管理 SJBaseVideoPlayer 中的播放操作.
示例:
- 可以参考 SJAVMediaPlaybackController 中的实现.
- 封装 ijkplayer 的示例: https://gitee.com/changsanjiang/SJIJKMediaPlaybackController
_player.playbackController = Your PlaybackController.
controlLayerAppearManager 内部存在一个定时器, 当控制层显示时, 会开启此定时器. 一定间隔后, 会尝试隐藏控制层.
其他相关操作, 请见以下内容.
[_player controlLayerNeedAppear];
此方法将会回调控制层的代理方法:
"- (void)controlLayerNeedAppear:(__kindof SJBaseVideoPlayer *)videoPlayer;"
代理将会对当前的控制层进行显示处理.
[_player controlLayerNeedDisappear];
此方法将会回调控制层的代理方法:
"- (void)controlLayerNeedDisappear:(__kindof SJBaseVideoPlayer *)videoPlayer;"
代理将会对当前的控制层进行隐藏处理.
///
/// 控制层的显示状态(是否已显示)
///
@property (nonatomic, getter=isControlLayerAppeared) BOOL controlLayerAppeared;
///
/// 暂停的时候是否保持控制层显示
///
/// default value is NO
///
@property (nonatomic) BOOL pausedToKeepAppearState;
///
/// 观察者
///
/// 当需要监听控制层的显示和隐藏时, 可以设置`player.controlLayerAppearObserver.appearStateDidChangeExeBlock = ...;`
///
@property (nonatomic, strong, readonly) id<SJControlLayerAppearManagerObserver> controlLayerAppearObserver;
_player.controlLayerAppearManager = Your controlLayerAppearManager;
// 0 到 1
_player.deviceVolumeAndBrightnessManager.brightness = 1.0;
// 0 到 1
_player.deviceVolumeAndBrightnessManager.volume = 1.0;
///
/// 观察者
///
@property (nonatomic, strong, readonly) id<SJDeviceVolumeAndBrightnessManagerObserver> deviceVolumeAndBrightnessObserver;
_player.disableBrightnessSetting = YES;
_player.disableVolumeSetting = YES;
_player.deviceVolumeAndBrightnessManager = Your deviceVolumeAndBrightnessManager;
对于旋转, 我们开发者肯定需要绝对的控制, 例如: 设置自动旋转所支持方向. 能够主动+自动旋转, 而且还需要能在适当的时候禁止自动旋转. 旋转前后的回调等等... 放心这些功能都有, 我挨个给大家介绍.
具体请看下面介绍.
请查看该指南: https://github.com/changsanjiang/SJVideoPlayer/wiki/Getting-Started#configure-rotation
/// 设置自动旋转支持的方向
_player.supportedOrientations = SJOrientationMaskLandscapeLeft