WebRTC入门
首先需要理解几个概念
WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 使用 UDP 协议
NAT(网络地址转换)
NAT是一种网络技术,用于将私有IP地址转换为公共IP地址,以允许局域网内的设备与公共互联网通信。
大部分文章都在写 “两个用户在 NAT 后…” 这就很容易让人产生误解,NAT是个网络地址转换的东西,
NAT后是转换后的意思?
这个NAT后的意思就是它的网络连接经过了至少一个NAT设备,通常是路由器(还有防火墙、网关等等)。这意味着该设备的通信流量被路由器转换、映射为公共IP地址。在NAT之前,设备使用私有IP地址(如192.168.x.x或10.x.x.x)在局域网内进行通信;而在NAT后,则使用路由器的公共IP地址在互联网上与其他设备进行通信。
大白话:我们的设备比如手机、电脑能访问外部互联网比如百度,是因为经过了路由器进行了网络地址转换
点对点(P2P)通信
点对点通信是一种直接的通信方式,它不需要经过中间的服务器或其他网络设备的转发。当你知道对方的真实地址时,你可以直接将数据发送到对方的地址上,而不需要经过第三方的干涉。
P2P通信(Peer-to-Peer,对等通信)是一种直接将两个或多个终端设备连接起来进行通信的方式,而不依赖于中央服务器。它不需要服务器的原因如下:
-
去中心化:P2P通信没有中央服务器的概念,所有终端设备平等地参与通信并共享资源。每个设备都可以作为客户端和服务器的角色,相互之间进行直接的连接和通信。
-
端到端通信:P2P通信是一种端到端的通信方式,数据直接从源设备发送到目标设备,不需要经过中央服务器进行转发。这种直接的连接和通信方式可以提高通信的效率和速度。
-
分布式网络:P2P通信建立了一个分布式的网络,每个终端设备都可以通过直接连接与其他设备进行通信。这种分布式的网络结构具有更好的鲁棒性和容错性,因为单个设备的故障不会影响整个网络的通信。
大白话的解释:我只要知道你的真实地址,我就可以顺着网线(数据通过物理和逻辑链路进行传输)去打你,不需要找第三方的打手
P2P通信中的数据传输和路由是通过互联网的协议和技术实现的,例如TCP/IP协议,而不是真正的物理网线。物理网线只是连接终端设备和路由器之间的媒介,数据经过路由器和互联网的转发才能到达目标设备。
对于P2P通信,A用户和B用户之间的通信数据会通过路由器层层转发到对方终端设备。当A用户发送数据时,数据将通过A用户的路由器进入互联网,并通过一系列中间路由器的转发,最终到达B用户的路由器,然后再传输到B用户的终端设备。
媒体协商(SDP)
// WebRTC API中的一个方法,用于创建一个新的对等连接对象。该对象用于管理实时通信的配置、状态和数据流。
// 它允许您配置和管理实时通信过程中的多个因素,例如流,认证,编码,传输协议等。
// 详细解释看下方RTCPeerConnection栏目
const peer = new RTCPeerConnection({
iceServers: [
{ url: "stun:stun.l.google.com:19302" }, // 谷歌的公共服务
{
urls: "turn:***",
credential: "***",
username: "***",
},
],
});
两个用户在连接之前需要相互确定并交换双方支持的音视频格式的过程就是媒体协商。SDP 是描述信息的一种格式,具体百度吧里面太多编码格式
大白话解释:取交集,没有交集就凉凉
备注:某些媒体协议或技术支持动态编码格式转换
例如,可以使用实时转码器(transcoder)或媒体网关(media gateway)将一种编码格式转换为另一种编码格式,以实现双方之间的媒体通信。
网络协商(candidate)
两个用户在 NAT 后交换各自的网络信息的过程就是网络协商。candidate 也是一种描述信息的一种格式。
- 获取外网IP地址映射
- 通过信令服务器交换网络信息
理想的网络情况是每个浏览器的电脑都是私有公网IP,可以直接进行点对点连接
现实情况:我们的电脑都是在某个局域网中,需要NAT(Network Address Translation,网络地址转换)
这种情况下,我们不知道对方的公网地址用对方的局域网地址并没有什么卵用,这时候就需要用到STUN/TURN服务
STUN 网络协议
STUN(Session Traversal Utilities for NAT)和TURN(Traversal Using Relays around NAT)是两种用于解决NAT(Network Address Translation)问题的协议。
大白话:告诉我你的公网IP地址+端口是什么
STUN并不是每次都能成功的为需要NAT的通话设备分配IP地址的,P2P在传输媒体流时,使用的本地带宽,在多人视频通话的过程中,通话质量的好坏往往需要根据使用者本地的带宽确定。那么怎么办?TURN可以很好的解决这个问题。
TURN 网络协议
TURN的全称为Traversal Using Relays around NAT,是STUN/RFC5389的一个拓展,主要添加了Relay功能。如果终端在NAT之后, 那么在特定的情景下,有可能使得终端无法和其对等端(peer)进行直接的通信,这时就需要公网的服务器作为一个中继, 对来往的数据进行转发。这个转发的协议就被定义为TURN。
在STUN分配公网IP失败后,可以通过TURN服务器请求公网IP地址作为中继地址。这种方式的带宽由服务器端承担,在多人视频聊天的时候,本地带宽压力较小,并且,根据Google的说明,TURN协议可以使用在所有的环境中。(单向数据200kbps 一对一通话)
单向数据200kbps: 200 * 4 = 800kbps = 100KB/S 反正是非常消耗带宽资源,具体资源换算还是百度研究吧
大白话:STUN无法解决设备之间的点对点通信问题了,我拿不到对方的真实地址,需要一个中继服务器来把我的数据转发给对面(我需要找一个打手去打你,我自己无法找到你)
使用TURN 转发数据所产生的带宽费用需要由自己承担!
ICE (框架)
ICE(Interactive Connectivity Establishment),是一种用于实现网络连接的技术框架,用于在对等连接(如实时通信、P2P 文件共享等)中解决 NAT(Network Address Translation)和防火墙等网络障碍的问题。 ICE 是一种框架,可以通过使用多种技术(如 STUN、TURN、NAT 透明性检测等)来搜索可用的网络路径,并选择最优的路径建立连接,从而解决了 NAT 和防火墙等网络障碍的问题。
- 收集网络接口信息,包括本地 IP 地址、端口等;
- 通过 STUN 服务器获取公网 IP 地址和端口号;
- 通过 NAT 透明性检测来确定 NAT 类型和行为;
- 尝试直接连接对等端点;
- 如果直接连接失败,则使用 TURN 服务器作为中继节点进行连接。 也就是,ICE 更好的进行 NAT 穿越效果,从而提高实时通信的质量和效率。
其次再了解下WebRTC的API
媒体设备(MediaStream)
MediaStream是用于获取音频和视频的对象。通过MediaStream可以访问摄像头、麦克风等设备。
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
// stream是获取到的音频或视频流
})
.catch(function(err) {
// 处理错误
});
// constraints参数是一个对象,用于指定需要获取的流的属性,比如:
var constraints = {
video: true,
audio: true
};
const constraints = {
audio: true,
video: {width: 1280, height: 720}
};
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
const video = document.querySelector('video');
video.srcObject = stream;
video.onloadedmetadata = (e) => {
video.play();
};
})
.catch((err) =>{
console.log(err.name + ":" + err.message);
});
核心对象 RTCPeerConnection
WebRTC 的核心是 RTCPeerConnection 接口,该接口封装了所有建立 P2P 连接的细节和基本操作。
RTCPeerConnection 作为创建点对点连接的 API,是我们实现音视频实时通信的关键。
属性:
- iceServers: WebRTC 中 ICE (Interactive Connectivity Establishment) 协议用于在两个端点之间建立网络连接。这个属性可以用来指定 STUN 或 TURN 服务器的 URL 以进行 NAT 穿透和中继等操作。如果不指定 ICE 服务器将会使用默认的 Google 服务器,但建议在生产环境中使用自己的服务器。
- iceTransportPolicy: 指定 RTCIceTransport (ICE 值传输协议)选项来控制使用哪种类型的 ICE 值传输或连接。可选值为 “all”, “relay”, “none”。默认为 “all”。
- bundlePolicy: 指定哪些媒体通道应该捆绑(组合)在一起。可选值为 “balanced”, “max-compat”, “max-bundle”。默认为 “balanced”。
- rtcpMuxPolicy: 指定使用哪种 RTCP 复用政策。可选值为 “negotiate” , “require” 或 “disable”。默认为 “require”。
- peerIdentity: 信任的对等身份,用于确保 SSL/TLS 证书的合法性。如果不指定将使用默认的 SSL/TLS 证书。
- sdpSemantics: 规范输入和输出 SDP (会话描述协议)的方法。可选值为 “plan-b” 或 “unified-plan”。默认为 “plan-b”。“unified-plan” 会使得更多媒体数据捆绑在一个 SDP 中,仅用于 Chrome 和 Firefox。“plan-b” 则兼容之前的浏览器。
- iceCandidatePoolSize: 指定 ICE candidates 的数量。默认为 0,表示自动设置数量。
- certificates: 指定用于 WebRTC 安全传输的 SSL/TLS 证书。如果不指定一个或多个证书,WebRTC 将生成自己的证书。
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun.l.google.com:19302",
},
{
urls: "turn:my-turn-server.com",
username: "my-username",
credential: "my-password",
},
],
});
主要会用到以下几个方法:
媒体协商方法:
- createOffer
- createAnswer
- setLocalDesccription
- setRemoteDesccription
重要事件:
- onicecandidate
- onaddstream
整个媒体协商过程可以简化为三个步骤对应上述四个媒体协商方法:
- 呼叫端创建 Offer(createOffer)并将 offer 消息(内容是呼叫端的 SDP 信息)通过信令服务器传送给接收端,同时调用 setLocalDesccription 将含有本地 SDP 信息的 Offer 保存起来
- 接收端收到对端的 Offer 信息后调用 setRemoteDesccription 方法将含有对端 SDP 信息的 Offer 保存起来,并创建 Answer(createAnswer)并将 Answer 消息(内容是接收端的 SDP 信息)通过信令服务器传送给呼叫端
- 呼叫端收到对端的 Answer 信息后调用 setRemoteDesccription 方法将含有对端 SDP 信息的 Answer 保存起来
经过上述三个步骤,则完成了 P2P 通信过程中的媒体协商部分,实际上在呼叫端以及接收端调用setLocalDesccription 同时也开始了收集各端自己的网络信息(candidate),然后各端通过监听事件 onicecandidate 收集到各自的 candidate 并通过信令服务器传送给对端,进而打通 P2P 通信的网络通道,并通过监听 onaddstream 事件拿到对方的视频流进而完成了整个视频通话过程。
addStream()- 已过时,官方不推荐使用
将一个MediaStream音频或视频的本地源,添加到WebRTC对等连接流对象中。官方推荐我们使用另外一个方法addTrack
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
peer.value = new RTCPeerConnection();
// 添加本地音视频流----已过时
peer.value.addStream(stream)
// 获取对方的音视频流
peer.value.onaddstream = (event: any) => {
// 拿到对方的视频流
console.log("接收方收到发送方的Stream",event.stream)
remoteVideo.value!.srcObject = event.stream;
remoteVideo.value!.play()
};
onaddsream触发的前提是:建立了有效的 PeerConnection、媒体流已通过远程对等端的 PeerConnection 添加、建立了有效的数据通路:PeerConnection 对象之间已经建立了有效的数据通道(通过信令交换,如 SDP 交换和 ICE 连接建立)
当远程对等端添加了媒体流到其 PeerConnection 对象中【peerConnection.addStream(localStream);】,onaddstream 事件将被触发
addTrack()- 推荐使用–没研究
将新的媒体轨道添加到轨道集,该轨道将被传输到另一对等方
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
peer.value = new RTCPeerConnection();
// 添加本地音频轨道
var localAudioTrack = stream.getAudioTracks()[0];
peer.addTrack(localAudioTrack, stream);
// 添加本地视频轨道
var localVideoTrack = stream.getVideoTracks()[0];
peer.addTrack(localVideoTrack, stream);
// 监听 ontrack 事件
peer.ontrack = function(event) {
// 可以通过 event.streams 属性获取远程的媒体流。如果你只关心音频或视频轨道,可以使用 event.track
var remoteStream = event.streams[0];
var remoteTrack = event.track;
videoElement.srcObject = remoteStream; // 连接远程视频流到 videoElement
}
createOffer&&setLocalDescription 创建网络邀请函并设置本地连接关联的本地视频描述
在 CreateOffer 中,会获取本地所支持的音视频编码格式,以及传输相关参数信息,一般在此方法中要设置setLocalDescription属性完成RTC对本地视频数据加载。
// 生成offer
// offer_to_receive_video ,offer_to_receive_audio : 0 表示不接收音视频,1表示接收音视频,声音和视频可以设置的不一样。
const offer = await peer.value.createOffer({
offerToReceiveAudio:1,
offerToReceiveVideo:1,
})
console.log("发起方发送的Offer",offer)
// 设置本地描述信息
await peer.value.setLocalDescription(offer);
// 通过信令服务器发送给对方
sock.emit('sendOffer',{ offer, roomId})
createAnswer&&setRemoteDescription 创建网络应答函并设置远程连接关联的远程视频描述
创建关于从远程对等方收到的SDP而产生的本地SDP对象,包含有关会话中已附加的任何媒体,浏览器支持的编解码器和选项以及已收集的所有ICE候选者的信息
// 设置远端描述信息
await peer.value.setRemoteDescription(offer);
// 生成answer
const answer = await peer.value.createAnswer()
// 设置本地描述信息
await peer.value.setLocalDescription(answer);
// 通过信令服务器发送给对方 对方收到后也peer.value.setRemoteDescription(answer);
sock.emit('sendAnswer', { roomId, answer });
onicecandidate ICE候选事件
在WebRTC中,在STUN和TURN服务器完成网络连接建立后,PeerConnection对象会自动触发icecandidate事件,并提供事件的相关信息,如候选地址candidate等。icecandidate事件的触发时机非常重要,因为我们需要在这个事件触发之后立即将候选地址发送给远程的PeerConnection,以便二者能够建立连接。
在使用WebRTC时,可以使用onicecandidatetimeout事件监听icecandidate信息是否生成。在收到icecandidate的消息后,需要将其发送给对方,以便对方添加到ice状态中
// 获取candidate信息
peer.value.onicecandidate = (event: any) => {
if (event.candidate) {
// 向服务器发送candidate信息
console.log("接收方发送的candidate",event.candidate)
sock.emit('sendCandidate', { roomId, candidate: event.candidate })
}
}
注意事项
- icecandidate事件是可重复触发的,它会在一个同步过程中不断地产生候选地址。一旦在icecandidate事件处理过程中调用了ICEServer.addIceCandidate()方法,那么同步过程就会被打断。
- 如果一个候选地址已经被添加到PeerConnection中,那么onicecandidate事件就不会再触发。
- 由于网络不稳定等原因,在生成candidate信息的过程中,如果用户的网络环境发生了变化,WebRTC将会重新生成候选地址,并重新触发onicecandidate事件。
addIceCandidate 添加对方的网络信息
接收到信令服务器发送过来的候选信息后调用,为本机添加 ICE 代理。
// 把收到的candidate添加peer对象中 进行网络信息交换
await peer.value.addIceCandidate(candidate);
完结 🎉撒花🎉
代码基本都是前端写,后端只做信令服务器用于信息转发,再搭建一个STUN/TURN服务器