iOS 分享一个功能很全的视频播放器

田妈
• 阅读 14055

SJVideoPlayer

基于AVPlayer.

极速初始化, 不阻塞主线程.

这个应该是目前基于AVPlayer的播放器中, 功能最全的一个吧.
_

使用

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返回时的动画效果

目前有两种:

- [类似腾讯视频返回.gif](https://upload-images.jianshu.io/upload_images/2318691-d5a992c40cfee5bb.gif?imageMogr2/auto-orient/strip)
- [在1的基础上加上了一层遮罩.gif](https://upload-images.jianshu.io/upload_images/2318691-3dcd02f47b0dff4a.gif?imageMogr2/auto-orient/strip)
  1. 可在某个ViewController禁用手势
  2. 可兼容 WKWebView 手势返回
  3. 可设置盲区, 在这个区域不触发手势
  4. 可设置手势触发过程中的回调
/// 将要拖拽
@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

这几个功能都已封装在默认的控制层中, 并且相应的提示文本也做了本地化处理iOS 分享一个功能很全的视频播放器
方法如下:

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

  1. DoubleTap
    双击手势
    双击会触发暂停或播放的操作
  2. Pan
    移动手势
    当用户水平滑动时, 会触发控制层相应的代理方法. 见 controlLayerDelegate
    当用户垂直滑动时, 如果在屏幕左边, 则会触发调整亮度的操作, 并显示亮度提示视图. 如果在屏幕右边, 则会触发调整声音的操作, 并显示系统音量提示视图
  3. 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/changsanji...

我的邮箱: changsanjiang@gmail.com

如果您有什么建议, 望请联系我!

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
java中比较两个时间的差值
项目背景1.某篇文稿的发布时间是publishDate,例如:2020072118:00:41。2.现要求判断该篇文稿的发布时间是否在近30天之内。publicstaticlongdayDiff(DatecurrentDate,DatepublishDate){LongcurrentTimecurrentDat
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
JS 苹果手机日期显示NaN问题
问题描述newDate("2019122910:30:00")在IOS下显示为NaN原因分析带的日期IOS下存在兼容问题解决方法字符串替换letdateStr"2019122910:30:00";datedateStr.repl
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
C++笔记002:VS2010报错:LINK fatal error LNK1123 转换到 COFF 期间失败文件无效或损坏
 原创笔记,转载请注明出处!点击【关注】,关注也是一种美德~错误描述:1已启动生成:项目:FirstCode,配置:DebugWin321生成启动时间为2018/2/521:00:30。1InitializeBuildStatus:1 正在
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这