基于AVPlayer.
极速初始化, 不阻塞主线程.
这个应该是目前基于AVPlayer的播放器中, 功能最全的一个吧.
使用
pod 'SJVideoPlayer' 复制代码
功能
- 支持
- 支持
- 支持
- 支持
- 支持
- 支持
- 支持
- 支持
- 支持
- 本地化处理.
- 可以
只需pod到项目中即可带全屏手势
为啥自定义手势?
在使用原生手势返回时, 当前播放的视频会出现卡帧的问题. 原因我不太清楚. 我查看了腾讯视频和爱奇艺等App均为自己实现的手势返回.
当时使用了一个全屏返回的三方库. 是截屏返回的, 发现存在两个问题: 一是截屏耗时, 二是视频部分截取不了(黑色的) 由于我们起步8.0, 我便使用了新的APIsnapshotViewAfterScreenUpdates:
(这个接口其实7.0就有了)来替换截屏的方式, 直接使用视图快照, 这个接口一举两得, 不仅速度快, 而且视频播放也可以截取到. 但是快照是一个view, 并不是image, 原来的三方库截屏方式无法使用, 于是我重新撸了一个全屏手势.
手势使用
pod 'SJFullscreenPopGesture'复制代码
手势功能介绍
// default is `SJFullscreenPopGestureType_EdgeLeft`.typedef NS_ENUM(NSUInteger, SJFullscreenPopGestureType) { SJFullscreenPopGestureType_EdgeLeft, // 默认, 屏幕左边缘触发手势 SJFullscreenPopGestureType_Full, // 全屏触发手势};复制代码
-
目前有两种:
/// 将要拖拽@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);复制代码
- 等 ...
网络状态切换提示
网络状态提示就是当网络状态变更时, 播放器显示一行字或者图片等, 提示用户网络变更了.
对于默认提示的内容(可自定义), 我做了本地化处理, 根据 localLanguage 自动选择一种语言提示(中文/繁体/英文), 如下:
关于旋转我想吐槽一下: 大部分三方库的旋转是直接把播放器添加到window的中心, 再做一个transform动画.. . 旋转过程中, 界面会闪一下, 这个体验几次眼就会累... 为了解决这个问题, 我又写了一个在添加之前, 我做了相应的坐标转换, 使播放器添加到window上时, 还处于原始位置. 然后去做transform动画并更新bounds和center.
播放器除了自动旋转之外, 您可以随意的控制旋转. 支持的方向如下:
/// 竖屏 || (左右)横屏/// Auto rotate supported orientationtypedef 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);复制代码
这几个功能都已封装在默认的控制层中, 并且相应的提示文本也做了本地化处理
方法如下:- (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.
-
SingleTap 单击手势 当用户单击播放器时, 播放器会调用显示或隐藏控制层的相关代理方法. 见
controlLayerDelegate
-
DoubleTap 双击手势 双击会触发暂停或播放的操作
-
Pan 移动手势 当用户水平滑动时, 会触发控制层相应的代理方法. 见
controlLayerDelegate
当用户垂直滑动时, 如果在屏幕左边, 则会触发调整亮度的操作, 并显示亮度提示视图. 如果在屏幕右边, 则会触发调整声音的操作, 并显示系统音量提示视图 -
Pinch 捏合手势 当用户做放大或收缩触发该手势时, 会设置播放器显示模式
Aspect
或AspectFill
.
关于自定义控制层的相关协议
下面介绍如何自定义一个控制层, 相关的方法后面, 我都会跟上一个相应实现.
数据源介绍
@protocol SJVideoPlayerControlLayerDataSource@required- (UIView *)controlView;- (BOOL)controlLayerDisappearCondition;- (BOOL)triggerGesturesCondition:(CGPoint)location;@optional- (void)installedControlViewToVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;@end复制代码
- (UIView *)controlView;
- (BOOL)controlLayerDisappearCondition;
- (BOOL)triggerGesturesCondition:(CGPoint)location;
代理介绍
控制层的显示与隐藏
@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:
.
controlLayerNeedAppear:
controlLayerNeedDisappear:
在UITableView或者CollectionView中播放时的代理回调
- (void)videoPlayerWillAppearInScrollView:(__kindof SJBaseVideoPlayer *)videoPlayer;- (void)videoPlayerWillDisappearInScrollView:(__kindof SJBaseVideoPlayer *)videoPlayer;复制代码
-
videoPlayerWillAppearInScrollView:
-
videoPlayerWillDisappearInScrollView:
准备播放一个资源时以及播放状态/播放进度的回调
- (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;复制代码
videoPlayer:prepareToPlay:
videoPlayer:stateChanged:
第三个方法
缓存的状态回调
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer loadedTimeProgress:(float)progress;- (void)startLoading:(__kindof SJBaseVideoPlayer *)videoPlayer;- (void)cancelLoading:(__kindof SJBaseVideoPlayer *)videoPlayer;- (void)loadCompletion:(__kindof SJBaseVideoPlayer *)videoPlayer;复制代码
videoPlayer:loadedTimeProgress:
startLoading:
cancelLoading:
loadCompletion:
锁屏的回调以及一个只有在锁屏状态下才触发的单击手势
- (void)lockedVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;- (void)unlockedVideoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer;- (void)tappedPlayerOnTheLockedState:(__kindof SJBaseVideoPlayer *)videoPlayer;复制代码
lockedVideoPlayer:
unlockedVideoPlayer:
tappedPlayerOnTheLockedState:
屏幕旋转的回调
- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer willRotateView:(BOOL)isFull;- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer didEndRotation:(BOOL)isFull;复制代码
videoPlayer:willRotateView:
videoPlayer:didEndRotation:
静音 / 音量 / 亮度 / 调速的回调
- (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;复制代码
videoPlayer:muteChanged:
videoPlayer:volumeChanged:
videoPlayer:brightnessChanged:
videoPlayer:rateChanged:
水平方向手势触发的回调
/// 水平方向开始拖动.- (void)horizontalDirectionWillBeginDragging:(__kindof SJBaseVideoPlayer *)videoPlayer;/** @param progress drag progress */- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer horizontalDirectionDidMove:(CGFloat)progress;/// 水平方向拖动结束.- (void)horizontalDirectionDidEndDragging:(__kindof SJBaseVideoPlayer *)videoPlayer;复制代码
horizontalDirectionWillBeginDragging:
videoPlayer:horizontalDirectionDidMove:
horizontalDirectionDidEndDragging:
Network
/// 网络状态变更- (void)videoPlayer:(__kindof SJBaseVideoPlayer *)videoPlayer reachabilityChanged:(SJNetworkStatus)status;复制代码
videoPlayer:reachabilityChanged:
Demo
项目地址: https://github.com/changsanjiang/SJVideoPlayer
我的邮箱: changsanjiang@gmail.com
如果您有什么建议, 望请联系我!