前段时间做了游戏的相关业务,其中弹幕相关的内容自成一块。弹幕已经不只是最初的视频弹幕了,战火已经烧到了评论区,烧到了手机淘宝的首页搜索结果。作为一种近几年迅速燃起的内容呈现形式,有必要适时引入,对于休闲化、娱乐化的业务更是如此。那么,要做出一个较为完整的弹幕效果来,需要哪几个部分呢?尤其是,在集团内部,怎么快速地搭建起一个可用的弹幕框架来?本文分3块来阐述。
目前弹幕的呈现载体主要是Web、无线客户端。因为我们的工作主要针对无线端,所以本文主要以无线端为例——包括iOS,Android两类系统。
弹幕无非是动画,是分布在时间轴上图像的连续运动。自然可以用Native的动画来实现。不过弹幕动画有一个重要的特征,即保持动画元素(sprites)尽可能少地碰撞,以使弹幕承载的信息能够清晰地传达,执行碰撞检测是必须的。但弹幕里的碰撞检测相对简单,因为弹幕的运动轨迹相对简单并且容易预测,所以只需要在一条弹幕将要显示之前,根据已经显示的弹幕(位置、速度、活跃的时间等)来确定他的运动轨迹。以尽最大可能地在其生命周期内不与已有弹幕冲突。
弹幕不是超然而独立的,往往相伴业务场景而生,目前可见最多的场景是视频,直播或者录播皆有。到此时则涉及到一个时间同步的问题。比如,一位用户在看一段时间第314s的时候突然有感而发,发出了一条弹幕,自然希望其他观众能够在看到视频此刻看到他的弹幕。所以一条弹幕上屏的时间是需要明确的,想想那些年文不对题的字幕君吧。那么,如何实现呢?一般,可以为一条弹幕提供一个时间点delay,当到了这个时刻,由控制器把这条弹幕播放出去。但仅仅这些是不够的,因为视频还存在暂停,存在快进快退,所以你必须也为弹幕组件提供类似的接口,以期能和视频内容同步。其他的应用场景也是类似的。比如下面的样子(弹幕在向左运动):
弹幕的运动样式主要有两种,一种是横向的过场弹幕,一种是纵向的浮动弹幕。弹幕的内容形式不外乎一段文字或者图片,其中以文字为主。对于文字,则有文字的颜色、背景、字体、边框等属性,这一切必须是灵活可配的。当然实际应用中一个APP需要的是风格统一的、优雅美观的弹幕动画。所以弹幕的方向不要太混乱,不要有太多不一样的主题配置。你可以定义几类色调协调但样式不同的弹幕,然后由业务代码决定使用哪一种风格的弹幕。
性能直接关系到用户体验。在绝大多数场景中,锦上添花的弹幕往往伴随着具体的业务逻辑,业务逻辑会占用CPU——甚至很高的CPU,比如视频解码———所以弹幕动画应该尽可能地使用GPU渲染。为应对线上可能的大规模弹幕的情况,本地最好也能测试到大量弹幕的情况。可以使用一个定时器,模拟客户端频繁接受渲染弹幕的情况,看看实际中弹幕的性能究竟如何。
弹幕稀稀疏疏地铺满半屏窗口,朦胧中犹抱琵琶半遮面的感觉,自然是最好的。但万一遇到弹幕决堤,内容疯狂涌来,那当如何应对?渲染内容层层堆叠,既看不清,又降低了系统应用性能,为此可以在业务或者组件中选择限流。
若不考虑弹幕在用户间共享,只需下图左侧的模块即可;若需引入弹幕共享、存储功能,则如下图右侧所示。
但实际情况往往比这复杂。弹幕很多时候是实时的,最好使用长连接来传输数据。业务导向的项目,很少从零开始开发专门的弹幕服务通道,而是尽可能地应用已有的服务组件。淘宝在长连通道上有多个选择,但其功能又是不尽相同的。这种不同也会带来弹幕实现方案的不同。比如通道A支持订阅功能,消息会根据订阅关系分发;而通道B是单纯的通道,订阅关系由业务方维护,凡是发送到客户端的消息都会接收,所以流量需要业务服务端来控制。
仅仅使用长连接通道是不够的,还需引入业务服务器,其原因如下:
整体的数据流如下图所示:
通过长连接传输的弹幕消息会有一些附加数据需要考虑,比如弹幕的样式、出现的时间,随着业务的扩展,可能需要更多的辅助字段。所以弹幕消息必须能够向后兼容,一般可设置为message,version两个字段,message为纯粹的json字符串,version表示消息的版本号。先解析version,根据判断得到的version选择响应的解析样式。太多的附加信息会降低数据的利用率,此是需要权衡的地方。当然,如果针对的是在线视频业务,弹幕的流量相比于视频流而言,就显得不那么重要了。
主要有两种:
主题代表弹幕消息围绕的中心。在不同的业务场景中,主题的呈现方式可能是不同的。在视频直播业务中,主题代表了一个个直播房间,弹幕围绕着视频展开;在新闻咨询业务中,主题代表了一则则新闻,弹幕围绕着新闻展开。客户端与主题存在多对一的关系,如下图所示:
用户U1、U2订阅了主题T1,用户U3、U4订阅了主题T2。由于处于不同的语境中,U1、U2发送的弹幕U3、U4应该是不能接收到的,反之亦然。很自然服务端需要维护一个用户到主题的映射表简单的实现是,客户端监测到用户进入特定主题之后,发送一条网络请求登记这样一条订阅;用户离开特定主题时发送网络请求注销登记。但由于实际客户端运行场景复杂,离开特定主题不一定来得及发送网络请求。补充方案是,由客户端每隔特定时间心跳一次,用以告知服务端维护映射表。一旦服务端一段时间没有监听到心跳信息,就取消映射表中的一条订阅。这里需要注意的是,服务端需要防止心跳的伪造,否则可能映射表可能会因攻击而混乱掉。一旦映射表正确建立,用户发送的弹幕消息就可以准确传达到相同主题的用户客户端了。
对于直播等即时性业务,弹幕数据一般没有重播的必要;但是对于录播,则需要持久化弹幕,如是方能在其他用户看视频的时候看到其他人发出过的弹幕消息。持久化这类弹幕数据,必须在存储弹幕的时候带上弹幕对应的时间点。在用户进入了某一主题之后,批量返回给客户端对应的弹幕数据,由客户端将弹幕数据对应到视频业务响应的时间点上;如果此主题对应的弹幕数据很多,服务端可能实现做一定的筛选;对于录播同时新发送的弹幕,则由服务端记录并添加到对应的弹幕数据列表中。