导读:弹幕的出现增加了视频观看者的深度参与感,弹幕也逐渐成为国内各大视频网站最基本的评论交互形式,本文将通过网易易盾在弹幕实现原理及交互方式方面的实践,具体介绍弹幕相较于传统聊天室的区别与实践经验,希望能为大家在弹幕系统设计方面带来一些借鉴。
引言
在 2022 年的今天,弹幕在国内的各大视频网站已经成为了一个最基本的评论交互形式,它为视频社交增添了很大的活力,然而这也给视频内容的审核工作带来了巨大挑战,在较为严格的审核场景下,数量庞大的弹幕通过机审后,我们会进行一轮或多轮的人工审核,但是为了不误判,易盾的智能审核平台的实践中为了能结合视频内容上下文,对被审核弹幕进行精准判断,实现了一套弹幕系统,和原视频内容及弹幕内容保持高度一致,更有利于提高审核的精准与效率。接下来就是易盾在弹幕系统的实践中总结出来的通用设计与实践。
弹幕简介
弹幕的读音为 dàn mù,因为大量评论在视频上方滚动时很像飞行射击游戏里的“弹幕”,所以国人命名如此,而在日语中被称为 danmaku,注意了,不是 danmuku。
弹幕的发明者具体到个人不是很清楚,这种评论形式最初是在日本的线上影片分享网站 Niconico 动画出现的,后来被 AcFun 引进,再后来大家都知道了,Bilibili 将这种形式发扬光大,可以说弹幕成就了 B 站,B 站也发扬了弹幕这种形式。
现如今国内各大视频平台,发弹幕已经成为了最基本的功能了,不过就我目前体验来看,还是 B 站的弹幕花样最多,但实际上对于腾讯视频、爱奇艺这种偏影视的视频网站来说,也不需要多少花样,不然可能会适得其反。
弹幕得以发展的原因
在弹幕这种评论形式出现之前,对于在线视频用户,他们之间的实时交流方式主要是聊天室模式,用户输入文本内容后,文本列表将整体从下向上滚动,如下图:
而在弹幕出现之后,用户输入文本内容后,文本将出现在视频右侧,在独立的轨道中从右向左移动,如下图:
两者之间各有各的优势,不过就现在用户的观看习惯来说,弹幕带来的用户体验是比聊天室模式要好的,接下来笔者将介绍弹幕都有哪些优势。
评论同屏密度
与聊天室模式相比,弹幕模式有更宽的展示区域,毕竟用户观看的信息主体在视频内容,没有哪个网站会把聊天框的宽度设置的比视频宽度还大的。同一条评论,因为弹幕是横向轨道内移动,所以不会像聊天室模式下由于句子过长而导致这行占据更多高度。所以同屏下,对于人眼来说,弹幕模式比聊天室模式接收到的信息更多。
评论更新频率
在聊天室模式中,所有评论都是以相同的频率向上滚动,一条评论的出现会将所有评论向上顶,而弹幕模式下每条评论都在独立的轨道中移动,并不受其他评论的出现所影响,可以通过算法保障每条评论在屏幕内的展示时长。
视线移动适应性
在聊天室模式中,用户如果关注视频内容则无法阅读评论。而弹幕模式通过把文字覆盖于视频画面之上让用户可以同时阅读评论与观看视频,无需视线在两个区域间往返移动,有更好的沉浸体验。以下是聊天室模式下,我们的视线移动方向示意图:
以下是弹幕模式下,我们的视线聚焦范围示意图:
阅读习惯
我们大多数人阅读习惯是从左到右、从上到下,因此人们养成了横向阅读单行信息的习惯。
在弹幕模式下文字从右向左移动,人从左向右阅读,形成了从左向右的合力,在这种模式下我们可以用较短时间就能理解文字的含义。以下是弹幕模式下,视觉的合力方向示意图:
而在聊天室模式下,人从左到右阅读,而阅读中的文字则在不停的向上移动,形成一个倾斜向上的合力,这会对快速阅读产生障碍。以下是聊天室模式下,视觉的合力方向示意图:
心理因素
弹幕的出现能使多个用户对同一视频时间点于不同的时空发表看法,有一种跨越时空交流的感觉,极大的增强了用户的参与感。
在观看视频的某一时间节点,在弹幕上能看到很多与自己相同的观点,会觉得这个世界上有很多和你一样想法的人,会有一种共鸣感。另外,有时候还会看到很有意思的弹幕,惹的人会心一笑,还会看到一些很有哲理、对自己的知识扩展也很有帮助的弹幕。
弹幕的实现方式
在现在市面上,弹幕的实现可以分为两种方式,一个是 HTML+CSS 方式,另一个是 Canvas 方式。前者实现能很方便地给每条弹幕添加事件,比如我们常用的移到弹幕上悬停并弹框跳出选项,这得益于原生的 DOM 事件很容易做到。而后者的实现需要自己去写一套事件机制,对于像我这种对 Canvas 不太熟的前端,就比较麻烦了,不过愿意花个几天时间去搞一搞倒也不是什么问题。
两者在性能上还是有区别的,结论是 HTML+CSS 的性能没有 Canvas 好,前者会在页面下创建非常多的 DOM 节点,当同屏弹幕过多导致出现大量 DOM 节点时,对于一些“老机器”说不准都能卡死。所以大家会看到,在直播时,对性能要求很高,比如很多视频网站直播就会采用 Canvas 的实现方式去创建弹幕。
接下来我们的代码实操均采用 HTML+CSS 方式实现,对 Canvas 感兴趣的,按照相同的设计思路也是能写出来的。
弹幕的设计
我们开始对弹幕的实现做一个功能上的设计,在后面代码实操之前让大家有一个初步的设计思路,更容易理解代码的意思。首先我们看一下视频中常规的弹幕画面:
其中我使用“红线”标记出来的就是每轮弹幕所滚动的区域,我们称这个区域为轨道,图中又画了我们常见的滚动弹幕及底部弹幕,当然,为了图简洁点,顶部弹幕我就不画了。
目前我们能确定的信息是,弹幕系统需要一个轨道的承载逻辑,同时背后还需要一个指挥官来决定每一条新加入的弹幕是去往哪个轨道,什么时候开始渲染弹幕,以及动画逻辑。
轨道
从弹幕的呈现效果可以很明显得知,一个轨道内有若干的弹幕,而且弹幕的出现是依次进行的,这意味着需要一个容器来把这些弹幕装进去,适合的时机再一条条放出来。为了让指挥官能计算出这个时机,我们还需要一个变量 offset 来表示轨道已占据的宽度。
根据上面的简单分析,我们可以先定义好表示这个轨道的类和它所需要的属性:
容器我们有了 danmus 这个数组,但是如何添加、删除弹幕呢?那就要定义方法了:
最后还有一个特别重要的方法是用于更新轨道已占据的宽度,在后面会说到渲染弹幕动画的时候,弹幕进入轨道的时机是由这个所占据轨道的宽度来计算的,所以弹幕的每次移动,我们都需要去对轨道的所占据宽度进行更新。
这就是整个轨道的设计,非常简洁明了,它的职责非常清晰:管理轨道内弹幕的增加、删除及已占据宽度的更新,但是不负责渲染!
(看到这里如果有心情仔细了解逻辑代码是怎么样的,可以点击这个仓库查看源码:https://github.com/vortesnail/qier-player/tree/master/packages/qier-player-danmaku)
指挥官
由前文可知,我们一个视频画布内是有多条轨道的,那么如何管理这些轨道呢?那就是背后的指挥官在“负重前行”了。
首先是一种指挥官管理了当前弹幕类型下的 2N 个轨道:
而我们这次的弹幕设计中包括了滚动弹幕,顶部固定弹幕和底部固定弹幕,所以对于指挥官我们又有了以下层级关系:
然而这些指挥官是有共同的属性及方法的,我们可以抽象成一个指挥官基类(BaseCommander),简单的代码结构如下:
有了指挥官基类,我们就可以去分别实现各种类型的指挥官了,在这里我不想把指挥官的实现代码贴出来,放一张思路图,如果有兴趣的,可以再去阅读源码:https://github.com/vortesnail/qier-player/tree/master/packages/qier-player-danmaku
render
上面的图除了“用户发送弹幕”这一步,其它所有步骤是我们的核心实现 render ,所以我们来对此方法做一个剖析。它的工作职责为下图:
从等待队列中抽取合适的弹幕放入轨道
每一个指挥官(Commander)都有自己的等待队列 waitingQueue,里面存放的是所有还未被渲染到画布上的弹幕,每次在 render 方法执行时,要把等待队列中的弹幕添加到合适的轨道上去,这个过程由 extractDanmu 方法实现:
这里的思想是,遍历等待队列中的弹幕,尝试将其通过 add 方法添加到轨道上,如果添加成功,将该弹幕从等待队列中删掉,进行后面的弹幕的添加。
但是遍历过程中若出现了一次添加失败,证明所有轨道都没有办法再添加新的弹幕了,我们就要停止遍历。
add 方法的作用即为将弹幕添加到合适的轨道上,而实现这一目的还需要以下过程:
1.找到合适的轨道;
2.创建真实的弹幕 DOM 并计算速度;
3.将其推入要被加入的轨道的 danmus 数组中,计算弹幕的 offset。
另外,add 是继承指挥官基类的抽象方法的具体实现,这里我们以最复杂的滚动弹幕作为实现举例:
上面代码中第一行就是 findTrack 方法,目的就是找到合适的轨道,如何找到合适的轨道呢?
这里采取的实现策略还是比较直观简单的,从上往下遍历轨道,找到第一个能插入的轨道,
找到合适的轨道就返回其下标,继续执行后面逻辑,没有就返回 false。
接下来我们探索下怎么计算弹幕的速度,这里弹幕的速度看产品喜好,下面介绍两种:
1.每个弹幕的速度都是相同的,所以也就不存在碰撞问题,但是效果非常死板。
2.每个弹幕的速度都是不一样的,但是需要解决碰撞问题。
为了实现不同的速度,最简单有效的方式其实就是通过追及问题求出弹幕的最大速度。
假设现在轨道长度为 L,轨道上已存在的最后一个 弹幕A 已经飞过了距离 S,其速度已知是 vA ,那么如何计算 弹幕B 的速度呢?
首先我们假设 弹幕B 和 弹幕A 要在同一时间达到轨道的终点,就会得到以下的等式:
于是见很简单的推理出了 弹幕B 的速度为 vB,转换为我们代码里面的变量名就是后面红色字的等式。
但是这样会有一个问题就是,假入 弹幕A 已经快到了轨道终点了,这样就会造成计算出的 弹幕B 的速度过大,具体表现即为弹幕飞的很快一闪而过,这种体验是很差的,所以我们需要有一个最大速度的限制,在上面 add 方法中有这么一行代码就是这个作用:
这里 defaultSpped 大小为平均速度,所以这里的限制即为平均速度的 2 倍。
后面就是将创建好的弹幕 DOM 放到指定的轨道中的 danmus 数组等待渲染即可。
遍历轨道数组,依次渲染轨道中的弹幕
放出 render 方法的实现,我们主要经历以下四个过程:
1.遍历每个轨道中的每个弹幕;
2.获取弹幕 DOM 并计算 translateX;
3.更新轨道的偏移量 offset;
4.移除超出画布的弹幕。
以上代码应该是很容易看出对应的过程,这里就不细述了。
总结
最后值得一提的是,中国网络视听节目服务协会发布《网络短视频平台管理规范》和《网络短视频内容审核标准细则》,对短视频的发布者和平台方提出详细要求,其中一大亮点是将“弹幕”划入“先审后播”的范围,进行“实时管理”先审后播,弹幕迎来良性发展。
百万用户同时在线,高并发的弹幕一度成为网站的内容生态治理的巨大挑战。面临的问题主要是:网友肆意发言,主动绕过机器审核,且由于弹幕数量庞杂,难免有很多有害内容成为“漏网之鱼”。无论是报复社会的喷子,还是竞争对手的黑粉水军,都会破坏观众的观看体验,威胁直播平台的核心竞争力。
在弹幕审核环节,易盾为网站弹幕提供“机审+人审+举报”机制,通过机器学习来自行判断弹幕,辅助人工灵活应对,抵御内容生态恶化是易盾的使命。弹幕将以文本形式汇合入“智能审核平台”,经过“内容智能检测技术”与“人工审核”的两道筛选,除了及时发现弹幕中涉及的广告、涉黄、暴恐等网络监管部门严令禁止的内容,还可以针对主播的需求,为主播提供实时的可操控的界面,通过添加相关黑粉攻击言论、带节奏语言性质的文本,进行实时的屏蔽,保障主播粉丝的观看氛围。
如何确保平台内容安全几乎是网站发展的第一要务,其中就包括弹幕,但凡弹幕出现与“正能量”相悖的内容,都将面临安全风险。这篇文章简单的讨论了弹幕的常规设计、实现与安全保障思路,里面还有可以优化的点,比如弹幕的速度问题,还有没有提到的事件监听,具体实现大家如果有兴趣可以阅读下源码:https://github.com/vortesnail/qier-player/tree/master/packages/qier-player-danmaku