前言
接上篇文章,实现了音视频通话的转接mqtt
的实现,接下来进行音视频的具体实现,这里采用的方案是通过webRTC
进行实现,这个API
的好处就是不用额外安装其他软件的前提下,浏览器直接点对点实现音视频通话,现在来简单介绍下具体实现过程,希望能对你有帮助。
webRTC是什么
webRTC
是一项实时通讯技术,也叫点对点通信,它能让 Web应用程序和 站点能够流式传输音视频媒体,以及浏览器之间交换数据而无需中间件,建立浏览器点对点数据共享和传输,而且通道一旦建立,可以不经过服务端,客户端对客户端进行实时通话。
准备工作
概念知识
RTCPeerConnection
简称peer
,点对点通信(peer-to-peer),通过这个方法创建一个peer
端,可以理解成创建了一个视频窗口壳子,例如发起方创建一个peer
,接收方也创建一个peer
,两端之间进行联通,即可实现实时通话。
offer
通过createOffer
创建的offer信息,里面包含SDP offer
信息,发起方将offer发送给对方,建立视频连接。
answer
通过createAnswer
创建的应答信息,和offer
类似,接收方接收到offer
后,通过createAnswer
方法创建本机的SDP
信息,然后发送给发起方,这样双方的SDP
交换就完成了。
candidate
有用过获取本机ip
的对这个应该眼熟,获取本机ip
地址,也是通过webRTC
的candidate
进行实现,这个的主要作用是获取本机的ip,它会优先抽局域网中获取,如果局域网获取不到,然后就会从TURN/STURN
获取。
信令服务器
通常使用webSocket
、mqtt
等进行实现,在前面我们有说过创建offer
和answer
发送给对方,浏览器怎么知道要发给谁?所以这里就是使用信令服务器发送给指定的人。
TURN/STURN
这个俗称就是打洞,网络穿透,当两个设备不处于同一局域网的前提下,就需要这个进行一个网络穿透,通过这个可以获取两个设备各自在公网的ip,这样就能进行连接互通。
为什么要交换SDP和信令服务器
有人可能会问,WebRTC
不是能够点对点直接通信吗?为什么还需要交换SDP
和信令服务器?
举个梨子:张三会中,英,韩语三门语言,李四会中、日、俄语,现在张三和李四想要对话交流,自然而然的找到两个人共同点语言中文进行交流,同理,交换SDP
其实就是找共同语言的一个过程,也就是取交集。
然后就是信令服务器,再打个比方,现在有100个房间,我现在只想去张三的房间,但是我又不知道张三的房间号,所以这个时候就需要信令服务器Socket,将我想去张三房间的这个想法转达给张三,并带我去他的房间。
常用方法
API
RTCPeerConnection
:创建一个peer端setLocalDescription
:设置本地描述setRemoteDescription
:设置远程描述createOffer
:创建一个SDP offer
createAnswer
:创建一个SDP Answer
addStream
: 添加视频流addIceCandidate
:添加对方的网络信息
监听
onicecandidate
:监听到ip信息onaddstream
:监护获取对方的视频流信息onicegatheringstatechange
:监听网络协商状态
实现步骤
交换SDP
发起方
peerA
,接收方peerB
都创建一个peer
端发起方获取本机摄像头信息,并通过
addStream
添加视频流到通道接收方获取本机摄像头信息,并通过
addStream
添加视频流到通道发起方通过
createOffer
获取到SDP
信息,并通过setLocalDescription
添加到本地视频描述信息接收方拿到发起方的
SDP
信息后,通过setRemoteDescription
添加对方SDP
到远程视频描述信息,再通过createAnswer
创建应答SDP
信息,通过setLocalDescription
添加到本地视频描述信息,再将answer SDP
信息发送给发起方发起方拿到对方发送的
SDP
信息后,通过setRemoteDescription
添加对方SDP
到远程视频描述信息,到这里,双方交换SDP
已经完成。
建立Candidate连接
通过监听onicecandidate
,我们能获取当前本机的Candidate
(ip,网络信息),通过信令服务器发送给对方,对方接收到后通过addIceCandidate
,将双方建立连接,从而进行画面的流式传递,通道建立完成。可以监听icegatheringstatechange
,当iceGatheringState
变为complete
时,表示双方网络通道已经连接就绪。
简单实现
peer接收方主要做的事:
初始化摄像头并通过
addStream
添加创建
offer
设置setLocalDescription
并发送offer sdp
收到
answer sdp
设置setRemoteDescription
收集到
candidate
发送给对方
// peerA 发起方 let localStream; let localPeer; //获取本机摄像头 async function getLocalVideo(){ const stream = await getAndSelectCamera({ audio: true }); if (stream) { localStream = stream; document.getElementById("localVideo").srcObject = stream; connectInit() } } getLocalVideo() // RTC初始化 function connectInit(){ localPeer = new RTCPeerConnection({}); localPeer.addStream(localStream); sendOffer(); localPeer.onaddstream = (e) => { document.getElementById("remoteVideo").srcObject = e.stream; }; localPeer.addEventListener("icegatheringstatechange",(ev) => { if (ev.target.iceGatheringState === "complete") { console.log("视频连接成功") } },false); } // 发送offer function sendOffer(){ localPeer.createOffer((offer) => { // 发送Offer给对方 mqttServer.publish("/webrtc",{type: "offer", offer}) // 将offer设置到本地 localPeer.setLocalDescription(offer); }); localPeer.onicecandidate = (event) => { if (event.candidate) { //收集到candidate后,将candidate信息发送给对方 mqttServer.publish("/webrtc",{type: "candidate", candidate: event.candidate}) } }; } // 获取到peerB的answer sdp function getAnswer(data){ localPeer.setRemoteDescription(new RTCSessionDescription(data.answer)) } // 收到对方的candidate后,配对 function handleCandidate(data) { localPeer.addIceCandidate(new RTCIceCandidate(data.candidate)); }
peer接收方主要做的事:
初始化摄像头并通过
addStream
添加收到对方发送的
offer
设置setRemoteDescription
创建
answer sdp
设置设置setLocalDescription
并发送answer sdp
收集到
candidate
发送给对方
// peerB接收方 let localStream; let localPeer; //获取本机摄像头 async function getLocalVideo(){ const stream = await getAndSelectCamera({ audio: true }); if (stream) { localStream = stream; document.getElementById("localVideo").srcObject = stream; connectInit() } } getLocalVideo() // RTC初始化 function connectInit(){ localPeer = new RTCPeerConnection({}); localPeer.addStream(localStream); localPeer.onaddstream = (e) => { document.getElementById("remoteVideo").srcObject = e.stream; }; localPeer.onicecandidate = (event) => { if (event.candidate) { mqttServer.publish("/webrtc",{type: "candidate", candidate: event.candidate}) } }; localPeer.addEventListener("icegatheringstatechange",(ev) => { if (ev.target.iceGatheringState === "complete") { console.log("视频连接成功") } },false); } // 收到对方的offer后创建answer function handleSendAnswer(data){ localPeer.setRemoteDescription(new RTCSessionDescription(data.offer)); localPeer.createAnswer( (answer) => { localPeer.setLocalDescription(answer); mqttServer.publish("/webrtc",{type: "answer",answer}) }); } //收到对方的candidate后,配对 function handleCandidate(data) { localPeer.addIceCandidate(new RTCIceCandidate(data.candidate)); }
TURN/STURN
这个主要是进行网络穿透,需要搭建一个TURN
服务器,在创建RTCPeerConnection
的时候有一个可选参数,配置这个可以进行网络穿透,由于我目前场景没有使用到这个,所以这里只是简单提一下。
{ iceServers: [ { url: "xxxx", username: "xxxx", credential: "xxxxxxxx", }, ], }
最后
到这里webRTC
音视频通话已经完成了,其实还是挺有意思的,除此之外,WebRTC
还可以用来实时传输文件等,例如可以实现白板写字同步功能等