Vue、WebRTC、SocketIO、Node和Redis助力多人视频会议(下)



借助 conference: to 同一会议内的所有端用户都拥有相同的会议标志值,即管理员用户名。

而在前端方面,新的Conference.vue组件也会持有相关功能。




peers对象会监测会议中的所有用户。


我们会储存每个用户的用户名、端连接对象(RTCPeerConnection)、为该端定位的视频元素以及连接成功建立后获得的视频流。

为完成上述操作,我们本应该使用Map作为数据结构的。但对于构建视频会议app,一个对象结构就够了。但Map在存储大数据集 (O(1)) 时的表现往往更好,所以对于大型会议来说,Map可能是个更好的选择。

当管理员开启会议、会议组件实例化后,我们(与会者)就能获取媒体流并自动加入会议室了。若有新的与会者想加入,管理员会通过 conferenceInvitation 事件向每个新加入的端发送邀请。


(会议邀请的web socket服务器监听器)

简而言之,管理员和初次加入的与会者间的交互机制可以概括为以下几点:

  1. 管理员(A)开启会议并加入会议室(即joinConference socket事件);

  2. A向用户B发送邀请(即conferenceInvitation socket事件);

  3. B收到邀请,加入会议(即joinConference事件);

  4. A收到邀请确认后,添加B为会议成员(即添加到自己的端对象中),创建并发送给B请求;

  5. B收到请求后,添加A为会议成员(即添加到自己的端对象中),创建并将请求再发回给A

就像我们之前所做的那样,我们已经用Chat.vue视图父组件中实现了用合适的FE socket监听器来处理邀请和确认事件。

除对端对象进行相应管理之外,webRTC机制在第4、第5点中的作用与上一篇文章中所述完全相同。但如今会议室会成为会议中所有与会者的信令机制。

这样安排目的还是为会议所有端提供交流方式,以交换通信所需的元数据。为了简化操作,我们定义了另一个信令socket事件PCSignalingConference,并在Chat.vue视图中定义了相关的监听器方法:


和之前一样,我们根据信令通道发送的信息区分出请求、答复或者加入连接的新与会者。

但即使按照同样的流程,管理员 A 和用户 B 到底要如何操作呢?

B确认到达后,管理员才会激活会议,所以我们要在Conference.vue组件中添加以下内容:


(和与会的新用户交互)

每当新用户入会,管理员会通过 initWebRTC 方法启动 webRTC 机制,依照我们在私人会话中的方式(只不过现在是使用 mixin )创建邀请。

需要注意的是,由于在实例初始化后检测属性添加或删除时的限制,我们使用this.$set添加一个新端到端对象中。点击此处获取更多有关反应性的细节。

另一方面,用户B在获取管理员允许前不能发起会议。所以我们也要在Conference.vue组件中进行相应修改。

(新的与会者收到邀请)

在收到邀请后,新的段会通过上述同样逻辑的initWebRTC创建并发送回复。

如果会议中已经有两个用户,而管理员又邀请了第三个用户呢?

同上述流程相同,只是扩展到了更多用户。

访客B加入已存在两个用户的会议

如图所示,在管理员邀请(1)之后,会议内所有人在得到确认(2)后会立即向新的端发出邀请(3)。之后,新的端会对每个用户进行回复(4),从而建立两个连接(N-1上行和下行链路)。

相信现在你也看到了,如果我们想把该操作扩展到更多的用户,就会变得有点麻烦。

请记住,虽然这两个邀请不会同时发生,也就是说它们不能以任何特定的顺序与新端交互。所以用户B需要在我们的Conference.vue组件中处理缺乏同步性的问题。


如前所述,第一次请求确认后,会议会被激活,所以我们也需要定位之后出现的新请求。但我们的项目只需借props conference对象,一次只处理一个请求。所以在处理几个连续请求时,我们要注意不误导对象引用。如此看来,对于上述这两种情况,检索请求信息时都要创建常量。

会议对象指的是包含正确请求、答复、ice candidates、远程用户等信息的对象。

另外,会议结束后我们会退出会议、停止所有媒体流。


每个端都会重置所有对等端连接,通过 leaveConference 事件离开会议,重置会议标志。

(离开会议的网络socket服务器监听器)

媒体方面,webRTC mixin会在销毁前重置本地媒体流。

beforeDestroy() {
this.localStream.getTracks().forEach(track => track.stop())
}

整合上述所有操作,我们就可以试运行了 !

为进行测试,我们为每个用户都创建了一个应用实例。所以我们会借docker-compose.yml文件在配置中添加第三个应用副本。


这样我们就得到了如下所示的本地测试环境。
%E5%8A%A9%E5%8A%9B2
本地测试环境

现在,我们只需要通过docker-compose来构建和运行应用就可以了!


进行含3个对等端视频会议的本地测试(每个用户一个实例)

我们安排了三个用户连接到不同的实例,并通过端对端连接进行包含这三个用户的会议。

有时webRTC应用中的错误追寻和解决操作有点复杂。这时,你可以使用Firefox检查about:webrtc的页面。它将为你提供关于SDP会话、ICE candidates等及时信息。

注:WebRTC使用ICE框架来克服网络的复杂性。

在本地环境下做测试应该能顺利进行。在本地测试中,对等端会通过host候选来交换网络信息,也就是说ip地址就是远程对等端(同一网络内的所有对等端)的真实地址。请看一个UDP请求的小例子。

a=candidate:0 1 UDP 2122121471 198.167.1.138 54056 typ host
a=candidate:6 1 UDP 2122252543 fd8b:15c5:43b9:9m00:1c89:1vvc:2592:c9c6 54057 typ host
a=candidate:18 1 TCP 2105393407 192.168.1.130 9 typ host tcptype active

TCP candidate仅在 UDP 不可用或受到限制而不适合媒体流时使用

在实际环境中,信息交换通常通过来自STUN服务器的srflx和prflx候选者来完成,两个对等端会发现他们的公共IP地址和他们所处的NAT类型。大多数情况下,该交换只在连接设置过程中出现。因为只要建立了连接,媒体流会直接在对等端和视频网关之间流动。

a=candidate:1 1 UDP 1685921791 212.194.185.191 47355 typ srflx raddr 192.168.1.130 rport 54056


WebRTC网络架构

但在其他一些情况下,比如远程对等端的网络受限,这就需要使用TURN服务器和relay候选者。relay候选者的IP地址是TURN服务器在直接连接失败时用来转发两个对等端之间媒体的地址。

a=candidate:3 1 UDP 92086015 133.244.182.3 60795 typ relay raddr 134.219.114.1 rport 60795

由于其特性,你会发现实际操作中有很多公共STUN服务器(比如该列表中的服务器),因为媒体流通过服务器就意味着带宽消耗。

所以请记住,如果你没有提供合适的TURN服务器配置,所有在受限网络的连接都会失败(不论何种情况)。点击此处,你可以检查任何服务器的连接情况。

如果你对TURN服务器感兴趣,有一些类似coturn的开源工具可以帮你创建自己的服务器。

总结

上述所讲案例的潜力非常大,其中囊括了使用mesh结构建立多对多视频会议的所有必要步骤。这种拓扑结构对于创建简单案例(理想情况下,用户数少于4个)来说是个不错的选择,如果你真的想建立大规模的服务,MCU和SFU才是正确方法。你需要将大部分精力集中在服务器的实现上。

点击此处,你可以在这个库里找到所需的所有源代码。

文章地址:Multiparty video conference using Vue, WebRTC, SocketIO, Node, and Redis | by Adrián García Diéguez | Level Up Coding
原文作者:Adrián García Diéguez

推荐阅读
相关专栏
开源技术
106 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。