git地址:webrtc-demonpminstallnodeapp.js//默认端口https://localhost:3000全局安装nodemon使用nodemonapp.js//热更新开始节点以https开头,可以从本地浏览器获取摄像头权限,输入https://localhost:3000https://localhost:3000/获取音视频设备信息https://localhost:3000/room完整项目展示本地流并连接远程Streamhttps://localhost:3000/mediastreamWebRTC主要提供三个核心API:音轨。RTCPeerConnection:用于建立P2P连接和传输多媒体数据。RTCDataChannel:建立双向通信数据??通道,可以传输多种数据类型。如果假设对等点A想要与对等点B建立WebRTC连接,则它需要执行以下操作:对等点A使用ICE生成其ICE候选者。在大多数情况下,它需要NAT(STUN)会话遍历实用程序或使用中继的NAT(TURN)服务器遍历。PeerA将ICE候选人和会话描述捆绑到一个对象中。该对象作为本地描述存储在对等点A中(对等点自己的连接信息),并通过信令机制与对等点B通信。这部分称为要约。对等点B收到提议并将其存储为远程描述(另一端对等点的连接信息)以供进一步使用。PeerB生成自己的ICEcandidate和sessiondescription,存储为本地描述,通过信令机制发送给peerA。这部分称为答案。(注:前面提到,步骤2和3中的ICE候选也可以单独发送)PeerA收到peerB的answer,存储为remotedescription。这样,双方都拥有彼此的连接信息,并且可以成功地开始通过WebRTC进行通信!//获取音视频设备初始化本地流函数start(){if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){console.log("不支持");return}else{vardeviceId=videoSource.value;//音视频采集varconstraints={video:{width:640,height:480,frameRate:15,//30,//帧率//facingMode:"enviroment"//cameradeviceId:deviceId?deviceId:undefined//视频设备id//设置后可以切换手机前后摄像头},audio:{noiseSuppression:true,//降噪echoCancellation:true//回声消除}}//只设置音频//varconstraints={//video:false,//audio:true//}navigator.mediaDevices.getUserMedia(constraints).then(gotMediaStream).then(gotDevices).catch(handleError)}}functiongotMediaStream(stream){console.log(stream)videoplay.srcObject=stream;//设置视频流window.stream=stream;//获取视频约束varvideoTrack=stream.getVideoTracks()[0];varvideoConstraints=videoTrack.获取设置();constraints.textContent=JSON.stringify(videoConstraints);//在页面上打印//audioplayer.srcObject=stream;//只设置音频returnnavigator.mediaDevices.enumerateDevices();//promisereturnstheninterfacethen}搭建信令服务器使用node+koa2搭建信令服务器importsocket.ionpmisocket.io--save//服务端代码constkoa=require('koa');constapp=newKoa();conststaticFiles=require('koa-static');constpath=require("path");//consthttp=require("http");consthttps=require("https");constfs=require("fs");constsocketIo=require('socket.io');constlog4js=require("log4js");letlogger=log4js.getLogger();logger.level="debug";app.use(staticFiles(path.resolve(__dirname,"public")));constoptions={key:fs.readFileSync("./server.key","utf8"),cert:fs.readFileSync("./server.cert","utf8")};letserver=https.createServer(options,app.callback())//constserver=http.createServer(app.callback());server.listen(3000,()=>{console.log(`startedlocalhost:${3000}`)});constio=socketIo(server);io.on('connection',(socket)=>{socket.on("join",(room)=>{socket.join(room);//varmyRoom=io.sockets.adapter.rooms[room];//获取当前房间,rooms是一个Map对象Map对象是set对象踩坑varmyRoom=io.sockets.adapter.rooms.get(room);//get获取Mapsize获取大小//用户数varusers=myRoom?myRoom.size:0;logger.debug('--房间内用户数--',users,'room',room);//是一对一直播if(users<9){//少于三个人socket.emit("joined",room,socket.id);if(users>1){socket.to(room).emit("otherjoin",room,socket.id);}}else{//消除大于三个的房间socket.leave(room);socket.emit(“满”,房间,socket.id);//房间已满}//发送给自己//socket.emit("joined",room,socket.id);//发送给除了你自己以外的这个节点这个站点上的每个人//发送给这个站点上除了你自己之外的每个人//socket.broadcast.emit("joined",room,socket.id);//发送给房间里除了你自己以外的每个人//socket.to(room).emit("joined",room,socket.id)//发送给房间里的每个人//io.in(room).emit('加入',房间,socket.id);});socket.on("离开",(room)=>{//varmyRoom=io.sockets.adapter.rooms[room];varmyRoom=io.sockets.adapter.rooms.get(room);console.log("myRoom",myRoom);varusers=myRoom?myRoom.size:0;logger.debug('--房间用户离开房间的数量--',users-1);////socket.broadcast.emit("leaved",room,socket.id);socket.to(room).emit('bye',room,socket.id);socket.emit("leaved",room,socket.id);//socket.leave(room);//io.in(room).emit('joined',room,socket.id);})socket.on("message",(room,data)=>{console.log("message",room)//socket.broadcast.emit("message",room,data)socket.to(room).emit("message",room,data)})});前端代码介绍//客户端代码//connectsocketfunctioncoon(){console.log("coon")socket=io.connect();//新用户加入socket.on("joined",(roomid,id)=>{console.log("received---joined",roomid,id);state="joined";createPeerConnecion();btnConn.disabled=true;btnLeave.disabled=false;console.log("recevie--joined-state--",state);});//其他joinssocket.on("otherjoin",(roomid,id)=>{console.log("-otherjoin-",roomid,id);//开始媒体协商if(state==="joined_unbind"){createPeerConnecion();//创建连接并绑定}state="joined_conn";call();//媒体协商console.log("recevie--otherjoin-state--",state);});//用户套接字已满.on("full",(roomid,id)=>{console.log("-full-",roomid,id);状态=“离开”;console.log("recevie--full-state--",state);套接字断开连接();//Disconnectalert("房间已满");//OK在有人退出后重新连接btnConn.disabled=false;btnLeave.disabled=true;});//离开socket.on("leaved",(roomid,id)=>{console.log("-leaved-",roomid,id);state="leaved";socket.disconnect();btnConn.disabled=false;btnLeave.disabled=true;console.log("recevie---leaved-state--",state);});//离开socket.on("bye",(roomid,id)=>{console.log("-bye-",roomid,id);state="joined_unbind";//未绑定状态console.log("bye-state--",state);closePeerConnection();});//服务端不管,客户端处理这些消息如何处理收到的端到端消息socket.on("message",(roomid,data)=>{console.log("收到客户端的消息",roomid,data);//媒体协商if(data){if(data.type==='offer'){//如果收到的是一个offerpeer已经创建pc.setRemoteDescription(newRTCSessionDescription(data));//创建答案pc.creatAnswer().then(getAnswer).catch(err=>{console.log("err未能创建getanswer")});}elseif(data.type==='answer'){pc.setRemoteDescription(newRTCSessionDescription(data));}elseif(data.type==='candidate'){varcandidate=newRTCIceCandidate({sdpMLineIndex:data.label,candidate:data.candidate});//添加到本地peercollectpc.addIceCandidate(candidate).then(()=>{console.log('添加冰候选成功');}).catch(err=>{console.error(err);});}else{console.error("消息无效!",data);}}});//发送消息socket.emit("join","111111");//添加房间return;}创建RTCPeerConnectionWebRTC,我们通过RTCPeerConnection建立通信双方之间的点对点连接。该接口提供了创建、维护、监视和关闭连接的方法的实现。为了建立连接,我们需要一个信令服务器,用于在浏览器之间建立通信时交换各种元数据。(信号)。同时还需要一个STUN或者TURN服务器来完成NAT穿透。连接建立主要包括两部分:信令交换和设置ICE候选项。RTCPeerConnection通用开源turn|stun服务器//stun:stun.l.google.com:19302//stun.xten.com//stun.voipbuster.com//stun.sipgate.net//stun.ekiga.net//stun.ideasip.com//stun.schlund.de//stun.voiparound.com//stun.voipbuster.com//stun.voipstunt.com//stun.counterpath.com//stun.1und1.de//stun.gmx.net//stun.callwithus.com//stun.counterpath.net//stun.internetcalls.com//numb.viagenie.cacreatepeerleticeServer={"iceServers":[//{//"url":"stun:stun.l.google.com:19302"//}],sdpSemantics:'plan-b',//sdpSemantics:'unified-plan',//bundlePolicy:'max-bundle',//iceCandidatePoolSize:0};//创建//浏览器兼容的PeerConnection写法letPeerConnection=(window.PeerConnection||window.webkitPeerConnection00||window.webkitRTCPeerConnection||window.mozRTCPeerConnection)//创建varpeer=newRTCPeerConnection(iceServer);valleyGoogleDebugGoogleDebug:chrome://webrtc-internals回声消除和降噪noiseSuppression在Firefox中完美运行,关闭此约束后,我可以很清楚地听到麦克风的声音参考:https://xirsys.com/developers/https://www.yuque.com/wangdd/...常用的视频会议和直播架构https://www.cnblogs.com/yjmyz...vue使用sockethttps://www.imooc.com/article...百度云盘分享音视频WebRTC实时互动直播技术介绍与实战:链接:https://pan.baidu.com/s/1NEJg...提取码:vi43
