写在前面
在开始之前,需要对如何建立点对点连接有一个了解,参考我的另一篇博文:WebRTC之点对点连接。下图是来自参考博文中的一张图片。
基础想法
基础架构如下:
客户端主要在于处理从WebSockets
接收到的消息,并且有着发起连接和被动接受连接的功能。
实现
-
连接建立成功时,注册用户身份,表明想加入的房间号。
ws.onopen = function () { console.log("connect success ... "); // 初始化当前用户身份 let urlParams = urlSearch(); user = { name: urlParams['name'], roomId: urlParams['roomId'] }; console.log("current user...", user); ws.send(JSON.stringify({ msgType: requestType.USER_IDENTITY, msgBody: user })); };
-
通过web sockets 发起创建多个连接的请求。
// call 按钮的点击事件中执行 ws.send(JSON.stringify({ msgType: requestType.CLIENT_MULTIPLY_CONNECTION_CREATEQUE, msgBody: user }));
-
接收服务器响应的房间成员信息,并创建多个用户连接(封装一个具有username和连接属性的对象),发送多个用户offer(username和offer)给信令服务器
// web socket 接收到该消息 ws.onmessage = function (evt) { // 接收到成员消息响应,创建多个连接 if(result.msgType === requestType.SERVER_CALLEESRESP){ console.log("接收到创建多个连接消息响应" + result.msgBody); createConnections(result.msgBody).catch((error)=>{ console.log("创建成员连接出错..." , result.msgBody,error); }); } }; // 创建多个点对点连接和video元素 async function createConnections(userNames) { const tempPcs = []; userNames.forEach(function (username){ tempPcs.push({ userName: username, pc: createRTCPeerConnection(username,createRemoteVideo(username)) }); }); // 发送多个用户的username和offer const offers = []; for (let i = 0; i < tempPcs.length; i++) { const pcc = tempPcs[i]; const offer = await pcc.pc.createOffer(offerOptions); pcc.pc.setLocalDescription(offer).catch(()=>{ console.log(pcc.userName + ">>> setLocalDescription error" , offer); }); console.log("offer 创建完成...",offer); offers.push({ userName: pcc.userName, sdp: offer }); } if(offers.length !== 0){ console.log("发送 多个offer消息...",offers); ws.send(JSON.stringify({ msgType: requestType.SDP_OFFER, msgBody: offers })); } } // 创建点对点连接 function createRTCPeerConnection(username,remoteVideo){ const configuration = getSelectedSdpSemantics(); console.log('RTCPeerConnection configuration:', configuration); const pc = new RTCPeerConnection(configuration); console.log(username+'>>> Created peer connection object pc'); pc.addEventListener('icecandidate', e => onIceCandidate(e,username)); pc.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc, e)); pc.addEventListener('track', e => { if (remoteVideo.srcObject !== e.streams[0]) { remoteVideo.srcObject = e.streams[0]; console.log(username+'>>> pc received remote stream'); } }); localStream.getTracks().forEach(track => pc.addTrack(track, localStream)); pcs.push({ userName: username, pc: pc }); return pc; } // function createRemoteVideo(id) { let remoteVideo = document.createElement('video'); remoteVideo.playsinline = true; remoteVideo.autoplay = true; remoteVideo.id=id; //获取body节点 localVideo.parentNode.insertBefore(remoteVideo, localVideo.nextSibling); return remoteVideo; }
-
客户端接收到offer消息时,创建连接,响应answer;并发起创建多个连接的请求消息,收到信令服务器响应的成员信息,创建多个连接,发送多个用户offer给信令服务器,并将之前创建的用户连接放入连接池中。
// 接收到offer消息时 if(result.msgType === requestType.SDP_OFFER){ console.log("接收到offer消息" , result.msgBody); // 创建 const rtcPeerConnection = createRTCPeerConnection(result.msgBody.userName, createRemoteVideo(result.msgBody.userName)); rtcPeerConnection.setRemoteDescription(JSON.parse(msgBody.sdp)).catch((error)=>{ console.log(result.msgBody.userName + ">>> setRemoteDesc error" , msgBody.sdp,error); }); // 应答 rtcPeerConnection.createAnswer().catch((error)=>{ console.log(result.msgBody.userName + ">>> createAnswer error" , error); }).then(answer=>{ rtcPeerConnection.setLocalDescription(answer).catch(()=>{ console.log(result.msgBody.userName + ">>> setLocalDesc error" , answer); }); // 响应answer ws.send(JSON.stringify({ msgType: requestType.SDP_ANSWER, msgBody: { userName:msgBody.userName, sdp: answer } })); }); // 发送创建多个请求消息 console.log("发送创建多个请求消息..."); ws.send(JSON.stringify({ msgType: requestType.CLIENT_MULTIPLY_CONNECTION_CREATEQUE, msgBody: user })); }
-
客户端收到单个answer消息时,放入对应用户连接的
desc
中// 接收到answer消息时 if(result.msgType === requestType.SDP_ANSWER){ console.log("接收到answer消息..."); pcs.forEach(function (pcc) { if(pcc.userName === msgBody.userName){ pcc.pc.setRemoteDescription(JSON.parse(msgBody.sdp)).catch(()=>{ console.log(msgBody.userName + ">>> setRemoteDesc error" + msgBody.sdp); }); } }); }
-
客户端设置发送candidate(包含用户信息和candidate)
async function onIceCandidate(event,username) { try { console.log(username+">>> event.candidate..." + event.candidate); ws.send(JSON.stringify( { msgType: requestType.SDP_CANDIDATE, msgBody: { userName: username, sdp: event.candidate } } )); console.log(username+">>> send candidate msg success"); } catch (e) { onAddIceCandidateError(pc, e); } }
-
客户端收到单个candidate消息时,添加到对应用户连接的IceCandidate中
// 接收到candidate消息时 if(result.msgType === requestType.SDP_CANDIDATE){ console.log("接收到candidate消息..."); pcs.forEach(function (pcc) { console.log(pcc.userName + "----" + msgBody.userName); if(pcc.userName === msgBody.userName){ pcc.pc.addIceCandidate(JSON.parse(msgBody.sdp)).catch((error)=>{ console.log(msgBody.userName + ">>> addIceCandidate error" , msgBody.sdp,error); }); console.log(`${pcc.pc} addIceCandidate success`); } }); }
推荐博文
参考博文
https://blog.youkuaiyun.com/heqinghua217/article/details/52174541 webRtc+websocket多人视频通话