3 HTML躬行记——WebRTC视频通话( 二 )

在上面的代码中,实现了最简易的路由分发,当访问 http://localhost:1234 时,读取 index.html 静态页面,结构如下所示 。
<video id="localVideo"></video><button id="btn">开播</button><video id="remoteVideo" muted="muted"></video><script src="https://www.huyubaike.com/biancheng/socket.io.js"></script><script src="https://www.huyubaike.com/biancheng/client.js"></script>socket.io.js 是官方的 socket.io 库,client.js 是客户端的脚本逻辑 。
在 remoteVideo 中附带 muted 属性是为了避免报错:DOMException: The play() request was interrupted by a new load request 。
最后就可以通过 node server.js 命令,开启 HTTP 服务器 。
2)长连接
为了便于演示,指定了一个房间,当与信令服务器连接时,默认就会被安排进 living room 。
并且只提供了一个 message 事件,这是交换各端信息的关键代码,将一个客户端发送来的消息中继给其他各端 。
const io = new Server(server);const roomId = 'living room';io.on('connection', (socket) => {// 指定房间socket.join(roomId);// 发送消息socket.on('message', (data) => {// 发消息给房间内的其他人socket.to(roomId).emit('message', data);});});因为默认是在本机演示 , 所以也不会安装 CoTurn,有兴趣的可以自行实现 。
三、客户端在之前的 HTML 结构中,可以看到两个 video 元素和一个 button 元素 。
const btn = document.getElementById('btn');// 开播按钮const localVideo = document.getElementById('localVideo');const remoteVideo = document.getElementById('remoteVideo');const size = 300;在两个 video 元素中,第一个是接收本地的音视频流,第二个是接收远端的音视频流 。
1)媒体协商
在下图中,Alice 和 Bob 通过信令服务器在交换 SDP 信息 。

3 HTML躬行记——WebRTC视频通话

文章插图
Alice 先调用 createOffer() 创建一个 Offer 类型的 SDP , 然后调用 setLocalDescription() 配置本地描述 。
Bob 接收发送过来的 Offer,调用 setRemoteDescription() 配置远端描述 。
再调用 createAnswer() 创建一个 Answer 类型的 SDP,最后调用 setLocalDescription() 配置本地描述 。
而 Bob 也会接收 Answer 并调用 setRemoteDescription() 配置远端描述 。后面的代码会实现上述过程 。
2)RTCPeerConnection
在 WebRTC 中创建连接,需要先初始化 RTCPeerConnection 类,其构造函数可以接收 STUN/TURN 服务器的配置信息 。
// STUN/TURN Serversconst pcConfig = {//'iceServers': [{//'urls': '',//'credential': "",//'username': ""//}]};// 实例化 RTCPeerConnectionconst pc = new RTCPeerConnection(pcConfig);然后注册 icecandidate 事件 , 将本机的网络信息发送给信令服务器 , sendMessage() 函数后面会介绍 。
pc.onicecandidate = function(e) {if(!e.candidate) {return;}// 发送 ICE CandidatesendMessage({type: 'candidate',label: e.candidate.sdpMLineIndex,id: e.candidate.sdpMid,candidate: e.candidate.candidate});};最后注册 track 事件,接收远端的音视频流 。
pc.ontrack = function(e) {remoteVideo.srcObject = e.streams[0];remoteVideo.play();};3)长连接
在客户端中,已经引入了 socket.io 库,所以只需要调用 io() 函数就能建立长连接 。
sendMessage() 函数就是发送信息给服务器的 message 事件 。
const socket = io("http://localhost:1234");// 发送消息function sendMessage(data){socket.emit('message', data);}本地也有个 message 事件 , 会接收从服务端发送来的消息,其实就是那些转发的消息 。
data 对象有个 type 属性,可创建和接收远端的 Answer 类型的 SDP 信息,以及接收远端的 ICE 候选者信息 。
socket.on("message", function (data) {switch (data.type) {case "offer":// 配置远端描述pc.setRemoteDescription(new RTCSessionDescription(data));// 创建 Answer 类型的 SDP 信息pc.createAnswer().then((desc) => {pc.setLocalDescription(desc);sendMessage(desc);});break;case "answer":// 接收远端的 Answer 类型的 SDP 信息pc.setRemoteDescription(new RTCSessionDescription(data));break;case "candidate":// 实例化 RTCIceCandidateconst candidate = new RTCIceCandidate({sdpMLineIndex: data.label,candidate: data.candidate});pc.addIceCandidate(candidate);break;}});在代码中,用 RTCSessionDescription 描述 SDP 信息 , 用 RTCIceCandidate 描述 ICE 候选者信息 。
4)开播
为开播按钮注册点击事件,在事件中,首先通过 getUserMedia() 获取本地的音视频流 。
btn.addEventListener("click", function (e) {// 获取音视频流navigator.mediaDevices.getUserMedia({video: {width: size,height: size},audio: true}).then((stream) => {localVideo.srcObject = stream;localStream = stream;// 将 Track 与 RTCPeerConnection 绑定stream.getTracks().forEach((track) => {pc.addTrack(track, stream);});// 创建 Offer 类型的 SDP 信息pc.createOffer({offerToRecieveAudio: 1,offerToRecieveVideo: 1}).then((desc) => {// 配置本地描述pc.setLocalDescription(desc);// 发送 Offer 类型的 SDP 信息sendMessage(desc);});localVideo.play();});btn.disabled = true;});

推荐阅读