开发者投稿|深入Storyteller:实时协同Tutorial编辑器

作者:余彦臻

深入 Storyteller:实时协同 Tutorial 编辑器

在刚刚结束的 RTE 2020 创新挑战赛中我提交了名为 Storyteller 的作品,其功能是一个“实时协同的交互式 tutorial 编辑器”。在本文中我会深入 Storyteller 的技术细节,聊聊声网 Agora 的实时服务如何在这个场景下大放异彩。

从零开始设计

当我开始构思这个作品时,我有两方面的考量:

  • 这个作品要在什么场景中解决什么问题
  • 声网 Agora 所提供的实时服务会为作品带来哪些助力

下文也会始终围绕这两方面的思考进行分享。

在寻找灵感时,我首先从日常的工作中入手。工作中我主要负责的产品是一个 toB 基础设施类软件的 Web 控制台,我们发现对于这类产品大家都有快速向客户演示的需求。

在过去,为了更好的演示,我们往往会选择以下几种形式:

  • 搭建真实环境用于演示。这一方式的弊端是资源消耗大,例如需要使用 3 台物理服务器搭建;另一方面由于是新环境,可以演示的内容也较少。
  • 录制视频。这一方式观看者参与感较差。
  • 基于 mock 数据开发 Demo 程序。这一方式开发和维护成本很高,并且有一些底层行为难以模拟。

因此我就将目标定为实现一种超越以上所有方式的演示工具,既能够像视频一样稳定、轻量,又能够像真实程序一样进行交互,同时还不需要编写代码,任何人都可以制作。

我将这种形式称为“交互式 tutorial”,而 Storyteller 就是一个“交互式 tutorial 编辑器”,两者的关系与视频和视频编辑器类似。

另一方面,我开始思考实时服务如何与 Storyteller 相结合。

事实上,同为“实时”,声网 Agora 的实时服务也和我们在 Web 场景中常见的 websocket 等通信方式有所不同,它与 WebRTC 更为相似,但提供了更多可靠性、传输性能方面的优化。

而这类实时服务最大的特点就是点对点的传输能力。也就是客户端之间可以直接通信,而不需要将数据传递给一台中心化的服务器再进行分发。所以在我的设想中,应该将这样的网络链路优势最大化,才能够让 Storyteller 真正插上实时协作的翅膀。为此,我准备在 Storyteller 中增加两个功能:

  1. 多人实时协同编辑。传统的视频编辑类软件大多数单机软件,而 Storyteller 可以在这方面做出突破,将多人实时协同的能力集成到 tutorial 编辑的过程里,加速 tutorial 的制作。
  2. 云端录制视频旁白。在一些需要讲解的场景,仍然可以将视频和交互式 tutorial 相结合,一同播放。而这个视频的录制也可以包含在 Storyteller 内,并且基于声网 Agora 云端录制的能力,获得最优的视频上传链路。

交互式 tutorial 的技术细节

首先我们可以通过两张示意图快速理解 Storyteller 编辑器和交互式 tutorial 的形态。

Storyteller 编辑器:

交互式 tutorial:

从示意图中可以看出,交互式 tutorial 的实现中有一个非常重要的细节:Web 视图静态化。

Web 视图静态化

所谓“静态化”是与动态的 Web 内容相对应的。举例来说,如果我们只是记录了示例中 TODO list 内的操作:

  1. 00:01 - 完成 Todo 3
  2. 00:05 - 添加新 Todo 4
  3. 00:10 - 将已完成的 Todo 2 标记为未完成

当再次在包含真实动态逻辑的 TODO list 中再次执行这些操作记录,可能就会得到完全不同的结果,甚至执行失败。

这也就是交互式 tutorial 需要解决的核心问题:像视频一样将 Web 视图中的内容及其变化保存下来,以特定的数据格式记录,再通过 JS 重建视图及变化达到回放的效果。

与普通视频相比,这样的录制方法有以下优点:

  • “画质”无损,因为最终重建并播放的是真实的 DOM,所以不会像图像数据被压缩损失画质。
  • 回放时可感知上下文。同样因为重建了完整的 DOM,所以交互式 tutorial 可以在任意时间点知道当前 DOM 的结构,也就可以在其基础上进行精细的编辑,例如给特定 DOM 增加 tooltip 提示。
  • 易于压缩,最终文件体积与同时长的视频相比更有优势。

当然这样的录制实现并不容易,可以简单列举一部分需要解决的问题:

  1. 将 Web 视图制作为可序列化的快照。与 HTML 等内容不同,这个快照需要记录更多的视图状态,并将动态脚本内容移除。
  2. 对所有可能对 Web 视图造成变化的因素进行观测,并记录为可序列化的事件。这一实现复杂且细节众多,但也是高性能录制的基础。
  3. 将各处的相对路径转为绝对路径,保证回放时的稳定性。
  4. 回放时通过复制 CSS 样式的方式模拟 hover 行为。

在 Storyteller 内我使用了由我开源维护的项目 rrweb 以实现这部分功能,更多与录制回放相关的技术细节可以从仓库的文档中进行了解。

完成基本的操作录制后,还需要给录制好的内容增加一定的特效,才能让交互式 tutorial 的内容变得更为丰富,我将这部分功能称为“剪辑 tutorial”

剪辑 tutorial

由 rrweb 录制的内容是一系列按时间线排列的快照与事件,所以剪辑 tutorial 的过程就是在同一条时间线上插入特定的事件,在特定的时间点进行执行。示意图如下:

图中蓝色的 tooltip 特效就是通过 Storyteller 的编辑器插入到录制的数据当中。在特定的时间点展示 tooltip 并暂停,就能够实现 tutorial 教学的效果。

除了基本的 tooltip 之外,Storyteller 还基于同样的方式拓展出更多的“特效”,例如:

  • 快进
  • 显示/隐藏鼠标
  • 显示旁边视频

实时协同

Storyteller 内交互式 tutorial 的数据结构其实非常适合增加实时协同的功能,因为所有的快照、事件、特效数据都是可以序列化的,也就可以通过网络传输给其它编辑器客户端,让多个用户同时编辑同一份 tutorial。

但是在开始实现之前,还需要确定 Storyteller 的实时协同在客户端之间是传递全部数据还是部分数据。

传递部分数据示意图:

1601958629492

所谓传递部分数据,就是在多个协同编辑的客户端之外,还有一个中心化的后端服务器。交互式 tutorial 的数据(示意图中的 story1, …, story3)都持久化的保存在后端服务器中。

当第一个客户端需要编辑时,就像服务器请求对应的 story1 数据。而当第一个客户端向第二个客户端发起协同编辑的邀请时,只会将 ID: 1 这一标识进行传递,第二个客户端获得 ID 后自行向服务器请求,得到相同的 story1 数据。至此两个客户端就获得了相同的数据,可以开始进行协同编辑,而后续的编辑动作因为数据量相对较少,只需直接在客户端直接同步,并定时保存至后端即可。

全部数据传递示意图:

1601958972101

而所谓的传递全部数据,则是不依赖一个中心化的后端服务器,由第一个客户端在分享时将本地的 story1 数据全量传递给第二个客户端,双方也就达到了相同的状态。

两种实现方式各有利弊,为了充分体验声网 Agora 实时消息 SDK 的功能,我选择了后者。

因为传递的数据都是可序列化的文本格式,所以我使用声网 Agora 实时消息的 SDK 作为网络传输层,它提供了客户端之间点对点的高可用传输。

在使用的过程中,实时消息 SDK 的时延和稳定性都明显优于基于 websocket server 实现的网络传输层。考虑到实际场景中可能存在客户端之间物理距离很近、距离中心化服务器很远的场景,两者的差距可能会比本地测试更为明显。

从上图的示意中就可以看出,基于实时消息 SDK 的点对点传输(黑色箭头)传输链路明显比经过中心化服务器转发(绿色箭头)的链路更为高效。

在完成这部分的实现之后,我也发现以下几点可以进一步优化的内容:

  1. Storyteller 第一版的实时交互实现了一个非常简单的协议,规定了几种语义,例如 sync-story, add-effect 等。在真实场景中,我们可以使用更为成熟的理论例如 CRDT 来描述协同数据的格式及变化,以保证在多个客户端之间不会产生数据冲突。
  2. 声网 Agora 实时消息 SDK 有发送数据量和频率的限制,因此也不适合直接传输大量数据。如果将 Storyteller 重新改造为传递部分数据的实现方式,可以与实时消息 SDK 结合的更好。

云端录制视频旁白

上文中我们提到过,在播放交互式 tutorial 的同时,我们可以插入视频类的特效,以起到视频旁白的效果。这个功能最初的灵感来源于我翻阅声网 Agora 实时服务文档时看到的“云端录制”服务。

在仔细阅读云端录制的文档之前,我还在设想如何获取各个客户端录制的音视频流数据,再在本地进行合并后上传。但当理解云端录制的工作方式后,我发现它的数据流更为合理,并且非常易于使用。

我们同样使用示意图帮助理解云端录制中各方的关系,但首先我们明确云端录制中各方的名称:

  • client 1,参与编辑的一个客户端。
  • client 2,参与编辑的另一个客户端。
  • cloud recording,云端录制服务。
  • object storage,用于保存录制视频的第三方对象存储。

设想的录制场景是 client 1 和 client 2 加入到同一个实施音视频 channel 中,一起完成这段旁边视频,并最终将这段视频上传至第三方对象存储中,再在交互式 tutorial 的数据中记录对应视频的 URL 地址,在对应时间点开始播放。

从云端录制的 API 上分析,它非常巧妙地以一个类似 shadow client 的形式加入了 channel 中,因此也就能够接收到 channel 内各个客户端的音视频数据流。与此同时,云端录制提供了一系列 API 用于控制:

  • 开始录制的时机
  • 录制的 channel
  • channel 内需要录制的客户端
  • 是否需要将多个客户端的音视频流合并

这样的设计带来了很大的灵活性,并且避免了在一个客户端进行这些操作再上传所带来的开销。

云端录制也对各种常用的对象存储都做了比较好的支持,只需要通过 API 配置对象存储的权限和元数据信息,就能够非常便捷地完成上传。

总结

在完成 Storyteller 的过程中,我对 rrweb 的适用场景有了更多探索,也尝试了一些实时协同场景的实现方案。

另一方面,也验证了声网 Agora 实时消息 SDK 的通用性。事实证明,实时消息 SDK 可以更广泛的被应用于各类数据传递的场景,而不只是作为音视频场景的辅助传递一些聊天信息。

最后,声网 Agora 云端录制的精巧方案也让我对一个统一、稳定的实时网络的价值有了更深的理解。

希望这篇文章可以对读者们也有所启发。

推荐阅读
作者信息
Henin
TA 暂未填写个人简介
文章
12
相关专栏
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。