本文字数:6244字
预计阅读时间:35分钟
+
介绍
弹幕诞生于日本的视频平台,后来被B站这种短视频平台引入到国内,并在国内发展壮大。后来逐渐被长视频平台所接受,现在视频相关的应用基本上都会有弹幕。
但是长视频弹幕和B站这类的短视频弹幕还不太一样,短视频平台有自己特有的弹幕文化,所以弹幕更注重和用户的互动。长视频平台还是以看剧为主,弹幕类似于评论的功能,所以不能影响用户看剧,弹幕不能太密集,而且相互之间最好不要有遮盖,否则会对视频内容会有比较明显的影响。
本篇文章主要从长视频平台的角度来讲弹幕的实现原理,但其实短视频平台的弹幕也是同样的原理,区别在于短视频可能弹幕种类会多一些。
+
技术实现
画布
以我公司应用为例,有iPhone
和iPad
两个平台,在iPhone
平台上有横竖屏的概念,都需要展示弹幕。在iPad
上有大小屏的概念,也需要都展示弹幕。弹幕的技术方案肯定是两个平台用一套,但需要考虑跨不同设备和屏幕的情况。
所以,对于这个问题,我通过画布的概念来解决通用性的问题。画布并不区分屏幕大小和比例的概念,只是单纯的用来展示弹幕,并不处理其他业务逻辑,通过一个Render
类来控制画布的渲染。对于不同设备上的差异,例如iPad
字体大一些,iPhone
字体小一些这种情况,通过config
类来进行控制,画布内部不做判断。
小屏上画布会根据比例少展示一些,大屏上则多展示一些。字体变大画布也会根据比例和左右间距进行控制,保证展示比例是对的,并且在屏幕宽高发生改变后,自动适应新的尺寸,不会出现弹幕衔接断开的问题,例如iPad
上大小屏切换。外部在使用时,只需要传入一个frame
即可,不需要关注画布内部的调整。
弹幕轨道
从屏幕上来看,可以看到弹幕一般都是一行一行的。为了方便对弹幕视图进行管理,以及后续的扩展工作,我对弹幕设计了“轨道”的概念。每一行都是一个轨道,对弹幕进行横向的管理,这一行包括速度、末端弹幕、高度等参数,这些参数适用于这一行的所有弹幕。轨道是一个虚拟的概念,并没有对应的视图。
轨道有对应的类来实现,类中会包含一个数组,数组中有这一行所有的弹幕。这个思路有点像玩过的一款游戏-节奏大师,里面也有音乐轨道的概念,每个轨道上对应不同速度和颜色的音符,音符数量也是不固定的,根据节奏来决定。
轨道还有一个好处在于,对于不同速度的弹幕比较好控制。例如腾讯视频的弹幕其实是不同速度的,但是你仔细观察的话,可以发现他们的弹幕是“奇偶行不同速”,也就是奇数行一个速度,偶数行一个速度,让人从感官上来觉得所有弹幕的速度都不一样。如果通过轨道的方式就很好实现,不同的轨道根据当前所在行数,对发出的弹幕设置不同的速度即可。
有时候看视频过程中会从右侧出现一条活动弹幕,可能是视频中的梗,也可能是类似于广告的互动。但是活动弹幕出现时一般是单行清屏的,也就是和普通弹幕是互斥的,展示活动弹幕的时候前后没有普通弹幕。这种通过轨道的方式也比较好实现,每条弹幕都对应一个时间段,根据活动弹幕的时间和速度,将活动弹幕展示的前后时间,将这段时间轨道暂时关闭,只保留活动弹幕即可。
轮询
每条弹幕都对应着一个展示时间,所以需要每隔一段时间就找一下有没有需要展示的弹幕。我设计的方案是通过轮询,来驱动弹幕展示。
通过CADisplayLink
来进行轮训,将frameInterval
设置为60,即每秒轮询一次。在轮询的回调中查找有没有要展示的弹幕,有的话就从上到下查找每条轨道,某条轨道有位置可以展示的话就交给这条轨道展示,如果所有轨道都有正在展示的弹幕,则将此条弹幕丢弃。是否有位置是根据屏幕最右侧,最后一条弹幕是否已经展示完全,并且后面有空余位置来决定的。
对于取数据的部分,数据和视图的逻辑是分离的,相互之间并没有耦合关系。取数据时只是从一个很小的字典中,根据时间取出所用的弹幕数据,并转化为model
。字典的数据很少,最多十秒的数据,而且这里并不会接触到读数据库的操作,也没有网络请求的逻辑,这些都是独立的逻辑,后面会讲到。
弹幕视图
经常看视频的同学应该会知道,弹幕的展示形式有很多,有带明星头像的、有带点赞数的、带矩形背景色的,很多种展示形态。为了更好的对视图进行组织,所以我采用的就是很普通的UIView
的展示形式,并没有为了性能去做很复杂的渲染操作。
用UIView
的好处主要就是方便做布局和子视图管理,但在屏幕上做动画时,是对CALayer
进行渲染的。也就是说UIView
就是用来做视图组织,并不会直接参与渲染,这也符合苹果的设计理念。
复用池
弹幕是一个高频使用的控件,所以不能一直频繁创建,以及添加和移除视图,会对性能有影响。所以就像很多同学设计的模块一样,我也引入了缓存池的概念,我这里叫复用池。
弹幕复用池和UITableView
的复用池类似,离开屏幕的弹幕会被放在复用池中等待复用,下次直接从复用池中取而不重新创建。弹幕视图做的工作就是接收新的model
对象,并根据弹幕类型进行不同的视图布局。
并且弹幕只会在创建时被addSubview
一次,当弹幕离开屏幕不会被从父视图移除,这样弹幕从复用池中取出时也不需要被addSubview
。当动画执行完成后,弹幕就直接留在动画结束的位置,下次做动画时弹幕会自动回到fromValue
的位置。实际上视图结构就如上图所示,灰色区域就是可视区域。
系统弹幕
在视频刚开始时会有引导信息,比如引导用户发弹幕,或者提示弹幕有多少条,这个我们叫做系统弹幕。系统弹幕一般是展示到屏幕中间时,才开始展示后续弹幕。但是要精确的计算到弹幕到达屏幕中间,然后再展示后续弹幕,这种的采用清除前后特定时间段的弹幕就不太精确,