专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
51好读  ›  专栏  ›  Cocoa开发者社区

[iOS]调和 pop 手势导致 AVPlayer 播放卡顿

Cocoa开发者社区  · 公众号  · ios  · 2017-07-18 10:59

正文


框架特性


✅ 全屏 pop 手势支持

✅ 全屏 push 到绑定的控制器支持

✅ 为每个控制器定制 UINavigationBar 支持(包括设置颜色和透明度等)

✅ 为每个控制器添加底部联动视图支持

✅ 自定义 pop 手势范围支持(从屏幕最左侧开始计算宽度)

✅ 为单个控制器关闭 pop 手势支持

✅ 为所有控制器关闭 pop 手势支持

❤️ 当当前控制器使用 AVPlayer 播放视频的时候, 使用自定义的 pop 动画以保证 AVPlayer 流畅播放.



01.真有这么回事?


做过视频的朋友都知道系统的 pop 手势会导致视频画面卡顿,没做过的朋友都不敢相信,这绝对不是苹果的风格,居然留了这么一个坑。如果碰到这个问题,尝试去网上搜关键词pop 手势 AVPlayer 卡顿,你搜不到太多有价值的解决方案。


因为我之前写了一个导航控制器的轮子,还写了一个视频播放器的轮子,所以理所当然,我必须趟平这个坑。下面我们花几分钟一起来看一下我是怎么做的。


02.思路分析


pop 手势就是为了在大屏下能获得更好的用户体验设计的。有了 pop 手势,返回的时候不用非要点一下返回按钮,只需优雅的右滑就能返回。但是系统的播放器会和 pop 手势冲突,对于有追求的程序员来说,这样做太影响用户体验了。


如果不做任何处理,系统在执行 pop 动画的时候,视频声音仍然播放正常,但是画面会阻塞会卡顿,等你取消 pop 手势仍然回到当前页面的时候,你会惊喜的发现,系统也知道画面出问题了,所以飞快的向后查找当前需要播放的那帧画面,但是很遗憾,系统也找不到了,所以最后播放的时候,声音和画面对不上,或者画面根本就不更新了,就卡在那里,然后声音一直在播放。


为了应对这个系统的 bug,开发者心里一般是默念一句...(此处略去三个字),然后在 -viewWillDisappear: 里写下一行:


[self.player pause];


可是别人的 APP 都没这个问题啊,你看看腾讯视频、哔哩哔哩、爱奇艺...


为了说明这个问题,我前段时间在公司内部分享上讲了这个事情,这里我简单说一下。如果你自己对比一下这些实现了 pop 手势不导致画面卡顿的 APP,你会发现他们的 pop 动画和系统默认的似乎有些不一样,至于究竟有哪些不一样,请诸君各位自己去自己观察。


有了这样的观察以后,我们的思路似乎变得清晰起来,没错,就是自己实现 pop 手势。


03.动手实现


思路有了,赶紧来验证一下我们的思路吧。


我在 [iOS]UINavigationController全屏pop之为控制器添加左滑push 这篇文章里详细的说了如何实现 push 动画,虽然现在 JPNavigationController 2.0 的具体实现已经全部重新写过了,但是大致思路还是一样的。为了保证内容不重复,我这里就不再讲一遍一样的知识点了,如果你不知道怎么实现,你去看那篇文章就好了。


我们的动画结构仍然是在动画容器上面添加我们当前要 pop 的 view 以及要 pop 到的元素的 view,然后用一个 UIPercentDrivenInteractiveTransition 百分比手势来驱动整个动画过程。按照这个思路实现以后,然后在要 pop 的页面上添加了一个 AVPlayer 播放视频,日了狗了,发现和系统的居然是一样的卡顿。


这样就比较郁闷了,瞬间感觉自己方向错了,有一种柯洁面对 AlphaGo 的赶脚。


但是从别的 APP 分析得到的启发就是要自己实现这个 pop 动画,这一点肯定没错。仔细想一下,pop 动画整个过程有以下几个部分:


  • 手势:自己定义的 UIPanGestureRecognizer.

  • 动画元素:自己添加的.

  • 百分比驱动:系统的.

  • 动画容器:系统的.



从上面的分析可以知道,我们只是自己定义了手势和动画元素,但是百分比手势驱动和动画容器都是系统的,所以问题只有可能出在百分比手势驱动和动画容器上面。我想找到问题所在,所以逐个排除。


04.如何实现自己的百分比手势驱动类?


我们先来看 CAMediaTiming 协议下的一个属性


/* Additional offset in active local time. i.e. to convert from parent

* time tp to active local time t: t = (tp - begin) * speed + offset.

* One use of this is to "pause" a layer by setting `speed' to zero and

* `offset' to a suitable value. Defaults to 0. */


@property CFTimeInterval timeOffset;


这里说了动画时间的计算方法 t = (tp - beginTime) * speed + timeOffset,比方说我们约定一个动画在 0.25 秒内执行完成,系统默认 beginTime = 0,speed = 1 ,timeOffset = 0,这样处理以后,这个计算式就变成了 t = tp。当动画开始,tp 开始从 0 增长到 0.25,那么动画执行的进度 t = tp,也是从 0 增长到 0.25。


这里还有一句 "One use of this is to "pause" a layer by setting speed to zero and offset to a suitable value."也就是说可以通过设置 speed = 0的方式来实现动画的技术性暂停。


@interface CALayer : NSObject


从 CALayer 的头文件,我们可以看到 CALayer 是遵守了 CAMediaTiming 协议的。所以我们可以写一个 demo 来模仿一下。


#import "ViewController.h"


@interface ViewController ()


@property (nonatomic, weak) IBOutlet UIView *containerView;

@property (nonatomic, weak) IBOutlet UISlider *speedSlider;

@property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;

@property (weak, nonatomic) IBOutlet UILabel *speedLabel;

@property (weak, nonatomic) IBOutlet UILabel *timeOffsetLabel;


@property(nonatomic, strong) UIView *animateView;


@end


@implementation ViewController


- (void)viewDidLoad{

[super viewDidLoad];


self.animateView = ({

UIView *view = [UIView new];

view.frame = self.containerView.bounds;

view.backgroundColor = [UIColor redColor];







请到「今天看啥」查看全文