2

摘要

介于英语考试在即,当前工作需要,特意找了这本书,原书《Learning_WebRTC_Develop_interactive》,之前好久都没有中文版,执意决定自行翻译。后来在豆瓣阅读找到了翻译版,有需要者可以移步购买Learning WebRTC中文版
说实话,WebRTC没有网络基础是不太容易消化理解,这本书的好处是重实践,在实践成果中慢慢理解深奥的计算机网络。2017年著作,其中一些api相继废弃,本人在原书pdf中做了修改批注,有需要的联系641007257@qq.com
第三章是一个核心篇章,所以决定从此开始翻译,前面两章是基础,比较简单,后续会补上翻译文,大概计划与本年底翻译完整本。

能力不高,技术有限,文章仅供参考,勿喷!!!

创建一个基础应用

理解udp实时传输

​ webrtc应用首选udp传输的原因是,它提供了高性能传输,不用像tcp那样控制数据的准确性。目前,大多数web应用都建立在tcp协议之上,原因在于它为用户提供保证,其中一些特性如下:

  • 发送的所有数据都会接收到确认
  • 数据一旦发送失败,将停止继续发送并重新发送失败的数据
  • 确保数据唯一性

​ 今天,大多数的web应用选择tcp,也正式基于这些特性。假如,你发送一个html页面,让所有数据以正确的顺序排列并确保它到达另一方是有意义的。但是,这一技术不否和所有的用户场景。例如,在多人游戏中传输数据。视频游戏中的大多数数据在几秒钟甚至更短时间内变得陈旧,这就意味着用户只关心过去几秒内发生的事情,仅此而已。如果需要保证每一条数据都能到达另一方,那么当数据丢失时,这可能会导致很大的瓶颈。

clipboard.png

​ 因为tcp约束的原因,使得webrtc的开发人员首选udp作为他们的传输方式。webrtc的音频和视频并不需要的可靠的连接,而是,需要高性能传输。我们可以接收丢包,从而,也意味着,对于这类的应用程序来说,udp是一个更好的选择。

​ UDP通过做出大量的不保证来实现这种情况,它被构建成一个不太可靠的传输层,对您发送的数据进行较少的假设。upd不可靠传输的原因如下:

  • 不确定数据发送给对方的顺序
  • 传输过程中可能丢包,不能确保每个数据包都能准确无误的发送给对方
  • 不跟踪数据包的状态,即时客户端丢失数据,也不会停止发送数据

现在,webrtc可以以尽快的方式发送音频和视频,这也透漏出webrtc是一个多么复杂的主题。并不是每个网络都允许udp流量通过,具有企业防火墙的大型网络可以直接阻止UDP流量,以防止恶意连接。这些连接必须沿着与今天大多数网页下载不同的路径传播。必须围绕UDP构建许多变通方法和流程,以使其适用于广泛的受众。在WebRTC技术方面,这只是冰山一角。在接下来的几节中,我们将介绍在浏览器中启用WebRTC的其他支持技术。

webrtc接口

接下来的几节将介绍目前在浏览器中实现的WebRTC API。这些函数和对象允许开发人员与WebRTC层进行通信,并与其他用户建立对等连接。它由几个主要技术组成:

  • The RTCPeerConnection object
  • Signaling and negotiation
  • Session Description Protocol (SDP)
  • Interactive Connectivity Establishment (ICE)
The RTCPeerConnection object

RTCPeerConnection是webrtc api的主入口点。提供初始化连接、对等连接、赋值媒体信息。它处理与另一个用户的UDP连接的创建。现在我们开始熟悉这个api,因为在本书的其余部分将会多次出现。

RTCPeerConnection的主要作用是在浏览器中维护对等连接的会话和状态。它还处理对等连接的设置和创建。它封装了所有这些内容,并公开了一组在连接过程中的关键点被触发的事件。通过这些事件,您可以访问对等连接期间发生的配置和内部信息:

clipboard.png

RTCPeerConnection对象是浏览器中的一个简单对象,可以使用新的构造函数进行实例化,如下所示:

var myConnection = new RTCPeerConnection(configuration);
myConnection.onaddstream = function (stream) {
 // Use stream here
};

连接接受配置对象,我们将在本章后面介绍。在示例中,我们还为onaddstream事件添加了一个处理程序。
当远程用户将视频或音频流添加到其对等连接时,会触发此操作。我们还将在本章后面介绍这一点。

信令和协商

​ 通常,连接到另一个浏览器需要知道浏览器在网络上的位置。通常情况下是采用ip地址和端口号的方式寻找目的主机。你的计算机或移动设备的IP地址允许其他启用Internet的设备直接在彼此之间发送数据; 这就是RTCPeerConnection的基础。一旦这些设备知道如何在互联网上找到彼此,他们还需要知道如何相互交谈。这意味着交换有关每个设备支持的协议以及音视频编解码器等有关数据。

​ 这也意味着,为了连接到另一个用户,你需要更多的了解他们。一种可能的解决方案是在你的计算机上存储可以连接到的用户的列表。要启用与其他用户的通信,您只需交换联系信息,让WebRTC处理其余的信息。然而,这有一个弊端,你必须和你需要连接的用户手动共享信息。你必须维护一个您想要连接的任何用户的大列表,并通过其他通信渠道交换信息。使用WebRTC,我们可以使这一过程自动化。

​ 幸运的是,我们今天使用的大多数Web通信应用程序中解决了这个问题。要与Facebook或LinkedIn等热门服务上的任何人联系,您只需要知道他们的名字并搜索他们。然后,你可以将他们添加到已知联系人列表中,并随时访问其信息。

​ 这一过程就是WebRTC中的信令和协商。

信令过程包括几个步骤:

  1. 生成对等连接的潜在候选列表。
  2. 用户或算法将选择用户进行连接。
  3. 信令层将通知该用户有人想要与他/她联系,并且他/她可以接受或拒绝。
  4. 通知第一个用户接收要约连接。
  5. 如果接受,第一个用户将与另一个用户启动RTCPeerConnection
  6. 用户都将通过信令信道交换有关其计算机的硬件和软件信息。
  7. 两个用户还将通过信令信道交换关于他们的计算机的位置信息。
  8. 用户之间的连接成功或失败。

​ 然而,这只是WebRTC信令可能发生的一个例子。实际上,WebRTC规范没有包含关于两个用户如何交换信息的任何标准。这是由于不断增加的连接用户标准列表。今天存在许多标准,甚至在信号和谈判过程中创造了更多标准。WebRTC标准作者决定,试图就一个标准达成一致意见将阻止它向前发展。

​ 这本书中,我们将建立自己的信令和协商履行条约。编写一个可以在两个浏览器之间传输信息的简单服务器。虽然它很简单并且容易出现安全漏洞,但它应该让你很好地理解这个过程在WebRTC中是如何工作的。同时,你也可以探究其他公司提供的信令方案。有数百种信令和谈判解决方案,每天都有更多的信息和协商解决方案。有些集成了当前的电话或基于聊天的实现,例如XMPPSIP,有些还提出了一种全新的信令方式。

会话描述协议

​ 要与其他用户建立联系,您需要先了解一下这些用户。关于另一个客户端的一些最重要的事情是他们支持的音频和视频编解码器,他们的网络带宽,以及他们的计算机可以处理多少数据,而且还需要在客户之间轻松传输。由于我们没有指定如何传输这些数据,因此它也应该能够通过多种类型的传输协议进行传输。这意味着我们需要一个基于字符串的名片,其中包含我们可以发送给其他用户的所有用户信息。
SDP正好为我们提供了这样的功能。

SDP的伟大之处在于它已经存在了很长时间,可以追溯到90年代末的第一次初稿。这意味着SDP是一种在客户端之间建立基于媒体的连接的可靠方法。在WebRTC之前,它已被用于许多其他类型的应用程序,例如电话和基于文本的聊天。这就意味着在实现和应用方面有很多好的资源。SDP是浏览器提供的基于字符串的blob数据。这个字符串格式是一组由换行符分割的键值对:

<key>=<value>\n

键是一个单一字符,用于建立这个类型的值。该值是一组结构化文本,包含机器可读配置值。然后通过换行符分割不同的键值对。SDP将涵盖给定用户的描述,时序配置和媒体约束。在与用户建立连接的过程中,RTCPeerConnection对象给出了SDP。当我们在本章后面开始使用RTCPeerConnection对象时,您可以轻松地将其打印到JavaScript控制台。这将允许您准确查看SDP中包含的内容,如下所示:

v=0
o=- 1167826560034916900 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1
m=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:Vl5FBUBecw/U3EzQ
a=ice-pwd:OtsNG6FzUH8uhNEhOg9/hprb
a=ice-options:google-ice
a=fingerprint:sha-256 
FB:56:7D:B6:E0:C7:E7:39:FE:47:5A:12:6C:B4:4E:0E:2D:18:CE:AE:33:92: 
A9:60:3F:14:E4:D9:AA:0D:BE:0D
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendrecv
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 
inline:zE+3pkUbJyFG4UmmvPxG/OFC4+QE24X8Zf3iOSCf
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
a=ssrc:4274470304 cname:+j4Ma6UfMsCcQCWK
a=ssrc:4274470304 msid:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1 
a1751f6b-98de-469b-b6c0-81f46e19009d
a=ssrc:4274470304 mslabel:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1
a=ssrc:4274470304 label:a1751f6b-98de-469b-b6c0-81f46e19009d
m=video 1 RTP/SAVPF 100 116 117
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:Vl5FBUBecw/U3EzQ
a=ice-pwd:OtsNG6FzUH8uhNEhOg9/hprb
a=ice-options:google-ice
a=fingerprint:sha-256 FB:56:7D:B6:E0:C7:E7:39:FE:47:5A:12:6C:B4:4E:0E:
2D:18:CE:AE:33:92: 
A9:60:3F:14:E4:D9:AA:0D:BE:0D
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send￾time
a=sendrecv
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 
inline:zE+3pkUbJyFG4UmmvPxG/OFC4+QE24X8Zf3iOSCf
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=ssrc:3285139021 cname:+j4Ma6UfMsCcQCWK
a=ssrc:3285139021 msid:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1 
bd02b355-b8af-4b68-b82d-7b9cd03461cf
a=ssrc:3285139021 mslabel:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1
a=ssrc:3285139021 label:bd02b355-b8af-4b68-b82d-7b9cd03461cf

这是在会话启动过程中从我自己的机器上获取的。如您所见,生成的代码第一眼看上去很复杂。它首先确定IP地址的连接。然后,设置有关请求的基本信息,例如我是否请求音频、视频还是两者。接下来,它会设置一些音频信息,包括加密类型和ICE配置等主题。它还以相同的方式设置视频信息。

最后,我们的目标不是理解每一行代码,而是理解SDP的用途。在本书的学习过程中,您永远不必直接使用它,但可能需要在将来的某个时候使用它。总之,SDP扮演一个业务名片,供其他用户与您建立连接的时候使用。SDP与信令和协商相结合,是对等连接的前半部分。在接下来的几节中,我们将介绍在两个用户知道如何找到对方之后会发生什么。

参考
P2P通信标准协议(三)之ICE

寻找另一个用户的明确线路

今天大多数网络的很大一部分是安全性。您正在使用的任何网络都有几层访问控制,告诉您的数据在何处以及如何发送。这意味着连接到另一个用户需要找到一条清晰的路径,不仅仅是您自己的网络,还有其他用户的网络。WebRTC内部涉及多种技术:

  • NAT会话遍历(STUN)
  • NAT中继设备遍历(TURN)
  • 交互式连接建立(ICE)

这些涉及许多服务器和连接,以便WebRTC正确使用。要了解它们的工作原理,我们首先应该可视化典型WebRTC连接过程的布局:

clipboard.png
首先是找出你的IP地址。所有连接到Internet的设备都有一个IP地址,用于标识它们在Web上的位置。这是将数据包定向到正确目的地的方法。在局域网内查找IP地址时会出现问题。路由器隐藏计算机的IP地址并将其替换为另一个,以提高安全性并允许多台计算机使用相同的网络地址。通常,您可以在自己,网络路由器和公共Internet之间拥有多个IP地址。

NAT会话遍历

STUN是在两个对等体之间找到良好连接的第一步。它有助于识别Internet上的每个用户,并且旨在由其他协议用于建立对等连接。它首先向服务器发出请求,使用STUN协议启用。然后,服务器识别发出请求的客户端的IP地址,并将其返回给客户端。然后,客户端可以使用给定的IP地址标识自己。

​ 使用STUN协议需要启用STUN的服务器才能连接。目前,在Firefox和Chrome中,默认服务器直接由浏览器供应商提供。这非常适合快速启动并运行测试。

参考
P2P通信标准协议(一)之STUN

NAT中继设备遍历

在某些情况下,防火墙可能限制性太强,不允许任何基于STUN的流量到其他用户。在企业NAT中可能就是这种情况,它利用端口随机化来允许比通常发现的数千个设备更多的设备。在这种情况下,我们需要一种与另一个用户连接的不同方法。 这就是TURN

​ 这种方式是在客户端之间添加一个中继,代表客户端充当对等连接。然后,客户端从TURN服务器获取其信息,就像通过向服务器发出请求从流行的视频服务流式传输视频一样。这要求TURN服务器下载,处理和重定向每个客户端发送给它的每个数据包。这就是为什么使用TURN在制作WebRTC连接时通常被认为是最后的手段,因为设置高质量的TURN服务的成本很高。

​ 关于STUN与TURN的使用有很多不同的统计数据,但它们似乎都指向了相同的结论 - 大部分时间,没有TURN你的用户就会没事。将WebRTC与STUN一起使用将适用于大多数网络配置。在设置您自己的WebRTC服务时,最好跟踪此信息并自行决定使用TURN服务的成本是否值得。

参考
P2P通信标准协议(二)之TURN

交互式连接建立

​ 现在我们已经介绍了STUN和TURN,我们可以通过另一个名为ICE的标准来了解它是如何组合在一起的。利用STUN和TURN为对等连接提供成功路由的过程。它的工作原理是找到每个用户可用的一系列地址,并按排序顺序测试每个地址,直到找到适合两个客户端的组合。

​ ICE的过程从不对每个用户的网络配置做出假设开始。它将逐步通过一系列步骤来发现每个客户端的网络是如何设置的。此过程将使用不同的技术集来执行此操作。目标是发现有关每个网络的足够信息,以建立成功的连接。

​ 通过使用STUN和TURN找到每个ICE候选者。如果连接失败,它将查询STUN服务器以查找外部IP地址并将TURN服务器的位置附加为备份。每当浏览器找到新候选者时,它通知客户端应用程序它需要通过信令信道发送ICE候选者。在找到并测试了足够的地址并建立连接后,该过程终于结束了。

参考
P2P通信标准协议(三)之ICE

构建一个基本的WebRTC应用程序

​ 现在我们已经很好地理解了WebRTC的使用方式,我们将构建我们的第一个支持WebRTC的应用程序。到本章结束时,您将拥有一个可用的WebRTC网页,您可以在其中查看实际使用的技术。我们将把我们刚刚介绍的所有信息提取到一个易于开发的示例中。我们将涵盖:

  • 创建RTCPeerConnection
  • 创建SDP服务和响应
  • 寻找ICE对等候选人
  • 创建一个成功的WebRTC连接
创建RTCPeerConnection

​ 不幸的是,我们现在创建的不是一个完全的对等应用程序,摄像头只能捕获到自己。我们在本章中的目的是将浏览器窗口连接到自身,从用户的摄像头流式传输视频数据。最终目标是在页面上获得两个视频流,一个直接来自摄像头,另一个来自浏览器在本地制作的WebRTC连接。

​ 虽然这并不完全有用,但它可以使代码更具可读性和便于理解,从而帮助我们。我们将在稍后学习如何使用服务器进行远程连接。由于我们连接的环境是我们本地的浏览器,因此我们不必担心网络不稳定或创建服务器。完成项目后,您的应用程序应如下所示:

clipboard.png

正如你所看到的,除了我英俊的面孔,这个例子是非常基本的。首先,我们将采用与第2章“获取用户媒体”中创建的第一个示例类似的步骤。您需要创建另一个HTML页面并使用本地Web服务器托管它。在第2章获取用户媒体中参考“获取媒体设备访问权限”部分中的“设置静态服务器”小节可能是一个好主意,并查看如何设置开发环境。

​ 我们将采取的第一步是创建一些处理多个浏览器支持的函数。这些将能够告诉我们当前的浏览器是否支持我们需要使用的功能来使我们的应用程序工作。它还将规范化API,确保我们始终可以使用相同的功能,无论我们运行什么浏览器。

​ 要开始使用,请使用JavaScript源文件设置新网页。我们的HTML页面上将包含两个视频元素,一个用于第一个客户端,另一个用于第二个客户端:

<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8" />
 <title>Learning WebRTC - Chapter 4: Creating a 
 RTCPeerConnection</title>
 </head>
 <body>
 <div id="container">
 <video id="yours" autoplay></video>
 <video id="theirs" autoplay></video>
 </div>
 <script src="main.js"></script>
 </body>
</html>

如果您经常做HTML5网页,那么此页面的html和head标签应该很熟悉。这是任何符合HTML5的页面的标准格式。
有很多不同的样板模板用于创建页面,而这个模板是我觉得最简单的,同时还能完成工作。只要视频元素存在,就没有什么会彻底改变我们的应用程序的工作方式,所以如果你需要对这个文件进行更改,请随意这样做。

您会注意到两个标记为您和他们的视频元素。这些将是我们的两个视频源,将模拟连接到另一个同伴。在本章的其余部分中,您将被视为启动连接的本地用户。
其他用户 - 他们的 - 将被视为我们正在进行WebRTC连接的远程用户,即使他们并非物理上位于其他地方。

​ 最后,包括我们的脚本功能。请始终牢记在HTML页面的末尾添加此内容。这可以保证正文中的元素可以使用,并且页面已完全加载,以便JavaScript与之交互。
接下来,我们将创建我们的JavaScript源代码。创建一个名为main.js的新文件,并使用以下代码开始填写它:

function hasUserMedia() {
 navigator.getUserMedia = navigator.getUserMedia || 
 navigator.webkitGetUserMedia || navigator.mozGetUserMedia || 
 navigator.msGetUserMedia;
 return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
 window.RTCPeerConnection = window.RTCPeerConnection || 
 window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
 return !!window.RTCPeerConnection;
}

​ 现在我们可以告诉用户支持哪些API,让我们继续使用它们。接下来的几个步骤也应该非常熟悉。我们将重复第2章“获取用户媒体”中的“约束媒体流”部分中遇到的一些功能,以获取用户的相机流。在我们对WebRTC做任何事情之前,我们应该从用户那里获取本地摄像头流。这可以确保用户准备好创建对等连接,并且在建立对等连接之前,我们不必等待用户接受摄像头共享。

我们在WebRTC中构建的大多数应用程序都将经历一系列状态。让WebRTC工作最困难的部分是按正确的顺序做事。如果一个步骤发生在另一个步骤之前,它可以快速分解应用程序。这些状态是阻塞的,这意味着我们不能在没有完成前一个状态的情况下进入下一个状态。以下是我们的应用程序如何工作的概述:

clipboard.png

首先,我们需要获取用户媒体。确保用户接受视频和音频的媒体流正常。

​ 接下来,我们创建对等连接。在断开连接之前启动进程。这是我们可以使用我们想要使用的ICE服务器配置WebRTC连接的地方。此时,浏览器闲置着等待连接过程开始。

​ 当其中一个用户创建offer时,应用就正式开始了。这使浏览器开始行动,并开始准备与另一个用户建立对等连接。提议和响应是本章讨论的信令过程的一部分。

​ 同时,浏览器还将寻找其他对等方可以连接的候选端口和IP组合。它将在一段时间内继续执行此操作,直到可以建立连接或连接失败。完成此操作后,WebRTC连接过程结束,两个用户可以开始共享信息。

​ 下一段代码将捕获用户的摄像头并使其在我们的流变量中可用。您现在可以在JavaScript中的两个函数定义之后添加以下代码:

var yourVideo = document.querySelector("#yours"),
    theirVideo = document.querySelector("#theirs"),
    yourConnection, theirConnection;
if (hasUserMedia()) {
    navigator.getUserMedia({ video: true, audio: false }, function
        (stream) {
        yourVideo.srcObject = stream;
        if (hasRTCPeerConnection()) {
            startPeerConnection(stream);
        } else {
            alert("Sorry, your browser does not support WebRTC.");
        }
    }, function (error) {
        alert("Sorry, we failed to capture your camera, please try again.");
    });
} else {
    alert("Sorry, your browser does not support WebRTC.");
}

​ 第一部分从文档中选择我们的视频元素,并设置一些我们将在接下来使用的变量。我们假设浏览器此时支持querySelector API。然后,我们检查用户是否可以访问getUserMedia API。如果他们不这样做,我们的程序将停在此处,并提醒用户他们不支持WebRTC

​ 如果成功,我们会尝试从用户那里获取相机。这是一种异步操作,因为用户必须同意共享他们的相机。如果成功,我们将本地视频的流设置为用户的流,以便他们可以成功地看到这一点。如果失败,我们会通知用户错误并停止该过程。

​ 最后,我们检查用户是否可以访问RTCPeerConnection API。如果是这样,我们调用将启动连接过程的函数(这将在下一节中定义)。如果没有,我们停在这里并再次通知用户。

​ 下一步是实现上一节中调用的startPeerConnection函数。此函数将创建我们的RTCPeerConnection对象,设置SDP offer和响应,并找到两个对等体的ICE候选对象。
​ 现在我们为两个对等体创建RTCPeerConnection对象。将以下内容添加到JavaScript文件中:

function startPeerConnection(stream) {
 var configuration = {
 // Uncomment this code to add custom iceServers
 //"iceServers": [{ "url": "stun:stun.1.google.com:19302" }]" 
}]
 };
 yourConnection = new webkitRTCPeerConnection(configuration);
 theirConnection = new webkitRTCPeerConnection(configuration);
};

​ 在这里,我们定义函数来创建连接对象。在配置对象中,您可以传递要在应用程序中使用的ICE服务器的参数。要使用自建ICE服务器,只需取消注释代码并更改值即可。浏览器将自动获取配置并在进行对等连接时使用它。此时,这不是必需的,因为浏览器应该具有一组默认的ICE服务器。在此之后,我们创建两个对等连接对象来代表我们应用程序中的每个用户。仍请记住,我们的两个用户都将在此应用程序的同一浏览器窗口中。

创建SDP offer和响应

​ 在本节中,我们将执行offer和响应应答程序以建立对等连接。我们的下一个代码块将在两个对等体之间设置提供和响应应答流:

function startPeerConnection(stream) {
 var configuration = {
// Uncomment this code to add custom iceServers
 //"iceServers": [{ "url": "stun:stun.1.google.com:19302" }]
 };
 yourConnection = new webkitRTCPeerConnection(configuration);
 theirConnection = new webkitRTCPeerConnection(configuration);
 // Begin the offer
 yourConnection.createOffer(function (offer) {
 yourConnection.setLocalDescription(offer);
 theirConnection.setRemoteDescription(offer);
 theirConnection.createAnswer(function (offer) {
 theirConnection.setLocalDescription(offer);
 yourConnection.setRemoteDescription(offer);
 });
 });
};

​ 你可能注意到的一件事是,经过整整一章的解释,这段代码看起来相当简单。这是因为两个对等体都在同一个浏览器窗口中。这样,我们可以保证其他用户何时获得offer,而不必执行许多异步操作。

​ 以这种方式实现offer/answer机制使其更容易理解。您可以清楚地看到所需的步骤以及成功创建对等连接所需的顺序。如果您使用附加到浏览器的调试工具,则可以执行这些步骤并在每个步骤检查RTCPeerConnection对象,以确切了解发生的情况。

​ 在下一章中,我们将更深入地探讨这个主题。通常,您要连接的另一个对等体不在同一个浏览器中 - 这意味着需要服务器来连接浏览器窗口之间的对等体。这使得这个过程变得更加复杂,因为这些步骤不仅需要按照这里显示的确切顺序进行,而且还要跨多个浏览器窗口进行。这需要在可能有时不稳定的环境中进行大量同步操作。

寻找ICE候选

​ 建立对等连接的最后一部分是在对等体之间传输ICE候选者,以便它们可以相互连接。您现在可以将startPeerConnection函数更改为如下所示:

function startPeerConnection(stream) {
 var configuration = {
 // Uncomment this code to add custom iceServers
 //"iceServers": [{ "url": "stun:127.0.0.1:9876" }]
 };
 yourConnection = new webkitRTCPeerConnection(configuration);
 theirConnection = new webkitRTCPeerConnection(configuration);
 // Setup ice handling
 yourConnection.onicecandidate = function (event) {
 if (event.candidate) {
 theirConnection.addIceCandidate(new 
RTCIceCandidate(event.candidate));
 }
 };
 theirConnection.onicecandidate = function (event) {
 if (event.candidate) {
 yourConnection.addIceCandidate(new 
RTCIceCandidate(event.candidate));
 }
 };
 // Begin the offer
 yourConnection.createOffer(function (offer) {
 yourConnection.setLocalDescription(offer);
theirConnection.setRemoteDescription(offer);
 theirConnection.createAnswer(function (offer) {
 theirConnection.setLocalDescription(offer);
 yourConnection.setRemoteDescription(offer);
 });
 });
};

​ 您可能会注意到这部分代码完全由事件驱动。这是由于找到ICE候选者的异步性质。浏览器将不断寻找候选者,直到它找到了认为可以创建对等连接或建立稳定对等连接的数量。
​ 在接下来的章节中,我们将构建实际通过信令通道发送此数据的功能。需要注意的一点是,当我们从他们的连接中获得ICE候选者时,我们将其添加到yourConnection,反之亦然。我们必须通过互联网才能连接到不在同一地方的用户。

添加和修饰数据流

​ 使用WebRTC可以轻松地将流添加到对等连接。API负责设置流并通过网络发送数据的所有工作。当其他用户将流添加到其对等连接时,将通过该连接发送此通知,通知第一个用户该更改。然后,浏览器调用onaddstream通知用户已添加流:

// Setup stream listening
 yourConnection.addStream(stream);
 theirConnection.onaddstream = function (e) {
 theirVideo.srcObject = e.stream;
 };

​ 然后,我们可以通过为流的位置创建对象URL,将此流添加到本地视频。这样做是创建一个标识浏览器中的流的值,以便视频元素可以与之交互。这充当我们视频流的唯一ID,告诉视频元素播放来自本地流的视频数据作为源。
​ 最后,我们将为应用程序添加一些样式。视频通信应用程序最流行的风格是Skype等应用程序中常见的风格。今天,许多使用WebRTC构建的演示都复制了这一点。通常,您呼叫的人位于应用程序的前面和中间,而您自己的摄像头显示为较大的一个小窗口。由于我们正在构建一个Web页面,因此可以通过一些简单的CSS实现,如下所示:

<style>
 body {
 background-color: #3D6DF2;
 margin-top: 15px;
 }
video {
 background: black;
 border: 1px solid gray;
 }
 #container {
 position: relative;
 display: block;
 margin: 0 auto;
 width: 500px;
 height: 500px;
 }
 #yours {
 width: 150px;
 height: 150px;
 position: absolute;
 top: 15px;
 right: 15px;
 }
 #theirs {
 width: 500px;
 height: 500px;
 }
 </style>

​ 只需将其添加到您的HTML页面,您就应该有一个良好的WebRTC应用程序。此时,如果您仍然认为我们的应用程序看起来很单调,请随时继续为应用程序添加样式。我们将在接下来的章节中对此进行构建,并且使用一些CSS做一些更令人兴奋的演示。

运行您的第一个WebRTC应用程序

​ 现在,运行您的网页进行测试。当您运行该页面时,它应该要求您与浏览器共享您的相机。一旦接受,它将启动WebRTC连接过程。浏览器迅速完成我们到目前为止讨论的步骤,并创建一个连接。然后,您应该看到自己的两个视频,一个来自您的相机,另一个是通过WebRTC连接进行流式传输。

作为参考,以下是此示例中代码的完整列表。以下是我们的index.html文件中的代码:

<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8" />
 <title>Learning WebRTC - Chapter 4: Creating a 
RTCPeerConnection</title>
 <style>
 body {
 background-color: #3D6DF2;
 margin-top: 15px;
 }
 video {
 background: black;
 border: 1px solid gray;
 }
 #container {
 position: relative;
 display: block;
 margin: 0 auto;
 width: 500px;
 height: 500px;
 }
#yours {
 width: 150px;
 height: 150px;
 position: absolute;
 top: 15px;
 right: 15px;
 }
 #theirs {
 width: 500px;
 height: 500px;
 }
 </style>
 </head>
 <body>
 <div id="container">
 <video id="yours" autoplay></video>
 <video id="theirs" autoplay></video>
 </div>
 <script src="main.js"></script>
 </body>
</html>

下面是mian.js文件内容:

function hasUserMedia() {
    navigator.getUserMedia = navigator.getUserMedia ||
        navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
        navigator.msGetUserMedia;
    return !!navigator.getUserMedia;
}

function hasRTCPeerConnection() {
    window.RTCPeerConnection = window.RTCPeerConnection ||
        window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
    return !!window.RTCPeerConnection;
}

var yourVideo = document.querySelector("#yours"),
    theirVideo = document.querySelector("#theirs"),
    yourConnection, theirConnection;
if (hasUserMedia()) {
    navigator.getUserMedia({video: true, audio: false}, function (stream) {
        yourVideo.srcObject = stream;
        if (hasRTCPeerConnection()) {
            startPeerConnection(stream);
        } else {
            alert("Sorry, your browser does not support WebRTC.");
        }
    }, function (error) {
        console.log(error);
    });
} else {
    alert("Sorry, your browser does not support WebRTC.");
}

function startPeerConnection(stream) {
    var configuration = {
        "iceServers": [{"url": "stun:stun.1.google.com:19302"}]
    };
    yourConnection = new webkitRTCPeerConnection(configuration);
    theirConnection = new webkitRTCPeerConnection(configuration);
    // Setup stream listening
    yourConnection.addStream(stream);
    theirConnection.onaddstream = function (e) {
        theirVideo.srcObject = e.stream;
    };
    // Setup ice handling
    yourConnection.onicecandidate = function (event) {
        if (event.candidate) {
            theirConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
        }
    };
    theirConnection.onicecandidate = function (event) {
        if (event.candidate) {
            yourConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
        }
    };
    // Begin the offer
    yourConnection.createOffer(function (offer) {
        yourConnection.setLocalDescription(offer);
        theirConnection.setRemoteDescription(offer);
        theirConnection.createAnswer(function (offer) {
            theirConnection.setLocalDescription(offer);
            yourConnection.setRemoteDescription(offer);
        });
    });
};

自测题

Q1: UDP适合WebRTC连接是因为它不能确保数据的准确性,对还是错?

Q2: WebRTC标准的信令和协商部分是由浏览器完全完成,对还是错?

Q3: 以下描述SDP正确的是:

  1. 一个WebRTC的配置文件
  2. 弄清楚支持视频编码的一种方式
  3. 你电脑的一个商业名片
  4. 一个迷惑的没人理解的技术文档

Q4: 交互式连接建立(ICE)有助于在典型的网络设置中找到两个客户端之间清晰的路径。对或错?

Q5: 下列关于TURN说法不正确的是?

  1. 它需要比普通连接更多的带宽和处理能力
  2. NAT穿透,TURN应该是最好的连接方式
  3. TURN服务器必须处理客户端之间发送的每个数据包
  4. TURN方法由浏览器提供

总结

​ 恭喜你做到这一点。如果您已成功完成本章,那么您就可以开始制作更大的WebRTC应用程序了。本章的目标不仅是创建WebRTC应用程序,还要了解在流程的每个步骤中发生的情况。

​ 在本章之后,应该已经清楚WebRTC是一项复杂的技术。我们介绍了WebRTC内部工作的大量信息。虽然现在不需要了解WebRTC如何在浏览器中实现,但了解主要部分如何协同工作将有助于您理解将要到来的示例。

​ 在本章中,我们介绍了如何在浏览器中创建对等连接的内部工作方式。我们介绍了支持UDPSDPICE的几种技术。您现在应该对两个浏览器如何找到彼此以及如何通过Internet进行通信进行表面层次的理解。

​ 最好回顾一下我们迄今为止所涵盖的材料,以充分了解WebRTC在我们的示例中的工作原理。重要的是要注意每个步骤和序列都很重要。这将有助于调试WebRTC应用程序中的问题,因为我们将在后面的章节中介绍更多的复杂性。

​ 本书的其余部分将以此示例为基础,使其比目前复杂得多。我们将添加功能,以便在多个不同环境中的多个浏览器中连接多个用户。每一章都将参考WebRTC流程的一部分,深入研究它,涵盖常见的陷阱,并处理网络稳定性和安全性等边缘情况。

​ 在下一章中,我们将开始构建信令服务器以支持连接远程用户。这是我们将通过本书其余部分使用的信令服务器的基础。它还允许我们创建我们的第一个真正的呼叫应用程序,就像Google 环聊一样。


zeronlee
112 声望13 粉丝