前言
接上篇文章,实现了音视频通话的转接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 offercreateAnswer:创建一个SDP AnsweraddStream: 添加视频流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还可以用来实时传输文件等,例如可以实现白板写字同步功能等,如果代码有需要,可以在评论区留言。