前言
WebRTC很简单!!!
WebRTC很简单!!!
WebRTC很简单!!!
重要的事情说三遍,很多同学刚开始学WebRTC的时候总感觉一头雾水,感觉WebRTC好高端,好复杂,好难学。 我刚开始的时候也是有这种感觉,但是学完之后,真的觉得它蛮简单的,所以把我的学到的以及一下自己的理解分享出来,让想学WebRTC的同学能够快速掌握。
至于WebRTC的简介,有什么用,什么场景下使用,我这里就不说了,看这里:百度百科
快速理解WebRTC
WebRTC就是点对点的一项实时通讯技术,以前我们想要和另外一个用户通信,一般都是通过服务器进行数据交换 例如websocket
这种情况下数据要先经过服务器,才能到另一个客户端
但是webrtc的话,就是客户端与客户端的连接
所以对于实时音视频数据(媒体流)来说传输速度会很快,延迟会很低
创建WebRTC,建立连接
那么要进行端对端的连接传输音视频数据,首先要具备几个条件
1.媒体协商 sdp
因为webrtc用来传输的是音视频数据,那么就得让双方知道,彼此都支持什么格式的音视频,这样发送给对的视频才能被对方解码显示,这个过程就叫媒体协商。
步骤一 获取本地sdp并设置本地sdp
调用webrtc的pai,createOffer方法可以获取本地(客户端A)的sdp,简单理解sdp就是所支持的音视频格式的信息,然后调用setLocalDescription,这个就是用来设置本地是sdp的(至于为什么非要从本地获取然后再设置本地sdp,也不用搞太清楚,知道要这么操作就行)
js
let peerConnection = new RTCPeerConnection(null);
// 创建offer offer中包含了sdp的数据
peerConnection.createOffer().then((offer) => {
console.log('offer', offer.sdp);
peerConnection.setLocalDescription(offer);
});
步骤二 设置远端sdp
调用setRemoteDescription方法可以设置远端(客户端B)的sdp
js
peerConnection.setRemoteDescription(remoteAnswer).then(() => {
console.log('local 设置远端sdp描述信息成功');
});
上面两个步骤是用来设置本地sdp和远端sdp的,那么中间还少了一步交换sdp数据,其实交换数据的这一步才是媒体协商中最重要的
对于客户端A和客户端B来说,他们创建webrtc的时候,对于他们自己都是本地,所以不管客户端A还是客户端B都需要执行上面的步骤一和步骤二
客户端A和客户端B都可以通过createOffer方法可以获取本地的sdp,我们只需要将这个sdp数据发送给对方,对方就能通过setRemoteDescription方法设置远端的sdp,所以现在的重点是将sdp的数据发送给对方,我们一般采用websocket来实现数据交互,这个就是信令服务器(webrtc是webrtc,websocket是websocket,这里使用websocket只是为了通过服务端交换sdp数据,我们也可以采用http接口通过服务端交换)。
所以具体的方法是在步骤一中createOffer拿到sdp之后,调用websocket的send方法发送给对方,然后再我们的websocket.onmessage中获取到了对方发送的sdp,使用步骤二进行设置,这样就完成了媒体协商(在理解一部分的时候一定要知道,使用的时候你这套代码是在两个客户端使用的,所以即会作为本地也会作为远端,要是实在有点混乱,你就把这套代码复制一份,写在一个新的文件中,一个作为本地一个作为远端理解)。
js
const socket = new WebSocket('这里是你自己的websocket的地址');
socket.onmessage = (e) => {
handleMessage(e);
};
const handleMessage = (e) => {
let { type, data } = e;
if (type === 'offer') {
setRemoteSDP(decode(JSON.parse(data)));
}
};
const peerConnection = new RTCPeerConnection(null);
// 创建offer offer中包含了sdp的数据
peerConnection.createOffer().then((offer) => {
console.log('offer', offer.sdp);
peerConnection.setLocalDescription(offer);
// 将sdp信息发送给远端
socket.send({
type: 'offer',
data: encode(JSON.stringify(offer))
});
});
// 处理远端发送过来的sdp
const setRemoteSDP = (remoteAnswer) => {
peerConnection.setRemoteDescription(remoteAnswer).then(() => {
console.log('local 设置远端sdp描述信息成功');
});
};
2.网络协商 candidate
网络协商的流程和媒体协商的流程是一样的,也是交换数据,只是交换的是双方网络环境的数据
这里简单说一下为什么要交换网络环境数据,按常理来说,我们想要连接到一台电脑,是不是需要知道对方的ip地址+端口,webrtc的连接也是一样的。理想情况每个电脑都会有一个ip地址(我们简称私有ip)和公网的ip地址(百度搜索ip地址得到的那个就是公网ip),别人想要访问你本地时就是通过这个公网的ip地址和一个可用的端口进行访问的,webrtc端对端的连接就是访问彼此的公网ip+端口然后传递给对方音视频数据,所以我们需要将我们本地的公网ip+端口告诉对方,同时也要拿到对方的ip+端口,这样彼此就能实时交换音视频数据了。
步骤一 获取本地公网ip+端口
我们要拿到我们本地可以访问的公网ip+端口,需要借助使用STUN网络协议的服务器去获取,我们在初始化RTCPeerConnection的时候设置好其参数iceServers,告诉webrtc需要连接的STUN服务器,它会在内部处理好,我们不用关注,它内部处理好之后会触发RTCPeerConnection.onicecandidate并返回candidate。
js
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
});
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 这里就是我们本地的candidate
console.log('candidate', event.candidate);
}
}
其实当客户端A和客户端B在同一个局域网下时,是不需要STUN服务器的,可以直接通过私有ip地址直连,所以new RTCPeerConnection可以不需要参数,直接将我们的私有ip告知对方就可以,他们可以直接但是并不是所有情况下客户端A和客户端B都能进行webrtc的直连,如果无法直连的情况下,我们就需要一个中继服务器TURN,中继服务器作为两个端都能访问到的服务器,客户端A的将数据传到中继服务器,中继服务器再将数据传递给客户端B,也能完成webrtc的数据传递。
js
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: 'turn:my-turn-server.com',
username: 'my-username',
credential: 'my-password'
}
]
});
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('candidate', event.candidate);
}
};
上面STUN和TURN都是不可用的,想尝试的可以直接在局域网内尝试webrtc或者自己搭建STUN和TURN,更详细的可以看这个视频讲解。
步骤二 设置远端candidate 调用addIceCandidate方法可以设置远端(客户端B)的sdp
js
peerConnection.addIceCandidate(candidate).then(() => {
console.log('local 设置远端candidate成功');
});
同样,我们交换candidate的数据可以借用websocket,完整代码如下
js
const socket = new WebSocket('这里是你自己的websocket的地址');
socket.onmessage = (e) => {
handleMessage(e);
};
const handleMessage = (e) => {
let { type, data } = e;
if (type === 'candidate') {
setRemoteCandidate(decode(JSON.parse(data)));
}
};
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: 'turn:my-turn-server.com',
username: 'my-username',
credential: 'my-password'
}
]
});
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('candidate', event.candidate);
// 将candidate信息发送给远端
socket.send({
type: 'candidate',
data: encode(JSON.stringify(event.candidate))
});
}
};
const setRemoteCandidate = (candidate) => {
peerConnection.addIceCandidate(candidate).then(() => {
console.log('local 设置远端candidate成功');
});
};
发送和接收音视频数据(媒体流)
获取媒体流
要给对方发送音视频数据,我们首先要获取到这些数据MediaStream,获取方法有很多种,一般webrtc中进行实时音视频通话需要获取本地摄像头和麦克风,可以通过navigator.mediaDevices.getUserMedia获取。
js
const constraints = {
video: {
frameRate: 60
},
audio: true
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
console.log('音视频流', stream);
});
我们也可以通过video.captureStream获取
js
const video = document.createElement('video');
video.crossOrigin = 'anonymous';
video.src = '视频地址';
video.muted = true; // 静音才能实现自动播放
video.preload = 'auto';
video.loop = true;
document.body.addEventListener('click', () => {
console.log(11111111111);
video.muted = false;
});
video.onloadedmetadata = () => {
let stream = null;
try {
stream = video.captureStream();
console.log('音视频流', stream);
} catch (error) {}
};
也能使用canvas.captureStream获取
发送媒体流
发送媒体流只需要调用addTrack往peerConnection中添加音频或者视频轨道即可,这样当webrtc连接成功之后媒体流就会被发送给对方
js
// 遍历媒体流stream中的所有轨道
stream.getTracks().forEach((track) => {
// 将轨道加入到peerConnection中
peerConnection.addTrack(track, stream);
});
这个地方有一个要注意的点是添加媒体流必须是在媒体协商和网络协商之前,如果在媒体协商之后加入的轨道是无法被对方接收到的(我自己测试的表现是这样的,具体原因还未去测试,以及动态添加媒体流也未去尝试,有懂的大佬可以教一下)
接收媒体流
当对方有媒体流发送过来的时候,会触发peerConnection.ontrack回调,可以在这里面处理对方发送过来的媒体流
js
peerConnection.ontrack = (e) => {
handleRemoteStream(e);
};
const mediaStream = null;
let handleRemoteStream = (e) => {
// 存储视频流
if (!this.mediaStream) {
mediaStream = new MediaStream();
}
mediaStream.addTrack(e.track);
// 将接收到的流显示到屏幕上
document.getElementById('videoId').srcObject = mediaStream;
};
好了,整个webrtc的音视频通讯完整流程就讲解完了,其实步骤并不多,只要理解了其实就很简单,然后就可以根据业务场景进行拓展了。
(我也是入门阶段,如果有错误的地方欢迎大佬指出)