前言
最近看AVFoundation音视频类的东西,有了解到开源视频播放器ZFPlayer,在这里解读顺便了解学习一下作者的设计思路,如有不足或者错误,希望读者能够批评并指正。
这是作者对此框架的设计脑图。根据类名可以看出作者是按功能模块将之拆成了不同的类,这样结构比较清晰便于重用与组件化。
说明
用法作者在原文章有介绍用法原著,在这里只是解读研究源码与实现思路。
ZFPlayer 3.2.2 目前包含以下五个文件夹:
在这里我也根据使用方法与功能模块,分为上中下3篇介绍,本篇为上篇,主要介绍 Core文件夹下相关类。使用pod 'ZFPlayer', '~> 3.0'
安装即可。
Core文件夹中类与作用
安装成功后,所pod下来的文件是core文件夹,如下图:
就像作者所说,这只是一个播放器的壳子,如果你想完全自定义 播放器AVPlayer 与 控制视图AVPlayerControlView,这些文件就够了。但是,自定义的player需要严格遵循协议实现 ZFPlayerMediaPlayback
协议并实现,自定义的controlView需要遵循ZFPlayerMediaControl
协议,根据回调更新自定义的controlView。
既然player与controlView都自定义了,那还要此库有何用?此库帮我们做了什么?我们为什么还要继续使用此库?
-
ZFPlayerController
: 主类,所有的事件,回调视频,播放器,都由此获取。便于统一管理协作。
// 正常视频播放
+ (instancetype)playerWithPlayerManager:(id<ZFPlayerMediaPlayback>)playerManager containerView:(UIView *)containerView;
// 列表视频播放
+ (instancetype)playerWithScrollView:(UIScrollView *)scrollView playerManager:(id<ZFPlayerMediaPlayback>)playerManager containerViewTag:(NSInteger)containerViewTag;
ZFOrientationObserver
:封装了有关屏幕旋转,监听,布局更新。
视频播放时,我们可根据协议使用来自定义自己的业务与相关布局。ZFKVOController
: 更加安全的使用KVO ( 与播放器直接业务逻辑无关 ),避免在直接使用时带来的一些因多次过度释放导致程序crash的副作用。建议参考一下facebook早期的的开源库FBKVOControllerZFPlayerGestureControl
:多种手势管理的一个类,主要用于播放器的controlView,里面包含点击,双击,捏合,拖动等等,并优化了用户在使用播放器间这些手势间的之间的一些冲突。这些作者都已经帮我们完成并把手势识别的结果回调给我们,我们以此来操作控制层ControlView(UI)与播放器本身(逻辑)的业务如播放进度,播放器的暂停,播放,快进快退等等。
@property (nonatomic, copy, nullable) void(^singleTapped)(ZFPlayerGestureControl *control);
@property (nonatomic, copy, nullable) void(^doubleTapped)(ZFPlayerGestureControl *control);
@property (nonatomic, copy, nullable) void(^beganPan)(ZFPlayerGestureControl *control, ZFPanDirection direction, ZFPanLocation location);
@property (nonatomic, copy, nullable) void(^changedPan)(ZFPlayerGestureControl *control, ZFPanDirection direction, ZFPanLocation location, CGPoint velocity);
@property (nonatomic, copy, nullable) void(^endedPan)(ZFPlayerGestureControl *control, ZFPanDirection direction, ZFPanLocation location);
// 捏合
@property (nonatomic, copy, nullable) void(^pinched)(ZFPlayerGestureControl *control, float scale);
// 拖动方向,一开始是左右拖动的方向,或者是上下拖动的方向
@property (nonatomic, readonly) ZFPanDirection panDirection;
// 所处屏幕位置,如左边调节音量,右侧调整亮度
@property (nonatomic, readonly) ZFPanLocation panLocation;
@property (nonatomic, readonly) ZFPanMovingDirection panMovingDirection;
ZFPlayerLogManager
:日志类,根据配置环境可实时查看控制台的一些相关的播放信息便于调试,这里不多说。ZFPlayerNotification
: 播放器系统的相关通知监听:包括前后,耳机插拔,音量等,如果自定义,根据此回调处理自身业务,当然如果这些不满足,可自行再添加回调,只是不方便使用pod了。
// 将要失去焦点
@property (nonatomic, copy, nullable) void(^willResignActive)(ZFPlayerNotification *registrar);
// 已经变更为活跃
@property (nonatomic, copy, nullable) void(^didBecomeActive)(ZFPlayerNotification *registrar);
// 插入耳机
@property (nonatomic, copy, nullable) void(^newDeviceAvailable)(ZFPlayerNotification *registrar);
// 拔出耳机
@property (nonatomic, copy, nullable) void(^oldDeviceUnavailable)(ZFPlayerNotification *registrar);
// AVAudioSession对象的category发生改变时
@property (nonatomic, copy, nullable) void(^categoryChange)(ZFPlayerNotification *registrar);
// 音量改变
@property (nonatomic, copy, nullable) void(^volumeChanged)(float volume);
ZFPlayerView
: 基类,自己实现继承于此类,作者原码也是这样也实现的,后面会再写。ZFReachabilityManager
: 网络状态监听,同Reachability,AFNetworking...,因为库本身功能确实依赖网络状态,又避免使用者项目中可能会存在冲突,就为自己加了一个便一统一,也避免使用者麻烦。ZFFloatView
: 全局的小浮窗视图,可跟随手指的拖动而移动(UIPanGestureRecognizer),此类并无播放器相关业务。UIViewController+ZFPlayerRotation
: 方便控制当前的ViewController支持旋转及旋转的方向,里面重写的系统方法,有关屏幕旋转网上的解释也非常多,更多详情这里可以参考iOS 屏幕旋转。代码很固定,重写方法的字面意思也很好理解,用的时候根据自身业务与框架提供的布尔值即可,只是一个方向问题,难度不大,只是需要适配各种旋转情况。
注意:这几个方法只有当viewController是window的rootViewController或者viewController是present出来时,才会起作用!,所以如果想让各个viewController来控制自己的方向,重写UINavigationController,UITabbarController,UIViewController中固定的相关旋转的方法即可
- UITabbarController中重写以下方法
// 子控制器是否支持旋转
- (BOOL)shouldAutorotate {
UIViewController *vc = self.viewControllers[self.selectedIndex];
if ([vc isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)vc;
return [nav.topViewController shouldAutorotate];
} else {
return [vc shouldAutorotate];
}
}
// 子控制器支持旋转的所有方向,多选
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
UIViewController *vc = self.viewControllers[self.selectedIndex];
if ([vc isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)vc;
return [nav.topViewController supportedInterfaceOrientations];
} else {
return [vc supportedInterfaceOrientations];
}
}
// 子控制器优先支持的方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
UIViewController *vc = self.viewControllers[self.selectedIndex];
if ([vc isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)vc;
return [nav.topViewController preferredInterfaceOrientationForPresentation];
} else {
return [vc preferredInterfaceOrientationForPresentation];
}
}
- UINavigationController重写以下方法
// 同上
- (BOOL)shouldAutorotate {
return [self.topViewController shouldAutorotate];
}
// 同上
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations];
}
// 同上
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
-
UIScrollView+ZFPlayer
: 滚动中在可视范围内播放,类似微博首页中的视频。这个类传入列表视图,里面会自动根据偏移计算位置并在设定的位置播放,此类里面大多是关于坐标的计算与列表的偏移更新,但是需要使用者自己主动调用以下以下代理。
- (void)zf_scrollViewDidEndDecelerating;
- (void)zf_scrollViewDidEndDraggingWillDecelerate:(BOOL)decelerate;
- (void)zf_scrollViewDidScrollToTop;
- (void)zf_scrollViewDidScroll;
- (void)zf_scrollViewWillBeginDragging;
以上这些类是被定义在Core文件夹下,然而并没有丝毫有关播放器与控制视图的任务业务逻辑。
最后
ZFPlayer最初版本并不是以协议为基准分散到各个类去实现,后来更新了整个功能结构采用协议进行自由定制与复用,每个类的所实现的功能点比较明确单一,这样也便于维护拓展,背后可以看出作者很用心并且此库更新的版本也比较快。
使用途中,最好组件复用,比如使用了ZFReachabilityManager,若项目中之前存在ReachabilityManager网络监听类可以保留其一,避免重复。