本系列博客主要记录一对一WebRTC视频通话实现过程中的一些重点,代码全部进行了注释,便于理解WebRTC整体实现。
本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育
一对一WebRTC视频通话系列往期博客
一对一WebRTC视频通话系列(一)—— 创建页面并显示摄像头画面
一对一WebRTC视频通话系列(二)——websocket和join信令实现
一对一WebRTC视频通话系列(三)——leave和peer-leave信令实现
一对一WebRTC视频通话系列(四)——offer、answer、candidate信令实现
主要完成工作:
(1)点击离开时,要将RTCPeerConnection关闭(close);
(2)点击离开时,要将本地摄像头和麦克风关闭;
(3)检测到客户端退出时,服务器再次检测该客户端是否已经退出房间。
(4)RTCPeerConnection时传入ICE server的参数,以便当在公网环境下可以进行正常通话
一、客户端
(1)点击离开时,要将RTCPeerConnection关闭(close);
(2)点击离开时,要将本地摄像头和麦克风关闭;
点击离开按钮后,
doLeave() -> hangup() -> closeLocalStream()
-
doLeave()函数:发送消息离开房间。
首先,创建一个jsonMsg对象,包含命令(cmd)、房间ID(roomId)和本地用户ID(localUserId)。
使用JSON.stringify()方法将jsonMsg对象转换为字符串。
调用zeroRTCEngine.sendMessage()方法将消息发送给服务器。
输出消息内容到控制台。
调用hangup()函数关闭本地视频流和RTCPeerConnection。
-
hangup()函数:关闭本地视频流和RTCPeerConnection。
设置localVideo.srcObject为null,不显示本地流。
设置remoteVideo.srcObject为null,不显示对方。
调用closeLocalStream()函数关闭本地流。
检查pc变量是否为null,如果是,则关闭RTCPeerConnection。
-
closeLocalStream()函数:关闭本地视频流。
检查localStream变量是否为null,如果是,则停止所有视频流。
-
openLocalStream(stream)函数:用于打开本地流。
检查stream变量是否为null,如果是,则输出日志信息,并调用doJoin()函数
function doLeave() { var jsonMsg = { 'cmd': 'leave', 'roomId': roomId, 'uid': localUserId, }; var message = JSON.stringify(jsonMsg); //将json对象转换为字符串 zeroRTCEngine.sendMessage(message); //设计方法:用实现方法而不是直接用变量 console.info("doLeave message: " + message); hangup(); } function hangup() { localVideo.srcObject = null;//0.不显示本地流 remoteVideo.srcObject = null;//1.不显示对方 closeLocalStream();//2.关闭本地流 if(pc != null){ pc.close();//3.关闭RTCPeerConnection pc = null; } } function closeLocalStream() { if(localStream != null){ localStream.getTracks().forEach(function(track){ track.stop(); }); localStream = null; } }
关闭远程视频流,关闭RTCPeerConnection对象。
function handleRemotePeerLeave(message) { console.info("handleRomotePeerLeave message: " + message.remoteUid); remoteVideo.srcObject = null; if(pc != null){ pc.close();//关闭RTCPeerConnection pc = null; } }
二、服务端
(3)检测到客户端退出时,服务器再次检测该客户端是否已经退出房间。
var server = ws.createServer(function (conn) { console.log("New connection"); conn.sendText('我收到你的连接了') conn.client = null;//1.对应客户端信息 // 监听客户端发送的消息 conn.on("text", function (str) { console.info("Received msg:"+str); var jsonMsg = JSON.parse(str); switch(jsonMsg.cmd){ case SIGNAL_TYPE_JOIN: conn.client = handleJoin(jsonMsg, conn); //2.返回值需要修改 break; case SIGNAL_TYPE_LEAVE: handleLeave(jsonMsg); break; case SIGNAL_TYPE_OFFER: handleOffer(jsonMsg); break; case SIGNAL_TYPE_ANSWER: handleAnswer(jsonMsg); break; case SIGNAL_TYPE_CANDIDATE: handleCandidate(jsonMsg); break; } }); // 连接关闭时的输出 conn.on("close", function (code, reason) { console.log("Connection closed,code: " + code + " reason:" + reason); if(conn.client != null){ //3.如果客户还未删除 //强制让客户端从房间退出 handleForceLeave(conn.client); } }); // 监听连接错误 conn.on("error", function (error) { console.error("发生错误:", error); }) }).listen(port);
当连接关闭时,如果连接的客户端对象尚未删除,调用handleForceLeave函数强制让客户端从房间退出。
实现原理:
通过监听用户连接对象的 client.onclose 事件来触发 handleForceLeave 函数,并在其中强制离开用户。
1.首先,从用户连接对象中获取房间ID和用户ID。
2.然后,在全局变量roomTableMap 中查找房间ID对应的房间对象。
3.判断用户ID是否存在于房间内。如果不在房间内,则输出日志并返回。
4.如果用户已经在房间内,则输出日志并从房间对象中移除用户ID。
5.判断房间对象中是否还有其他用户。如果有其他用户,则给所有房间内其他用户发送用户离开消息。
function handleForceLeave(client){ // 获取房间ID和用户ID var roomId = client.roomId; var uid = client.uid; // 1.先查找房间号 var roomMap = roomTableMap.get(roomId); if(roomMap == null){ console.error("roomId: " + roomId + " is not exist"); return; } // 2.判别uid是否在房间内 if(roomMap.contains(uid) == false) { console.info("uid: " + uid + " have leave roomId " + roomId); return; } // 3.说明客户端没有正常离开,所以我们要强制离开程序 console.info("uid: " + uid + " force leave roomId " + roomId); roomMap.remove(uid); if(roomMap.size() >= 1){ var clients = roomMap.getEntrys(); for(var i in clients){ jsonMsg = { 'cmd':SIGNAL_TYPE_PEER_LEAVE, 'remoteUid':uid } // 给所有房间内其他用户发送用户离开消息 var msg = JSON.stringify(jsonMsg); var remoteUid = clients[i].key; var remoteClient =roomMap.get(remoteUid); if(remoteClient){ console.info("notify peer "+ remoteClient.uid +" uid " + uid + " leave room"); remoteClient.conn.sendText(msg); } } } }
三、公网通讯
(4)RTCPeerConnection时传入ICE server的参数,以便当在公网环境下可以进行正常通话
3.1 启动coturn
# nohup是重定向命令,输出都将附加到当前目录的 nohup.out 文件中; 命令后加 & ,后台执行起来后按 ctr+c,不会停止 sudo nohup turnserver -L 0.0.0.0 -a -u sxl:zxc -v -f -r nort.gov & # 前台启动 sudo turnserver -L 0.0.0.0 ‐a -u sxl:zxc -v -f -r nort.gov #然后查看相应的端口号3478是否存在进程 sudo lsof -i:3478
安装 sysstat包,查看网络情况
sudo apt-get install sysstat sudo sar -n DEV 1
3.2修改配置文件
修改客户端main.js代码,
var defaultConfiguration = { bundlePolicy: "max-bundle", rtcpMuxPolicy: "require", iceTransportPolicy:"relay",//relay 或者 all // 修改ice数组测试效果,需要进行封装 iceServers: [ { "urls": [ "turn:192.168.226.3:3478?transport=udp", "turn:192.168.226.3:3478?transport=tcp" // 可以插入多个进行备选 ], "username": "sxl", "credential": "zxc" }, { "urls": [ "stun:192.168.226.3:3478" ] } ] };
new RTCPeerConnection()时,使用defaultConfiguration参数来创建对象。
pc = new RTCPeerConnection(defaultConfiguration);
测试
设置configuration,先设置为relay中继模式,只有relay中继模式可用的时候,才能部署到公网去(部署到公网后也先测试relay)。
使用realy中继:
使用局域网P2P
可以看出采用realy中继通信下,网络流量明显大于p2p。
下图显示了网络协商打印情况:
还没有评论,来说两句吧...