让我们用 JavaScript 和 WebRTC 构建一个视频聊天应用程序

2025-05-25

让我们用 JavaScript 和 WebRTC 构建一个视频聊天应用程序

第一部分:理解 WebRTC
这是我们 WebRTC 系列的第一篇教程。本系列旨在解释什么是 WebRTC,以及如何在 Web 应用中使用它。

在本部分中,我们将了解使用 WebRTC 开发应用程序所需的各种概念。

本系列教程

  1. 理解 WebRTC(本教程)
  2. 在代码中实现 WebRTC

介绍

如果您在 Covid-19 疫情之前不熟悉网络视频会议平台和应用程序,我们敢打赌您现在对它们已经了如指掌,并且一定使用过其中之一。

持续的 COVID-19 疫情导致在家办公的人数比以往任何时候都多,公司使用网络会议作为员工和客户之间沟通的主要方式。

由于全球大部分地区实施封锁和保持社交距离措施,即使是与家人朋友共度的时光,现在也基本上只能通过在线视频通话。从派对到商务会议,这些视频会议平台如今几乎人人都可使用。

本系列教程旨在讲解如何开发这样的视频会议 Web 应用。此外,本系列教程也必将为所有想要学习构建实时广告、多人游戏、直播、电子学习等应用的开发者提供帮助。

基础知识

在开始开发这样的网络应用程序之前,我们需要了解电子会议应用程序与简单的聊天网络应用程序有何不同。

在一个简单的聊天 Web 应用中,当两个浏览器需要互相发送消息时,它们通常需要一个服务器来协调和传递消息。但是,中间的服务器会导致浏览器之间的通信延迟。这种延迟几乎不会影响聊天应用的实用性。即使延迟(比如说)只有 5 秒,我们仍然可以使用这个聊天应用。

然而,对于视频会议应用来说,这种延迟非常明显。使用这样的应用与人交谈会非常困难。想象一下,你和别人说话,5秒后才能收到你的声音。你就能体会到这有多么烦人了。

因此,对于视频会议,我们需要浏览器之间的实时通信。如果我们消除中间的服务器,这种通信就有可能实现。这就是为什么我们必须使用WebRTC——一个开源框架,它通过简单的 API 为 Web 浏览器和移动应用程序提供实时通信。

WebRTC

WebRTC 是 Web 实时通信的缩写。它支持点对点通信,无需任何服务器介入,并允许连接的对等体之间交换音频、视频和数据。在 WebRTC 中,服务器的作用仅限于帮助两个对等体发现彼此并建立直接连接。 要从零开始构建一个不使用 WebRTC 的应用程序(需要点对点通信),您需要大量的框架和库来处理以下典型问题:
替代文本
替代文本

  • 数据丢失
  • 连接断开
  • NAT 穿越
  • 回声消除
  • 带宽自适应性
  • 动态抖动缓冲
  • 自动增益控制
  • 降噪和抑制

有了 WebRTC,所有这些功能都内置在浏览器中,开箱即用。WebRTC在后台自动处理所有这些问题。这项技术无需任何插件或第三方软件。它是开源的,其源代码可在http://www.webrtc.org/免费获取。

虽然大多数主流浏览器(例如 Chrome、Firefox 等)都已实现 WebRTC 框架,并向开发者开放了 WebRTC 的 API,但最好还是确认您的浏览器版本支持该框架。您可以在此处找到所有支持 WebRTC 的浏览器列表。

WebRTC API

WebRTC 由多个相互关联的 API 和协议组成,它们协同工作以实现实时通信。本系列教程中我们将使用的一些最重要的 API 是——点击链接查看演示

  • getUserMedia():捕获音频和视频。
  • MediaRecorder:录制音频和视频。
  • RTCPeerConnection:在用户之间传输音频和视频。
  • RTCDataChannel:用户之间的流数据。

信号

在两个对等体开始相互通信之前,他们需要了解彼此的大量信息,例如:

  • 是否有任何其他对等方可供通信。
  • 网络数据,例如外界看到的对等点的 IP 地址和端口
  • 会话控制消息——用于打开或关闭通信
  • 错误消息
  • 媒体元数据,例如对等方将发送的编解码器、编解码器设置、带宽和媒体类型
  • 用于建立安全连接的关键数据

如果您不明白以上信息的含义,请不要担心。重要的是要意识到,在建立直接连接之前需要交换大量信息。这些信息可以称为元数据。

信令是指协调初始通信并支持在对等体(浏览器)之间发送元数据的机制。因此,最初,对等体使用信令机制相互通信——主要用于发现其他对等体并共享在它们之间建立直接连接所需的信息。一旦建立了直接连接,信令就不再起作用。

记住——为了发送信号,我们需要一个服务器。

会话描述协议:-

  • WebRTC 并未指定信令机制(方法、协议等)。我们需要自行构建。(虽然这看起来似乎很复杂,但相信我们,其实并不复杂。在本系列文章中,我们将使用 Socket.IO 进行信令传输,但其实还有很多替代方案。)
  • WebRTC 仅要求在对等体之间交换上述媒体元数据,例如offer 和 answer。 offer 和 answer 以会话描述协议(SDP) 格式进行通信,如下所示: ...

    v=0
    o=- 7614219274584779017 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE audio video
    a=msid-semantic: WMS
    m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
    c=IN IP4 0.0.0.0

  • 如果您不明白上述格式中每一行的含义,不用担心。WebRTC 会根据您笔记本电脑/PC 上的音频/视频设备自动创建此格式。

那么 WebRTC 应用程序如何工作?

到目前为止,我们已经描述了什么是 WebRTC、什么是信令以及开发人员可以使用哪些 API。现在,让我们讨论一下所有这些是如何协同工作的。了解了这些之后,我们就可以开始编写代码了。

在我们讨论之前,您必须了解什么是 IP 地址和端口。

  • 每个连接到互联网的设备都使用 IP 地址进行识别。
  • 端口号用于标识互联网或其他网络消息到达设备后要转发到的特定进程。使用端口号可以将来自互联网的数据定向到设备内的正确位置。

因此,每个连接到互联网的设备都有一个 IP 地址和许多端口(通常为 65,536 个)。API
RTCPeerConnection 和信令:提供、应答和候选

正如我们之前讨论过的,RTCPeerConnectionWebRTC 的 API 用于在用户之间传输音频和视频。因此,信令与浏览器协同工作,RTCPeerConnection在浏览器之间建立直接连接。

初始化这个过程RTCPeerConnection有两个任务:

  • 确定本地媒体条件(音频和视频),例如分辨率和编解码器功能。这是用于“提供-应答”的元数据,通过信令发送。
  • 获取应用程序主机的潜在网络地址(称为候选地址)(由 IP 地址和端口号组成),这些地址也必须通过信令发送

一旦确定了本地数据,就必须通过信令机制与远程对等方进行交换。

假设艾米 (Amy)正试图给伯纳黛特 (Bernadette)打电话。以下是完整的通话/应答机制的详细说明:首先,我们来讨论一下他们将如何共享有关媒体条件的信息。

  1. 艾米创建了一个RTCPeerConnection对象。
  2. Amy 使用该方法创建了一个提议(SDP 会话描述)RTCPeerConnection createOffer()
  3. Amy 打电话setLocalDescription()将创建的 offer(Session Description)设置为将要创建的连接中的本地媒体的描述。
  4. 艾米 (Amy) 对报价进行细化,并使用信号机制将其发送给伯纳黛特 (Bernadette)。
  5. 伯纳黛特打电话setRemoteDescription()告知艾米的提议,以便她RTCPeerConnection了解艾米的安排。
  6. Bernadette 调用createAnswer()并向成功回调函数传递本地会话描述 - Bernadette 的答案。
  7. Bernadette 通过调用 将她的答案设置为本地描述setLocalDescription()
  8. 然后,伯纳黛特使用信号机制将她的字符串化答案发送给艾米。
  9. Amy 使用 将 Bernadette 的回答设置为远程会话描述setRemoteDescription()

现在,Amy 和 Bernadette 也需要交换网络信息。“查找候选节点”指的是使用 ICE 框架查找网络接口和端口(存在于对等节点上,并且可用于与对等节点建立直接连接)的过程

  1. Amy创建了一个RTCPeerConnection带有onicecandidate处理程序的对象。
  2. 当网络候选可用时,将调用该处理程序。
  3. 在处理程序中,Amy通过信令通道向Bernadette发送字符串化的候选人数据。
  4. Bernadette收到Amy发来的候选人消息时,她打电话addIceCandidate()将该候选人添加到远程对等点描述中。

WebRTC 支持ICE 候选点涓流 (ICE Candidate Trickling),它允许呼叫者在初始提议后逐步自动向被叫者提供候选点,并且被叫者无需等待所有候选点都到达即可自动开始通话并建立连接。如果您不了解ICE 候选点涓流 (ICE Candidate Trickling) ,也不用担心。重要的是,一旦对端创建了提议,WebRTC 就会自动创建 ICE 候选点(包含 IP 地址)。我们只需实现通过信令接收和发送这些候选点所需的方法即可。

一旦两个对等体之间共享了有关媒体条件和冰候选的信息,WebRTC 就会自动在对等体之间建立直接连接。

信令后——使用 ICE 应对 NAT 和防火墙

因此,您自然会期望每个 WebRTC 连接端点都拥有唯一的 IP 地址和端口号,以便与其他对等点交换信息,从而实现直接通信。 但事情并非如此简单。有两个因素可能会导致问题。在使用我们的网络会议应用程序之前,我们必须先处理好这两个因素。
替代文本

问题 1 — NAT

如果您熟悉计算机网络,您一定知道什么是 NAT。如果您不知道,也不用担心。我们将在这里进行解释:

你已经知道 IP 地址是什么了。它是一个用来标识连接到互联网的设备地址。从逻辑上讲,你会认为每个连接到互联网的设备都应该有一个唯一的 IP 地址。但事实并非如此。

IPv4 地址长度为 32 位,这意味着大约有 40 亿个唯一地址(2³² = 4,294,967,296)。截至 2018 年底,约有 220 亿台设备连接到互联网。那么,您一定会想——如果只有 40 亿个 IP 地址,那么 220 亿台设备如何连接到互联网?答案就是 NAT。

维护互联网的人员提出了以下解决方案——他们将整个 IPv4 地址范围分为两组——公共 IP 地址和私有 IP 地址。现在,每个公共 IP 地址只能分配给一台设备,但私有 IP 地址则不然。请参见下图了解更多信息。 在上图中,每个路由器都有两个 IP 地址——一个公共 IP 地址(面向服务器)和一个私有 IP 地址(面向内部网络)。因此,如果内部网络 1 中的任何设备向服务器发送请求,服务器将看到来自同一 IP 地址(即 180.190.104.50)的请求。
替代文本

因此,这意味着每个路由器将一个公网 IP 地址映射到设备的多个私有 IP 地址。这也意味着每个设备(笔记本电脑、PC、智能手机)只知道自己的私有 IP 地址,而不知道路由器的公网 IP 地址。(此外,如果您在 Google 上搜索“我的 IP 地址”,Google 会告诉您(您所连接的)路由器的公网 IP 地址,因为 Google 看到的是路由器的公网 IP 地址,而不是您的私有 IP 地址。)

因此,在某种程度上,我们可以说每个设备都有两个 IP 地址——一个私有 IP 地址(分配给设备)和一个公共 IP 地址(分配给设备连接到的路由器)。

这可能会给 WebRTC 带来问题,因为网络 ICE 候选地址(由浏览器生成)包含设备的私有 IP 地址,而不是公网 IP 地址。因此,我们必须找到一种方法让浏览器获取公网 IP 地址,以便它可以创建包含公网 IP 地址的候选地址。解决方案是使用 STUN(NAT 会话遍历实用程序)服务器。当设备向 STUN 服务器发出请求时,STUN 服务器会回复一条消息,其中包含该设备所连接路由器的公网 IP。通过这种方式,STUN 服务器可以帮助浏览器生成候选地址。

我们将在本教程的后面看到如何将 STUN 与 WebRTC 集成。

问题 2 — 防火墙

实际上,大多数设备都位于一层或多层防火墙之后,这些防火墙类似于会阻止某些端口和协议的杀毒软件。防火墙和 NAT 实际上可能由同一设备实现,例如家用 WIFI 路由器。由于 WebRTC 使用许多非标准端口,某些防火墙不允许两个浏览器之间建立直接连接。 因此,为了解决这个问题,我们需要一个 TURN(使用中继 NAT 进行穿越)服务器。TURN 服务器本质上充当中继服务器,即在直接(点对点)连接失败时,直接在两个对等体之间中继流量。下图说明了解决方案
替代文本

替代文本

正如我们之前讨论过的,在使用 WebRTC 建立点对点连接时,我们需要使用 STUN 和 TURN 服务器。要将 TURN 和 STUN 与 WebRTC 集成,我们只需将包含 TURN 和 STUN 服务器 URL 的对象作为参数传递给 RTCPeerConnection() 即可。以下代码演示了如何实现:



//Object containing TURN/STUN URLs.
var pcConfig = {
  'iceServers': [
    {
    'urls': 'stun:stun.l.google.com:19302'
    },
    {
    'urls': 'turn:192.158.29.39:3478?transport=udp',
    'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
    'username': '28224511:1379330808'
    },
    {
    'urls': 'turn:192.158.29.39:3478?transport=tcp',
    'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
    'username': '28224511:1379330808'
    }
  ]
}........
//Passing the above object to RTCPeerConnection
RTCPeerConnection(pcConfig);


Enter fullscreen mode Exit fullscreen mode

如上所示,我们只需传递 URL。WebRTC 会负责处理其他所有事情。

下图说明了 WebRTC 呼叫期间建立的所有连接注意:-
替代文本

  • 大多数情况下,仅使用 STUN 服务器即可成功建立连接,无需 TURN 服务器。只有少数情况下,您需要使用 TURN 服务器才能成功通话。
  • 有些公司(如 XirSys)提供免费的 TURN/STUN 服务器。

恭喜

你已经完成了本教程。现在你一定对 WebRTC 的工作原理有了清晰的了解。

到目前为止,我们仅描述了当一个人(在我们的示例中为Amy)尝试使用 WebRTC 呼叫另一个人(在我们的示例中为 Bernadette)时发生的一系列事件。如果您想了解如何在代码中实现所有这些功能,请继续关注我们。我们将在下一个教程中编写并解释实现所有这些概念的代码。

继续阅读第二部分

来源

文章来源:https://dev.to/nilmadhabmondal/let-s-build-a-video-chat-app-with-javascript-and-webrtc-380b
PREV
2021 年需要掌握的 15 项高级 CSS 技术
NEXT
Redux Toolkit - 编写 Redux 的标准方法