05.webrtc实例
1、项目结构:
2、前端:
a、index.html
<!DOCTYPE html> <html> <head> <title> WebRTC demo </title> </head> <body> <h1> WebRTC Demo </h1> <div id="buttons"> <input id="zero-roomid" type="text" placeholder="请输入房间ID" maxlength="40" /> <input type="button" id="joinBtn" value="加入" /> <input type="button" id="leaveBtn" value="离开" /> </div> <div id="videos"> <video id="localVideo" autoplay muted playsinline>本地窗口</video> <video id="remoteVideo" autoplay playsinline>远端窗口</video> </div> <script type="text/javascript" src="js/main.js"></script> <script type="text/javascript" src="js/adapter-latest.js"></script> </body> </html>
b、main.js
'use strict' const SIGNAL_TYPE_JOIN = 'join'; //join 主动加入房间 const SIGNAL_TYPE_RESP_JOIN='resp-join'; //告知加入者对方是谁 const SIGNAL_TYPE_LEAVE ='leave'; //主动离开房间 const SIGNAL_TYPE_NEW_PEER ='new-peer'; //有人加入房间,通知已经在房间的人 const SIGNAL_TYPE_PEER_LEAVE ='peer-leave'; //有人离开房间,通知已经在房间的人 const SIGNAL_TYPE_OFFER = 'offer'; //发送offer给对端peer const SIGNAL_TYPE_ANSWER = 'answer'; // 对端发送回的answer const SIGNAL_TYPE_CANDIDATE='candidate'; // candidate 发送candidate给对端peer var localVideo = document.querySelector('#localVideo'); var remoteVideo = document.querySelector('#remoteVideo'); var localStream = null; var remoteStream = null; var pc = null; var localUserId = Math.random().toString(36).substring(2); var remoteUserId = -1; var roomId = 0; var yhRTCEngine; // //说明:发送本机candidate到信令服务器 // function handleIceCandidate(event){ console.info("handleIceCandidate"); if(event.candidate){ // console.info(event.candidate); var jsonMsg = { 'cmd':SIGNAL_TYPE_CANDIDATE, 'roomId':roomId, 'uid':localUserId, 'remoteUid':remoteUserId, 'msg':JSON.stringify(event.candidate) }; var message = JSON.stringify(jsonMsg); yhRTCEngine.sendMessage(message); //candidate信息发送给信令服务器 console.info("handleIceCandidate message"+message); //console.info("handleIceCandidate message"); } else { console.info(event.candidate) console.error("end of candidates"); } } // //说明:显示对端视频 // function handleRemoteStreamAdd(event){ console.info("handleRemoteStreamAdd"); remoteStream = event.streams[0]; remoteVideo.srcObject = remoteStream; } function handleConnectionStateChange(){ if(pc != null){ console.info("connectionState->"+pc.connectionState); } } function handleIceConnectionStateChange(){ if(pc != null){ console.info("IceConnectionState->"+pc.iceConnectionState); } } // //说明:创建RTCPeerConnection(由本地计算机到远端的WebRTC连接) // function createPeerConnection(){ console.info("createPeerConnection") var defaultConfiguration = { bundlePolicy:"max-bundle", rtcpMuxPolicy:'require', iceTransportPolicy:'all', //relay只能中继,all可以p2p iceServers:[ { "urls":[ "turn:192.168.0.5:3478?transport=udp", "turn:192.168.0.5:3478?transport=tcp" ], "username":'ziff', "credential":"123456" }, { "urls":[ "stun:192.168.0.5:3478" ] } ] } //1.创建RTCPeerConnection //pc = new RTCPeerConnection(defaultConfiguration); pc = new RTCPeerConnection(); //2.绑定事件 pc.onicecandidate = handleIceCandidate; //发送本机candidate到信令服务器 pc.ontrack = handleRemoteStreamAdd; //显示对端视频 pc.onconnectionstatechange = handleConnectionStateChange; pc.oniceconnectionstatechange = handleIceConnectionStateChange //3.加入本地视频流 localStream.getTracks().forEach(track=>{ pc.addTrack(track,localStream); }) } // //说明:设置本机的SDP信息添加到PeerConnection,并将信息发送给信令服务器,通知对端客户端B // function createOfferAndSendMessage(session){ //1.设置本机的SDP信息添加到PeerConnection pc.setLocalDescription(session) .then(()=>{ var jsonMsg = { 'cmd':SIGNAL_TYPE_OFFER, 'roomId':roomId, 'uid':localUserId, 'remoteUid':remoteUserId, 'msg':JSON.stringify(session) //本机的sdp信息 }; var message = JSON.stringify(jsonMsg); //2.将sdp信息发送给信令服务器,通知对端客户端B yhRTCEngine.sendMessage(message); //发送到信令服务器 //console.info("send offer message:"+message); console.info("send offer message:"); }) .catch(error=>{ console.error("offer setLocalDiscription failed:"+error); }); } // //处理offer异常 // function handleCreateOfferError(error){ console.error("handleCreateOfferError:"+error); } // //说明:用户 B 创建一个 Answer,并添加到 PeerConnection 中(setLocalDescription); //用户 B 通过信令服务器将 answer 转发给用户 A; function createAnswerAndSendMessage(session){ //1.设置本机的SDP信息添加到PeerConnection console.info("session") console.info(session) pc.setLocalDescription(session) .then(()=>{ var jsonMsg = { 'cmd':SIGNAL_TYPE_ANSWER, 'roomId':roomId, 'uid':localUserId, 'remoteUid':remoteUserId, 'msg':JSON.stringify(session) //B端本机的sdp信息 }; var message = JSON.stringify(jsonMsg); //2.将sdp信息发送给信令服务器,通知对端客户端A yhRTCEngine.sendMessage(message); //发送到信令服务器 //console.info("send answer message:"+message); console.info("send answer message:"); }) .catch(error=>{ console.error("answer setLocalDiscription failed:"+error); }); } // //处理answer异常 // function handleCreateAnswerError(error){ console.error("handleCreateAnswerError:"+error); } var YHRTCEngine = function(wsUrl){ this.init(wsUrl); yhRTCEngine = this; return this; } YHRTCEngine.prototype.init = function(wsUrl){ //设置websocket url this.wsUrl = wsUrl; //websocket对象 this.signaling = null; } YHRTCEngine.prototype.createWebSocket = function(){ yhRTCEngine = this; yhRTCEngine.signaling = new WebSocket(this.wsUrl); //websocket对象 yhRTCEngine.signaling.onpen = function(){ //连接websocket yhRTCEngine.onOpen(); } yhRTCEngine.signaling.onmessage = function(event){ //监听客户端信息 yhRTCEngine.onMessage(event); } yhRTCEngine.signaling.onerror = function(){ yhRTCEngine.onError(); } yhRTCEngine.signaling.onclose = function(){ yhRTCEngine.onClose(); } } YHRTCEngine.prototype.onOpen = function(){ console.log("websocket onopen"); } // //说明:接收信令服务器返回信息 // YHRTCEngine.prototype.onMessage = function(evet){ console.log("websocket onMessage"); //console.log(evet.data); var jsonMsg; try { jsonMsg = JSON.parse(evet.data); } catch(e){ console.warn("onMessage "+e); return; } switch(jsonMsg.cmd){ case SIGNAL_TYPE_NEW_PEER: //1.有新的人加入房间,create peerConnect,send offer handleRemoteNewPeer(jsonMsg); break; case SIGNAL_TYPE_RESP_JOIN: //通知新进来的用户,房间原来的用户信息 handleResponseJoin(jsonMsg); break; case SIGNAL_TYPE_PEER_LEAVE: //有人离开房间,告知房间里的人 handleRemotePeerLeave(jsonMsg); break; case SIGNAL_TYPE_OFFER: //接收对端offer handleRemoteOffer(jsonMsg); break; case SIGNAL_TYPE_ANSWER: //接收对端发送回的answer handleRemoteAnswer(jsonMsg); break; case SIGNAL_TYPE_CANDIDATE: //用户 A 和 用户 B 都接收到了 candidate 信息后,都通过信令服务器转发给对方并添加到 PeerConnection 中(addIceCandidate); handleRemoteCandidate(jsonMsg); break; } } YHRTCEngine.prototype.onError = function(event){ console.log("websocket onError"); } YHRTCEngine.prototype.onClose = function(event){ console.log("websocket onClose"+event.code+", reason:"+EventTarget.reason); } YHRTCEngine.prototype.sendMessage = function(message){ this.signaling.send(message); } yhRTCEngine = new YHRTCEngine("ws://127.0.0.1:3000"); //yhRTCEngine = new YHRTCEngine("ws://192.168.0.5:3000"); yhRTCEngine.createWebSocket(); // //说明:将新进来的用户信息通知房间原来已经在里面的用户 // function handleRemoteNewPeer(message){ console.info("handleRemoteNewPeer, remoteUid:"+message.remoteUid); remoteUserId = message.remoteUid; doOffer(); } // //说明:通知新进来的用户,房间原来的用户信息 // function handleResponseJoin(message){ console.info("handleResponseJoin, remoteUid:"+message.remoteUid); remoteUserId = message.remoteUid; } // //说明:收到有人离开 // function handleRemotePeerLeave(message){ console.info("handleRemotePeerLeave, remoteUid:"+message.remoteUid); remoteVideo.srcObject = null; //关闭对方视频 if(pc !=null){ pc.close(); pc = null; } } // //说明:处理对端A传过来的offer // function handleRemoteOffer(message){ console.info("handleRemoteOffer"); if(pc == null){ //1.创建PeerConnection createPeerConnection(); } var desc = JSON.parse(message.msg); //2.将对端的SDP信息添加到 PeerConnection 中(setRemoteDescription) pc.setRemoteDescription(desc); //3.用户 B 创建一个 Answer,并添加到 PeerConnection 中(setLocalDescription);通过信令服务器将 answer 转发给用户 A doAnswer(); } // //说明:处理对端B发送回来的answer //用户 A 接收到 answer 后将其添加到 PeerConnection 中; // function handleRemoteAnswer(message){ console.info("handleRemoteAnswer"); var desc = JSON.parse(message.msg); pc.setRemoteDescription(desc); //设置对端B的SDP信息 } // //说明:对端在创建PeerConnection会获取candidate信息并发给信令服务器 //将对方的Candidate添加到 PeerConnection 中(addIceCandidate); // function handleRemoteCandidate(message){ console.info("handleRemoteCandidate"); var candidate =JSON.parse(message.msg); pc.addIceCandidate(candidate).catch(e=>{ console.error("addIceCandidate failed:"+e.name); }) } // //用户 A 作为发起方创建 offer(offer 中包含了 SDP 信息), //并将获取的本地 SDP 信息添加到 PeerConnection 中(setLocalDescription), //然后再通过信令服务器转发给用户 B; // function doOffer(){ //1.创建RTCPeerConnection if(pc == null){ createPeerConnection(); } pc.createOffer().then(createOfferAndSendMessage).catch(handleCreateOfferError); } // //说明:用户 B 创建一个 Answer,并添加到 PeerConnection 中(setLocalDescription); //用户 B 通过信令服务器将 answer 转发给用户 A //用户 A 接收到 answer 后将其添加到 PeerConnection 中; // function doAnswer(){ pc.createAnswer().then(createAnswerAndSendMessage).catch(handleCreateAnswerError); } // //加入 //roomId:房间号 // function doJoin(roomId){ var jsonMsg = { 'cmd':SIGNAL_TYPE_JOIN, 'roomId':roomId, 'uid':localUserId }; var message = JSON.stringify(jsonMsg); yhRTCEngine.sendMessage(message); console.info("发送给服务端信息:"+message); } // //说明:离开房间 // function doLeave(){ var jsonMsg = { 'cmd':SIGNAL_TYPE_LEAVE, 'roomId':roomId, 'uid':localUserId } var message = JSON.stringify(jsonMsg); yhRTCEngine.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(track=>{ track.stop(); }) } } //打开本地视频 function openLocalStream(stream){ console.log(stream) doJoin(roomId); localVideo.srcObject = stream; //页面显示本地视频 localStream = stream; } function initLocalStream(){ navigator.mediaDevices.getUserMedia({ audio:true, video:true }).then(openLocalStream) .catch(e=>{ alert("getUserMedia() error:"+e); }) } document.getElementById('joinBtn').onclick= function(){ console.log('加入按钮被点击'); roomId = document.getElementById("zero-roomid").value; if(roomId == "" || roomId == "请输入房间ID"){ alert('请输入房间ID'); return; } //初始化本地码流 initLocalStream(); } //离开 document.getElementById('leaveBtn').onclick= function(){ console.log('离开按钮被点击'); doLeave(); }
3、后端:
signal_server.js
var ws = require('nodejs-websocket') var port = 3000 const SIGNAL_TYPE_JOIN = 'join'; //join 主动加入房间 const SIGNAL_TYPE_RESP_JOIN='resp-join'; //告知加入者对方是谁 const SIGNAL_TYPE_LEAVE ='leave'; //主动离开房间 const SIGNAL_TYPE_NEW_PEER ='new-peer'; //有人加入房间,通知已经在房间的人 const SIGNAL_TYPE_PEER_LEAVE ='peer-leave'; //有人离开房间,通知已经在房间的人 const SIGNAL_TYPE_OFFER = 'offer'; //发送offer给对端peer const SIGNAL_TYPE_ANSWER = 'answer'; // 发送offer给对端peer const SIGNAL_TYPE_CANDIDATE='candidate'; // candidate 发送candidate给对端peer var YHRTCMap = function(){ this._entrys = new Array(); this.put = function (key,value){ if(key == null || key == undefined){ return; } var index = this._getIndex(key); if(index == -1){ var entry = new Object(); entry.key = key; entry.value = value; this._entrys[this._entrys.length] = entry; //添加 } else { this._entrys[index].value = value; //覆盖原来的值 } } this.get = function(key){ var index = this._getIndex(key); return (index != -1) ? this._entrys[index].value : null; } this.remove = function(key){ var index = this._getIndex(key); if(index != -1){ this._entrys.splice(index,1); } } this.clear = function(){ this._entrys.length = 0; } this.contains = function(key){ var index = this._getIndex(key); return (index != -1) ? true : false; } this.size = function(){ return this._entrys.length; } this.getEntrys = function(){ return this._entrys; } this._getIndex = function(key){ if(key == null || key == undefined){ return -1; } var _length = this._entrys.length; for(var i = 0; i < _length; i++){ var entry = this._entrys[i]; if(entry == null || entry == undefined){ continue; } if(entry.key === key){ return i; } } return -1; } } var roomTableMap = new YHRTCMap(); // //说明:客户端信息 // function Client(uid,conn,roomId){ this.uid = uid; //用户所属的id this.conn = conn; // uid对应的websocket连接 this.roomId = roomId; } // //说明:客户端新加进来操作 //参数:mesage客户端的信息:roomId房间号,uid用户ID //conn客户端websocket链接 function handleJoin(message,conn){ var roomId = message.roomId; //房间号 var uid = message.uid; //用户ID console.log('uid:'+uid+' try to join romm '+roomId); //1.这个房间号是否已创建 var roomMap = roomTableMap.get(roomId); if(roomMap == null){ roomMap = new YHRTCMap(); //没有则创建 roomTableMap.put(roomId,roomMap); } //2.房间已满 if(roomMap.size() >= 2){ console.error("roomId:"+roomId +"已经有两人存在,请使用其它房间"); //通知客户端,房间已满 return null; } //3.添加用户信息到房间 var client = new Client(uid,conn,roomId); //客户端信息 roomMap.put(uid,client); //添加用户信息到房间 //4./房间里面已经有人,通知对方 if(roomMap.size() > 1){ //房间里面已经有人,通知对方 var clients = roomMap.getEntrys(); for(var i in clients){ var remoteUid = clients[i].key; //客户端(对端的)uid if(remoteUid != uid){ //跳过自己 var jsonMsg = { 'cmd':SIGNAL_TYPE_NEW_PEER, 'remoteUid':uid //新进来的用户ID } var msg = JSON.stringify(jsonMsg); var remoteClient = roomMap.get(remoteUid); console.info('new-peer:'+msg); remoteClient.conn.sendText(msg); //将新进来的用户信息通知房间原来已经在里面的用户 jsonMsg = { 'cmd':SIGNAL_TYPE_RESP_JOIN, 'remoteUid':remoteUid //房间原来在里面的用户ID } msg = JSON.stringify(jsonMsg); console.info('resp-join:'+msg); conn.sendText(msg); // 通知新进来的用户,房间原来的用户信息 } } } return client; } // //说明:客户端有人离开 // function handleLeave(message){ var roomId = message.roomId; var uid = message.uid; console.info("uid:"+uid+"try to leave room "+roomId); //1.获取所在房间号 var roomMap = roomTableMap.get(roomId); if(roomMap == null){ console.error("can't find the roomId "+roomId); return; } //2.删除用户 roomMap.remove(uid); //删除房间用户 //3.通知其它人,有人离开 if(roomMap.size() >= 1){ var clients = roomMap.getEntrys(); //该房间所有客户端信息 for(var i in clients){ var jsonMsg = { 'cmd':SIGNAL_TYPE_PEER_LEAVE, 'remoteUid':uid } var msg = JSON.stringify(jsonMsg); var remoteUid = clients[i].key; //console.info("查看下客户端信息"); //console.info(clients[i].value); var remoteClient = clients[i].value; // roomMap.get(remoteUid); //获取客户端信息 //console.info(remoteClient); if(remoteClient){ console.info("notify peer :"+ remoteClient.uid+" uid:"+uid+" leave"); remoteClient.conn.sendText(msg); } } } } // //说明:断开连接,强制用户离开房间 // function handleForceLeave(client){ var roomId = client.roomId; var uid = client.uid; //1.获取所在房间号 var roomMap = roomTableMap.get(roomId); if(roomMap == null){ console.warn("can't find the roomId "+roomId); return; } //2.判断uid是否在房间 if(!roomMap.contains(uid)){ console.warn("uid:"+uid +" have leave roomId "+roomId); return; } console.info("uid:"+uid+" force leave room "+roomId); //2.删除用户 roomMap.remove(uid); //删除房间用户 //3.通知其它人,有人离开 if(roomMap.size() >= 1){ var clients = roomMap.getEntrys(); //该房间所有客户端信息 for(var i in clients){ var jsonMsg = { 'cmd':SIGNAL_TYPE_PEER_LEAVE, 'remoteUid':uid } var msg = JSON.stringify(jsonMsg); var remoteUid = clients[i].key; //console.info("查看下客户端信息"); //console.info(clients[i].value); var remoteClient = clients[i].value; // roomMap.get(remoteUid); //获取客户端信息 //console.info(remoteClient); if(remoteClient){ console.info("notify peer :"+ remoteClient.uid+" uid:"+uid+" leave"); remoteClient.conn.sendText(msg); } } } } // //说明:处理客户端发送过来的offer,将其转发给对端B // function handleOffer(message){ var roomId = message.roomId; var uid = message.uid; //自己用户ID var remoteUid = message.remoteUid; //对端用户ID console.info("handleOffer uid:"+uid+" transfer offer to remoteUid "+remoteUid); var roomMap = roomTableMap.get(roomId); //获取房间信息 if(roomMap == null){ console.error("handleOffer can't find the roomId "+roomId); return; } if(roomMap.get(uid) == null){ console.error("handleOffer can't find the uid "+uid); return; } var remoteClient = roomMap.get(remoteUid); //对端用户信息 if(remoteClient){ var msg = JSON.stringify(message); remoteClient.conn.sendText(msg); //转发给对端客户端含SDP信息 } else{ console.error("can't find remoteUid "+remoteUid); } } // //说明: // function handleAnswer(message){ var roomId = message.roomId; var uid = message.uid; //自己用户ID var remoteUid = message.remoteUid; //对端用户ID console.info("handleAnswer uid:"+uid+" transfer answer to remoteUid "+remoteUid); var roomMap = roomTableMap.get(roomId); //获取房间信息 if(roomMap == null){ console.error("handleAnswer can't find the roomId "+roomId); return; } if(roomMap.get(uid) == null){ console.error("handleAnswer can't find the uid "+uid); return; } var remoteClient = roomMap.get(remoteUid); //对端用户信息 if(remoteClient){ var msg = JSON.stringify(message); //console.info(msg); remoteClient.conn.sendText(msg); //转发给对端 } else{ console.error("can't find remoteUid "+remoteUid); } } // //说明: // function handleCandidate(message){ var roomId = message.roomId; var uid = message.uid; //自己用户ID var remoteUid = message.remoteUid; //对端用户ID console.info("handleCandidate uid:"+uid+" transfer candidate to remoteUid "+remoteUid); var roomMap = roomTableMap.get(roomId); //获取房间信息 if(roomMap == null){ console.error("handleCandidate can't find the roomId "+roomId); return; } if(roomMap.get(uid) == null){ console.error("handleCandidate can't find the uid "+uid); return; } var remoteClient = roomMap.get(remoteUid); //对端用户信息 if(remoteClient){ var msg = JSON.stringify(message); //console.info("remoteUid:"+remoteUid+" uid:"+uid); remoteClient.conn.sendText(msg); //转发给对端 } else{ console.error("can't find remoteUid "+remoteUid); } } var server = ws.createServer(conn=>{ console.log("创建一个新的连接...."); //conn.sendText("我是服务端,收到你客户端的连接了"); conn.client = null; conn.on('text',str=>{ // console.info("recv msg:"+str); var jsonMsg = JSON.parse(str); switch(jsonMsg.cmd){ case SIGNAL_TYPE_JOIN: conn.client = handleJoin(jsonMsg,conn); 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",(code,reason)=>{ console.info("连接关闭 code:"+code+",reason:"+reason); if(conn.client != null){ handleForceLeave(conn.client); //强制离开 } }); conn.on("error",err=>{ console.info("监听错误:"+err); }); }).listen(port);