WebRTC 是 RTC 技术中流行的概念之一,有各种教程。此外,有大量库(尤其是 NPM 库)使用 WebRTC 建立通信,用途广泛。许多教程使用这些库来展示如何构建 Zoom、Discord 等的克隆应用程序。但是,它们未能展示 WebRTC 的核心工作原理。
在本文中,我们将制作一个简单的Demo,演示两个浏览器之间的信号传输。它只需要一个浏览器和一些 Javascript 代码。由于目标是掌握信号概念,因此它不会是一个有用的应用程序,而只是一个Demo,以简单的方式演示建立连接所需的每个组件。
设置
在本 Demo 中,我们需要两个浏览器同时工作。文件结构如下图所示。如果你愿意,可以使用两台电脑来查看不同设备之间的连接。无论是使用同一台设备还是两台设备,情况都是一样的。
signaling demo
├── userA
│ ├── index.html
│ └── scriptA.js
└── userB
├── index.html
└── scriptB.js
HTML 文件将是空白的,它们只需要一个 HTML 模板,并需要连接到它们的脚本文件。
<!-- userA/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>User A</title>
</head>
<script src="scriptA.js"></script>
</html>
<!-- userB/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>User B</title>
</head>
<script src="scriptB.js"></script>
</html>
为了使用我们的环境,我建议在 VS Code 中打开signaling demo
文件夹并启用 Live Share。然后我们可以打开两个选项卡来访问其中两个目录:
http://127.0.0.1:5500/userA
http://127.0.0.1:5500/userB
不过,只要在浏览器上打开 HTML 文件就足够了。我们不需要页面本身,只需要像下面这样打开两个标签页及其控制台。
创建 SDP
设置已就绪,让我们开始使用 scriptA.js:
const localConnection = new RTCPeerConnection();
const dataChannel = localConnection.createDataChannel('dataChannel');
//Event listeners
dataChannel.onopen = () => {
console.log('Data channel opened');
}
dataChannel.onmessage = event => {
console.log("New Message: " + event.data);
}
- 第一步是创建 RTCPeerConnection,建立我们的连接。
- 该连接需要一个数据通道来传输我们的信息。我们为 a 创建
localConnection
通道,并将其标签设置为dataChannel
。 - 我们需要一些事件监听器来从连接中获取更新:
- 第一个事件监听器监听数据通道是否已打开。打开是指与远端peer的连接成功,peer之间的数据通道连通。
- 一旦数据通道打开,我们就可以开始监听传入的信息。
localConnection.createOffer().then( offer => {
localConnection.setLocalDescription(offer);
}).then( a => {
console.log("Offer created!");
})
localConnection.onicecandidate = event => {
console.log("New candidate! SDP updated: ");
console.log(localConnection.localDescription);
}
- 为了获取 SDP 对象,我们需要创建一个报价。该报价将保存到用户 A 的本地描述中。
- 现在我们倾听 ICE 候选人的声音。一旦报价创建,我们将为我们的设备找到几个 ICE 候选者。每次我们获得新的 ICE 候选者时,我们都会更新 SDP,以便它包含迄今为止的所有候选者。
SDP已创建,我们可以在控制台中看到它。我们将有几个 SDP 对象,但我们需要最后一个,因为它包含所有 ICE 候选对象。
我们可以轻松复制它,如上图所示。为了完成信令,我们需要将此 SDP 对象传输到远程对等点。对于此Demo,我们只需复制它并将在用户 B 中使用它。在下一篇文章中,我们将研究如何通过 Firebase 数据库发送 SDP 信号。
发出报价并创建答案
在继续与用户 A 讨论之前,我们需要将 SDP 传递给用户 B。开始编写scriptB.js
代码:
const remoteConnection = new RTCPeerConnection();
remoteConnection.onicecandidate = event => {
console.log("New candidate! SDP updated: ");
console.log(remoteConnection.localDescription);
}
remoteConnection.ondatachannel = event => {
const dataChannel = event.channel;
remoteConnection.dataChannel = dataChannel;
dataChannel.onmessage = event => {
console.log("New Message: " + event.data);
}
dataChannel.onopen = () => {
console.log('Data channel opened');
}
}
- 就像我们对用户 A 所做的那样,我们创建一个
RTCPeerConnection
对象并监听新的 ICE 候选者。当我们为用户 B 创建答案时,这些候选者将会出现。 - 事件侦听器很相似,但有一些细微的差别。由于用户A发起了信令过程,因此当连接建立时,其数据通道将被传递给用户B,这意味着他们将共享相同的数据通道。因此,我们需要监听用户B的数据通道。当数据通道中发生事件时,用户B将像用户A一样监听打开和消息事件。
function setOffer(offer) {
remoteConnection.setRemoteDescription(offer).then( a => {
console.log("Offer set!");
})
remoteConnection.createAnswer().then( answer => {
remoteConnection.setLocalDescription(answer);
}).then( a => {
console.log("Answer created!");
})
}
- 我们在这里使用一个函数,因为一旦我们从用户 A 获得 SDP 报价,我们就会调用这个函数。
- 为了将 SDP 传递给用户 B,我们将报价设置为远程描述。
- 当我们设置远程描述时,我们可以创建我们的答案并将其设置为本地描述。
// Execute this in the console of User B (SDP object is only given for example, you need to pass your own SDP object you copied)
setOffer({
"type": "offer",
"sdp": "v=0\r\no=- 6903397949454729551 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=application 56524 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 10.254.127.14\r\na=candidate:2579874737 1 udp 2122260223 10.254.127.14 56524 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:3611705153 1 tcp 1518280447 10.254.127.14 9 typ host tcptype active generation 0 network-id 1 network-cost 10\r\na=ice-ufrag:JApg\r\na=ice-pwd:AHSRC03UbXKzl2P+e9dPNLZT\r\na=ice-options:trickle\r\na=fingerprint:sha-256 42:50:14:BB:F9:6B:A0:3B:62:15:59:86:14:20:48:30:DD:50:8B:C0:30:31:AD:61:E3:42:B0:20:93:EE:14:30\r\na=setup:actpass\r\na=mid:0\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n"
})
一旦我们执行该函数,就会创建一个答案,并且每次找到新的 ICE 候选者时,用户 B 的 SDP 都会更新。就像我们之前所做的那样,我们将复制答案 SDP 对象。
建立连接并发送消息
我们回到用户 A 并完成scriptA.js
。我们将定义另外两个函数来获得聊天功能。
function setAnswer(answer) {
localConnection.setRemoteDescription(answer);
}
function sendMessage(message) {
dataChannel.send(message);
}
// Execute this in the console of User A (SDP object is only given for example, you need to pass your own SDP object you copied)
setAnswer({
"type": "answer",
"sdp": "v=0\r\no=- 8372747172637914019 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=application 52700 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 10.254.127.14\r\na=candidate:2579874737 1 udp 2122260223 10.254.127.14 52700 typ host generation 0 network-id 1 network-cost 10\r\na=ice-ufrag:9BNJ\r\na=ice-pwd:duKEejZ669stZBln7bBESiii\r\na=ice-options:trickle\r\na=fingerprint:sha-256 1B:17:0E:A8:41:90:C5:79:F1:12:0E:61:E2:05:EE:CE:4D:91:12:A3:97:20:91:D8:A7:42:17:F1:C8:9E:23:16\r\na=setup:active\r\na=mid:0\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n"
})
- 为了完成信令过程,我们需要向用户 A 发回信号。我们将应答SDP设置为用户A的远程描述。
- 一旦我们完成信令,数据通道就会为双方打开。我们可以轻松地通过数据通道从用户 A 向用户 B 发送消息。
还剩下最后一个函数,我们将在scriptB.js
中声明它:
function sendMessage(message) {
remoteConnection.dataChannel.send(message);
}
这个函数有点不同,因为用户 B 使用从用户 A 处获得的数据通道,所以它需要使用与其连接绑定的数据通道。
通过建立连接,我们可以轻松地向远程对等设备发送字符串等简单变量。不仅如此,您还可以发送视频音轨、对象甚至文件。在下一篇文章中,我们将开始构建能实现所有这些功能的应用程序。
如果你想试用该 Demo,可以在 GitHub 代码库 中找到它。
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/31214.html