使用webRTC开发一对多的音视频通信

    科技2022-08-03  109

    1、首先在使用webRTC是需要https的,但在开发时可以通过设置Chrome来进行开发

    1)点桌面上的Chrome图票,右键->属性,把目票输入框中的内容换成以下内容

    "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --unsafely-treat-insecure-origin-as-secure="http://ip:port" --user-data-dir=本地目录

    把字符串中的ip:port换成你自己的服务器的ip加端口,本地目录就是你自己本地一个文件夹

    2)再重启chrome就可以了

    在进行一对多通信时,首先了解一些一对一的音视频是如何通信的要进行通信主要的就是媒体协商这个过程 媒体协商相关的方法: createOffer、createAnswer、setLocalDescription、setRemoteDescription、 还有addTrack(track,stream); 还有一些事件: onicecandidate、ontrack 这里offer、answer都是交换的sdp(一个信息格式的标准)、这里交换的sdp就是一些比如会话元、 网络描述、流描述、安全描述、服务质量等信息;信息的交换是通过信令服务器进行中转

    2、如果只是实现一个端对端通信了解以下的过程就最够开发使用了、接下来看看整个端到端的流程

    3、一对多的通信、我这里就选择比较简单的方式,就是使用多个一对一来实现

    这里信令服务器使用node开发、使用socket.io (由于我项目是集成在了vue中使用的下面就先分享一下,原生的demo,demo中可能很多逻辑没有实现,因为主要是了解整个协商和通信的过程)

    主要的思路: 以直播会议的场景举例,两个角色,一个是boss,一个是client,boss发起会议,然后监听otherjoined,每次有client加入就重新创建一个连接实例与之建立一对一的通信,当然每个boss和client都是一一对应的,这里用一个唯一client的socketId来进行标识

    1、boss方代码如下: 这里的“123” 就是代表的房间id 只是写死了

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>boos</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> </head> <body> <video id="localvideo" autoplay playsinline></video> <div> <button id="connect">connect</button> <button id="leave">leave</button> </div> </body> <script> let localStream = null; let socket = null; let pBoos = null; let peerList = {}; connect.onclick = function () { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { console.log("不支持navigator.mediaDevices") return; } else { let constraints = { video: true, audio: true } navigator.mediaDevices.getUserMedia(constraints) .then(getMediaStream) .catch(handlError); } }; leave.onclick = function(){ if (socket) { socket.emit('leave', { code: '123', name: "boss" }); } closePreeConnection(); closeLocalMedia(); connect.disabled = false; leave.disabled = true; } function sendMessage(code, data,socketId) { if (socket) { socket.emit('message', data, code, "boss",socketId); } } function closePreeConnection() { //销毁连接 console.log("closePreeConnection") if (peerList) { //全部清理掉 for(id in peerList){ peerList[id].close(); peerList[id] = null; } } } function closeLocalMedia() { if (localStream && localStream.getTracks()) { localStream.getTracks().forEach((track) => { track.stop(); }) } localStream = null; console.log('closeLocalMedia'); } function createPeerConnection(socketId) { pBoos = new RTCPeerConnection(); pBoos.onicecandidate = (e) => { console.log("candidate---------", e); if (e.candidate) { //当发现candidate后 发送出去(潜在的连接端点称为 ICE 候选者。) sendMessage("123",{ type:"candidate", data: e.candidate },socketId); } } //媒体协商 先添加流 localStream.getTracks().forEach((track) => { //将本地采集的音视频流添加到pBoos中 调用者中 pBoos.addTrack(track, localStream); }); //媒体协商 let offerOptions = { offerToRecieveAudio: 1, offerToRecieveVideo: 1 } pBoos.createOffer(offerOptions).then((e)=>getOffer(e,socketId)).catch(handleOfferError); peerList[socketId] = pBoos; } function getOffer(desc,socketId) { console.log(socketId,"----------getOffer"); pBoos.setLocalDescription(desc); sendMessage("123",desc,socketId); //发送给接受端 } function handleOfferError(error) { console.log(error) } function conn() { socket = io.connect("localhost:3000"); socket.emit('makeMeeting', { code: '123', name: "boss" }); //这里的code相当于是房间id 就暂时写死 socket.on("otherjoined", (room,name,socketId) => { console.log('otherjoined---------',room,socketId); createPeerConnection(socketId); //创建连接 connect.disabled = true; leave.disabled = false; }); socket.on("message", (code, data) => { //监听answer if (data.msg.type === 'answer') { //调用者收到回答的answer peerList[data.socketId].setRemoteDescription(new RTCSessionDescription(data.msg)); //设置调用方的dcp } if(data.msg.type === "candidate"){ peerList[data.socketId].addIceCandidate(data.msg.data); } }); socket.on("otherleaved",(room, name,socketID)=>{ console.log(room,name,socketID,"otherleaved------------------"); peerList[socketID].close(); peerList[socketID] = null; }) } function getMediaStream(stream) { localvideo.srcObject = stream; localStream = stream; conn();//与scoketio进行连接 并且接受服务器消息 } function handlError(error) { console.log(error); } </script> </html>

    client方代码如下

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>client</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> </head> <body> <video width="340" height="240" id="remotevideo" autoplay playsinline></video> <div> <button id="connect">connect</button> <button id="leave">leave</button> </div> </body> <script> let pClient = null; let socketID; function sendMessage(code, data,socketId) { if (socket) { socket.emit('message', data, code, "bbb",socketId); } } leave.onclick = function () { if (socket) { socket.emit('leave', { code: '123', name: "周林阳",socketID}); } closePreeConnection(); connect.disabled = false; leave.disabled = true; } connect.onclick = function () { conn();//与scoketio进行连接 并且接受服务器消息 } function conn() { socket = io.connect("localhost:3000"); socket.emit('joinMeeting', { code: '123', name: "clientZz" }); socket.on("userjoined", (room,id) => { socketID = id; console.log(id,'joined---------'); createPeerConnection(); //创建连接 connect.disabled = true; leave.disabled = false; }); socket.on("message", (code, data) => { console.log(data,"---------------message") if (data.msg.type === 'offer' && data.socketId === socketID) { //被调用方 收到offer后 pClient.setRemoteDescription(new RTCSessionDescription(data.msg)); pClient.createAnswer().then((e)=>getAnswer(e,socketID)).catch(handleAnswerError); } if (data.msg.type === "candidate" && data.socketId === socketID) { console.log("candidate"); pClient.addIceCandidate(data.msg.data); } }) } function closePreeConnection() { //销毁连接 console.log("closePreeConnection") if (pClient) { pClient.close(); pClient = null; } } function getAnswer(desc,socketId) { pClient.setLocalDescription(desc); sendMessage("123", desc,socketId); //这里的123 就是会议room 暂时写死了 } function handleAnswerError(error) { console.log(error); } function createPeerConnection() { console.log("createPeerConnection--------------") if (!pClient) { pClient = new RTCPeerConnection(); pClient.onicecandidate = (e) => { if(e.candidate){ //当发现candidate后 发送出去 sendMessage("123", { type: "candidate", data: e.candidate },socketID)} } pClient.ontrack = (e) => { console.log("ontrack-------------", e) remotevideo.srcObject = e.streams[0]; } //数据通了的时候 } } </script> </html>

    信令服务器代码:

    var app = require('express')(); var http = require('http').createServer(app); var io = require('socket.io')(http); app.get('/', (req, res) => { res.end("666") }); //服务器监听 io.on('connection', (socket) => { //没个用户就是一个新的连接 let code = '';//设置会议的id let joinID = ''; //记录参加会议的id console.log('a user connected'); //建立会议群聊 socket.on('makeMeeting', (data) => { code = data.code; //房间号 let name = data.name; let myRoom = io.sockets.adapter.rooms[code]; if (!myRoom) { socket.join(code);//加入房间 socket.emit("bossjoined", code, socket.id); }else{ socket.emit("errorMake", code, socket.id); } }); //加入会议群聊 socket.on('joinMeeting', (data) => { //加入会议 console.log(socket.id, "----------joinMeeting") //判断是否有这个房间 joinID = data.code; let name = data.name; let myRoom = io.sockets.adapter.rooms[joinID];// if (!myRoom) { socket.emit("noRoom", joinID, socket.id); } else { socket.join(joinID);//加入房间 socket.emit("userjoined", joinID, socket.id); socket.to(joinID).emit(`otherjoined`, joinID, name, socket.id); //给房间里广播 } }); //监听群聊信息 socket.on(`message`, (data, room, name, socketId) => { //广播群聊 console.log("getMsg-------", data.type, room); socket.to(room).emit(`message`, room, { msg: data, name: name, socketId }); //给房间里广播 }); //监听离开房间 socket.on("leave", (data) => { let { code, name, socketId } = data; console.log(code, name) socket.emit("leaved", code); socket.to(code).emit(`otherleaved`, code, name, socketId); //给房间里广播 }) }); http.listen(3000, () => { console.log('listening on *:3000'); });

    代码可能比较冗余,但主要是测试,所以没有优化,主要了解流程以后大家就可以随意发挥了鸭

    效果如下

    如果有错误请及时留言,改正

    Processed: 0.011, SQL: 8