Skip to content

iOS VideoPlayer MediaPlayer video player media player.

License

Notifications You must be signed in to change notification settings

877209649/SJVideoPlayer

 
 

Repository files navigation

readme

Build Status Version Platform License

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/AttributesFactory', :git => 'https://gitee.com/changsanjiang/SJUIKit.git'
pod 'SJUIKit/ObserverHelper', :git => 'https://gitee.com/changsanjiang/SJUIKit.git'
pod 'SJUIKit/Queues', :git => 'https://gitee.com/changsanjiang/SJUIKit.git'
$ pod update --no-repo-update   (不要用 pod install , 用这个命令安装)

AVPlayer 切换为 ijkplayer, 其他功能不变

  • 改成以下方式重新安装
pod 'SJBaseVideoPlayer/IJKPlayer'
  • 使用
// 导入头文件
#import "SJIJKMediaPlaybackController.h"

_player = SJVideoPlayer.player;
// 将播放控制切换为 SJIJKMediaPlaybackController 即可, 其它操作不变
_player.playbackController = SJIJKMediaPlaybackController.new;

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: [email protected]

QQGroup: 930508201 (iOS 开发)

大佬辛苦, 犒赏一下🤕

Documents


以下为详细介绍:

1.1 UIView

_player = [SJVideoPlayer player];
_player.view.frame = ...;
[self.view addSubview:_player.view];

// 设置资源进行播放
SJVideoPlayerURLAsset *asset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL];
_player.URLAsset = asset;

在普通视图中播放时, 不需要指定视图层次, 直接创建资源进行播放即可.


1.2 UITableView 中的层次结构

由于 UITableView 及 UICollectionView 的复用机制, 会导致播放器视图显示在错误的位置上, 为防止出现此种情况, 在创建资源时指定视图层次结构, 使得播放器能够定位具体的父视图, 依此来控制隐藏与显示.


1.2.1 UITableViewCell

--  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.

在滑动时, 管理类将会通过这两个参数控制播放器父视图的显示与隐藏.


1.2.2 UITableView.tableHeaderView

--  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];

1.2.3 UITableView.tableFooterView

--  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];

1.2.4 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];

1.3 UICollectionView 中的层次结构

在 UICollectionView 中播放时, 同 UITableView 中一样, 需指定视图层次, 使得播放器能够定位具体的父视图, 依此来控制隐藏与显示.


1.3.1 UICollectionViewCell

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

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

1.4 嵌套时的视图层次

嵌套的情况下, 传递的参数比较多, 不过熟悉了前面的套路, 下面的这些也不成问题. (会被复用的视图, 传 tag. 如果不会被复用, 则直接传视图)


1.4.1 UICollectionView 嵌套在 UITableViewCell 中

--  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.4.2 UICollectionView 嵌套在 UITableViewHeaderView 中

--  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.4.3 UICollectionView 嵌套在 UICollectionViewCell 中

--  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];

2. URLAsset

播放器 播放的资源是通过 SJVideoPlayerURLAsset 创建的. SJVideoPlayerURLAsset 由两部分组成:

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

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


2.1 播放 URL(本地文件或远程资源)

NSURL *URL = [NSURL URLWithString:@"https://...example.mp4"];
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL];

2.2 播放 AVAsset 或其子类

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

2.3 从指定的位置开始播放

NSTimeInterval secs = 20.0;
_player.URLAsset = [[SJVideoPlayerURLAsset alloc] initWithURL:URL specifyStartTime:secs]; // 直接从20秒处开始播放

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

我们可能需要切换界面时, 希望视频能够在下一个界面无缝的进行播放. 使用如下方法, 传入正在播放的资源, 将新的资源赋值给播放器播放即可.

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

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

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

当资源销毁时, 播放器将会回调该 block.


3. 播放控制

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

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

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

3.1 播放

[_player play];

3.2 暂停

[_player pause];

3.3 刷新

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

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

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

[_player refresh];

3.4 重播

从头开始重新播放

[_player replay];

3.5 停止

停止播放, 请注意: 当前资源将会被清空, 如需重播, 请重新设置新资源

[_player stop];

3.6 静音

_player.muted = YES;

3.7 调速

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

3.8 报错

当播放发生错误时, 可以通过它来获取错误信息

_player.error

3.9 跳转

///
/// 是否精确跳转, 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;

3.10 切换清晰度

///
/// 切换清晰度
///
- (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;

3.11 当前时间

@property (nonatomic, readonly) NSTimeInterval currentTime;                         ///< 当前播放到的时间

3.12 总时长

@property (nonatomic, readonly) NSTimeInterval duration;                            ///< 总时长

3.13 缓冲时长

@property (nonatomic, readonly) NSTimeInterval playableDuration;                    ///< 缓冲到的时间

3.14 是否已播放完毕

@property (nonatomic, readonly) BOOL isPlayedToEndTime;                             ///< 当前资源是否已播放结束

3.15 是否调用过播放

@property (nonatomic, readonly) BOOL isPlayed;                                      ///< 是否播放过当前的资源

3.16 是否调用过重播

@property (nonatomic, readonly) BOOL isReplayed;                                    ///< 是否重播过当前的资源

3.17 设置新资源时, 是否自动播放

@property (nonatomic) BOOL autoplayWhenSetNewAsset;                    ///< 设置新的资源后, 是否自动调用播放. 默认为 YES

3.18 进入后台, 是否暂停播放

关于后台播放视频, 引用自: 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.19 进入前台, 是否恢复播放

@property (nonatomic) BOOL resumePlaybackWhenAppDidEnterForeground;    ///< 进入前台时, 是否恢复播放. 默认为 NO

3.20 跳转完成, 是否恢复播放

@property (nonatomic) BOOL resumePlaybackWhenPlayerHasFinishedSeeking; ///< 当`seekToTime:`操作完成后, 是否恢复播放. 默认为 YES

3.21 资源准备状态

资源准备(或初始化)的状态

当未设置资源时, 此时 player.assetStatus = .unknown 当设置新资源时, 此时 player.assetStatus = .preparing 当准备好播放时, 此时 player.assetStatus = .readyToPlay 当初始化失败时, 此时 player.assetStatus = .failed

typedef NS_ENUM(NSInteger, SJAssetStatus) {
///
/// 未知状态
///
SJAssetStatusUnknown,

///
/// 准备中
///
SJAssetStatusPreparing,

///
/// 当前资源可随时进行播放(播放控制请查看`timeControlStatus`)
///
SJAssetStatusReadyToPlay,

///
/// 发生错误
///
SJAssetStatusFailed
};

3.22 播放控制状态

暂停或播放的控制状态

当调用了暂停时, 此时 player.timeControlStatus = .paused

当调用了播放时, 此时 将可能处于以下两种状态中的任意一个:

  • player.timeControlStatus = .playing 正在播放中.

  • player.timeControlStatus = .waitingToPlay 等待播放, 等待的原因请查看 player.reasonForWaitingToPlay

typedef NS_ENUM(NSInteger, SJPlaybackTimeControlStatus) {
///
/// 暂停状态(已调用暂停或未执行任何操作的状态)
///
SJPlaybackTimeControlStatusPaused,

///
/// 播放状态(已调用播放), 当前正在缓冲或正在评估能否播放. 可以通过`reasonForWaitingToPlay`来获取原因, UI层可以根据原因来控制loading视图的状态.
///
SJPlaybackTimeControlStatusWaitingToPlay,

///
/// 播放状态(已调用播放), 当前播放器正在播放
///
SJPlaybackTimeControlStatusPlaying
};

3.23 播放等待的原因

当调用了播放, 播放器未能播放处于等待状态时的原因

等待原因有以下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;

3.24 监听状态改变🔥

///
/// 观察者
///
///         可以如下设置block, 来监听某个状态的改变
///         了解更多请前往头文件查看
///         player.playbackObserver.currentTimeDidChangeExeBlock = ...;
///         player.playbackObserver.durationDidChangeExeBlock = ...;
///         player.playbackObserver.timeControlStatusDidChangeExeBlock = ...;
///
@property (nonatomic, strong, readonly) SJPlaybackObservation *playbackObserver;

3.25 已观看的时长(当前资源)

@property (nonatomic, readonly) NSTimeInterval durationWatched;                     ///< 已观看的时长(当前资源)

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

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

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

示例:

_player.playbackController = Your PlaybackController.

4. 控制层的显示和隐藏

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

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

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

4.1 让控制层显示

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

[_player controlLayerNeedAppear];

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

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

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

4.2 让控制层隐藏

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

[_player controlLayerNeedDisappear];

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

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

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

4.3 控制层是否显示中

///
/// 控制层的显示状态(是否已显示)
///
@property (nonatomic, getter=isControlLayerAppeared) BOOL controlLayerAppeared;

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

///
/// 暂停的时候是否保持控制层显示
///
///         default value is NO
///
@property (nonatomic) BOOL pausedToKeepAppearState;

4.5 监听状态改变🔥

///
/// 观察者
///
///         当需要监听控制层的显示和隐藏时, 可以设置`player.controlLayerAppearObserver.appearStateDidChangeExeBlock = ...;`
///
@property (nonatomic, strong, readonly) id<SJControlLayerAppearManagerObserver> controlLayerAppearObserver;

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

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

_player.controlLayerAppearManager = Your controlLayerAppearManager; 

5. 设备亮度和音量

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

5.1 调整设备亮度

// 0 到 1
_player.deviceVolumeAndBrightnessManager.brightness = 1.0;

5.2 调整设备声音

// 0 到 1
_player.deviceVolumeAndBrightnessManager.volume = 1.0;

5.3 监听状态改变🔥

///
/// 观察者
///
@property (nonatomic, strong, readonly) id<SJDeviceVolumeAndBrightnessManagerObserver> deviceVolumeAndBrightnessObserver;

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.supportedOrientations = SJOrientationMaskLandscapeLeft | SJOrientationMaskLandscapeRight;


/**
自动旋转支持的方向

- SJOrientationMaskPortrait:       竖屏
- SJOrientationMaskLandscapeLeft:  支持全屏, Home键在右侧
- SJOrientationMaskLandscapeRight: 支持全屏, Home键在左侧
- SJOrientationMaskAll:            全部方向
*/
typedef enum : NSUInteger {
SJOrientationMaskPortrait = 1 << SJOrientation_Portrait,
SJOrientationMaskLandscapeLeft = 1 << SJOrientation_LandscapeLeft,
SJOrientationMaskLandscapeRight = 1 << SJOrientation_LandscapeRight,
SJOrientationMaskAll = SJOrientationMaskPortrait | SJOrientationMaskLandscapeLeft | SJOrientationMaskLandscapeRight,
} SJOrientationMask;

6.3 禁止自动旋转

这里有两点需要注意:

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

_player.rotationManager.disabledAutorotation = 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, 表示全屏
@property (nonatomic, readonly) BOOL isFullScreen;                              ///< 是否已全屏

6.6 是否正在旋转

/// 如果为YES, 表示正在旋转中
@property (nonatomic, readonly) BOOL isTransitioning;

6.7 当前旋转的方向

_player.rotationManager.currentOrientation

6.8 监听状态改变🔥

///
/// 观察者
///
///         当需要监听旋转时, 可以设置`player.rotationObserver.rotationDidStartExeBlock = ...;`
///         了解更多请前往头文件查看
///
@property (nonatomic, strong, readonly) id<SJRotationManagerObserver> rotationObserver;

6.9 自己动手撸一个 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 监听状态改变🔥

///
/// 观察者
///
///         可以如下设置block, 来监听某个状态的改变
///
///         player.flipTransitionObserver.flipTransitionDidStartExeBlock = ...;
///         player.flipTransitionObserver.flipTransitionDidStopExeBlock = ...;
///
@property (nonatomic, strong, readonly) id<SJFlipTransitionManagerObserver> flipTransitionObserver;

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

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


9. 网络状态

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

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

9.1 当前的网络状态

@property (nonatomic, readonly) SJNetworkStatus networkStatus;

9.2 监听状态改变🔥

///
/// 观察者
///
@property (nonatomic, strong, readonly) id<SJReachabilityObserver> reachabilityObserver;

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.gestureControl.supportedGestureTypes = SJPlayerGestureTypeMask_All

typedef enum : NSUInteger {
SJPlayerGestureTypeMask_None,
SJPlayerGestureTypeMask_SingleTap = 1 << 0,
SJPlayerGestureTypeMask_DoubleTap = 1 << 1,
SJPlayerGestureTypeMask_Pan_H = 1 << 2, // 水平方向
SJPlayerGestureTypeMask_Pan_V = 1 << 3, // 垂直方向
SJPlayerGestureTypeMask_Pinch = 1 << 4,
SJPlayerGestureTypeMask_Pan = SJPlayerGestureTypeMask_Pan_H | SJPlayerGestureTypeMask_Pan_V,
SJPlayerGestureTypeMask_All = SJPlayerGestureTypeMask_SingleTap |
SJPlayerGestureTypeMask_DoubleTap |
SJPlayerGestureTypeMask_Pan |
SJPlayerGestureTypeMask_Pinch,
} SJPlayerGestureTypeMask;

10.6 自定义某个手势的处理

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

11. 占位图

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

11.1 设置本地占位图

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

11.2 设置网络占位图

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

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

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

12. 显示提示文本

目前仅支持 NSAttributedString.

12.1 显示管理类

///
/// 中心弹出文本提示
///
///         了解更多请前往协议头文件查看
///
@property (nonatomic, strong, null_resettable) id<SJPromptProtocol> prompt;

///
/// 左下角弹出提示
///
///         了解更多请前往协议头文件查看
///
@property (nonatomic, strong, null_resettable) id<SJPopPromptControllerProtocol> popPromptController;

12.2 配置提示文本

_player.prompt.backgroundColor = ...;
_player.prompt.contentInset = ...;

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.isTransitioning ) { // 旋转时, 不显示
if ( !self.disabledControlLayerAppearManager && self.isControlLayerAppeared ) return NO;
return YES;
}
// 全屏播放时, 使状态栏根据控制层显示或隐藏
if ( self.isFullScreen ) return !self.isControlLayerAppeared;
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;

15. 导出视频或GIF

15.1 导出视频

- (void)exportWithBeginTime:(NSTimeInterval)beginTime
duration:(NSTimeInterval)duration
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

- (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 是否已显示

///
/// 播放器视图是否显示
///
/// Whether the player is appeared when playing on scrollView. Because scrollview may be scrolled.
///
@property (nonatomic, readonly) BOOL isScrollAppeared;

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

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

16.4 滚动出去后, 是否暂停

///
/// 滚动出去后, 是否暂停. 默认为YES
///
/// - default value is YES.
///
@property (nonatomic) BOOL pauseWhenScrollDisappeared;

16.5 滚动进入时, 是否恢复播放

///
/// 滚动进入时, 是否恢复播放. 默认为YES
///
/// - default values is YES.
///
@property (nonatomic) BOOL resumePlaybackWhenScrollAppeared;

16.6 滚动出去后, 是否隐藏播放器视图

///
/// 滚动出去后, 是否隐藏播放器视图. 默认为YES
///
/// - default value is YES.
///
@property (nonatomic) BOOL hiddenViewWhenScrollDisappeared;

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. 对控制层上面的Item的操作

18.1 添加

SJEdgeControlButtonItem *item = [[SJEdgeControlButtonItem alloc] initWithImage:[UIImage imageNamed:@"test"] target:self action:@selector(test) tag:SJTestImageItemTag];
[_player.defaultEdgeControlLayer.topAdapter addItem:item];
[_player.defaultEdgeControlLayer.topAdapter reload];

18.2 删除

[_player.defaultEdgeControlLayer.bottomAdapter removeItemForTag:SJEdgeControlLayerBottomItem_Separator];
[_player.defaultEdgeControlLayer.bottomAdapter reload];

18.3 调整位置

[_player.defaultEdgeControlLayer.bottomAdapter exchangeItemForTag:SJEdgeControlLayerBottomItem_DurationTime withItemForTag:SJEdgeControlLayerBottomItem_Progress];
[_player.defaultEdgeControlLayer.bottomAdapter reload];

19. 对控制层上的Item的一些补充

19.1 设置与前后item的间距

SJEdgeControlButtonItem *titleItem = [_player.defaultEdgeControlLayer.topAdapter itemForTag:SJEdgeControlLayerTopItem_Title];
titleItem.insets = SJEdgeInsetsMake(16, 16);
[_player.defaultEdgeControlLayer.topAdapter reload];

19.2 设置隐藏

SJEdgeControlButtonItem *titleItem = [_player.defaultEdgeControlLayer.topAdapter itemForTag:SJEdgeControlLayerTopItem_Title];
titleItem.hidden = YES;
[_player.defaultEdgeControlLayer.topAdapter reload];

19.3 填充剩余空间

SJEdgeControlButtonItem *titleItem = [_player.defaultEdgeControlLayer.topAdapter itemForTag:SJEdgeControlLayerTopItem_Title];
titleItem.fill = YES;
[_player.defaultEdgeControlLayer.topAdapter reload];

20. SJEdgeControlLayer 的补充

20.1 是否竖屏时隐藏返回按钮

_player.defaultEdgeControlLayer.hiddenBackButtonWhenOrientationIsPortrait = YES;

20.2 是否禁止网络状态变化提示

_player.defaultEdgeControlLayer.disabledPromptWhenNetworkStatusChanges = YES;

20.3 是否使返回按钮常驻

_player.defaultEdgeControlLayer.showResidentBackButton = YES;

20.4 是否隐藏底部进度条

_player.defaultEdgeControlLayer.hiddenBottomProgressIndicator = YES;

20.5 是否在loadingView上显示网速

_player.defaultEdgeControlLayer.showNetworkSpeedToLoadingView = YES;

20.6 自定义loadingView

// 实现协议`SJEdgeControlLayerLoadingViewProtocol`即可, 然后赋值给控制层
_player.defaultEdgeControlLayer.loadingView = Your Loading View;

20.7 调整边距

_player.defaultEdgeControlLayer.leftMargin = 16;
_player.defaultEdgeControlLayer.rightMargin = 16;

20.8 取消控制层上下视图的阴影

[_player.defaultEdgeControlLayer.topContainerView cleanColors];
[_player.defaultEdgeControlLayer.bottomContainerView cleanColors];

赞助

如果对您有所帮助,欢迎您的赞赏

About

iOS VideoPlayer MediaPlayer video player media player.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Objective-C 98.1%
  • Ruby 1.2%
  • Other 0.7%