当第 3 个人加入聊天时,webrtc 视频聊天不起作用

webrtc video chat doesn't work when 3rd person joins the chat

我第一次尝试使用 webrtc 进行视频聊天应用程序,我希望每次聊天最多有 3 个人......我的代码适用于 2 人聊天

但是一旦第 3 个人加入聊天,一切都出错了...我在页面中得到多个视频标签,其中 none 来自第 3 个梨....我将不胜感激指针或建议大多数教程涵盖 2 人聊天

这里正在工作url

https://chate-test-3000.herokuapp.com/

这是我的代码

const PEARS = [];
var video_counter = 0 ;
const STREAMES = [] ;

var myVideoArea = document.querySelector('#myvideo');

var configuration = {
    'iceServers': [{
        'url': 'stun:stun.l.google.com:19302'
    }]
};
var rtcPeerConn;

const ROOM = 'caht1';
const SIGNAL_ROOM = 'newsingal1234567898765';

io = io.connect("" ,  {transports:['websocket']});
io.emit('ready' , { chat_room : ROOM , signaling_room : SIGNAL_ROOM});


io.emit('signal' , { text :'ready for video ? ' , room : SIGNAL_ROOM , type : 'user_here'});
io.on('signlaing_message' , function(data){

   console.log('signal recived');
   console.log(data);

  if(!PEARS.includes(data.pear_id))
  {
    console.log('adding new pear --- ' , data.pear_id);
    PEARS.push(data.pear_id);
    startSignaling(data.pear_id);
  }

  if (data.type != "user_here")
  {
        var message = JSON.parse(data.message);
        if (message.sdp) {
            rtcPeerConn.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
                // if we received an offer, we need to answer
                if (rtcPeerConn.remoteDescription.type == 'offer') {
                    rtcPeerConn.createAnswer(sendLocalDesc, logError);
                }
            }, logError);
        }
        else {
            rtcPeerConn.addIceCandidate(new RTCIceCandidate(message.candidate));
        }
  }

})


function startSignaling(pear_id) {


    if(!rtcPeerConn)
    rtcPeerConn = new RTCPeerConnection(configuration);

    // send any ice candidates to the other peer
    rtcPeerConn.onicecandidate = function (evt) {
        if (evt.candidate)
            io.emit('signal',{"type":"ice candidate", "message": JSON.stringify({ 'candidate': evt.candidate }), "room":SIGNAL_ROOM});
        displaySignalMessage("completed that ice candidate...");
    };

    // let the 'negotiationneeded' event trigger offer generation
    rtcPeerConn.onnegotiationneeded = function () {
        displaySignalMessage("on negotiation called");
        rtcPeerConn.createOffer(sendLocalDesc, logError);
    }

    // once remote stream arrives, show it in the remote video element
    rtcPeerConn.ontrack = function (evt) {
        displaySignalMessage("going to add their stream...");

        video_counter++ ;
        let vid = 'video-box-'+video_counter  ;
        console.log('adding new STREAM  !!')
        console.log('###### streams  ' , evt.streams);

        if(!STREAMES.includes(evt.streams[0].id))
        {
            STREAMES.push(evt.streams[0].id);
            $('#video-wrapper').append(`<video data-id="${evt.streams[0].id}" id="${vid}" autoplay loop autobuffer muted playsinline controls></video>`);
            console.log(' video length ..... ' , $('#video-wrapper').find('#'+vid).length );
            var theirVideoArea = $('#video-wrapper').find('#'+vid)[0];
            console.log(theirVideoArea);
            theirVideoArea.srcObject = evt.streams[0] ;
            theirVideoArea.play();
        }
        
    };

    // get a local stream, show it in our video tag and add it to be sent
        navigator.getUserMedia = navigator.getUserMedia  || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        navigator.getUserMedia({
            'audio': true,
            'video': true
        }, function (stream) {
            displaySignalMessage("going to display my stream...");

            myVideoArea.srcObject = stream
            myVideoArea.play();

            for (const track of stream.getTracks()) {
            rtcPeerConn.addTrack(track, stream);
            }

        }, logError);

}


function sendLocalDesc(desc) {
    rtcPeerConn.setLocalDescription(desc, function () {
        displaySignalMessage("sending local description");
        io.emit('signal',{"type":"SDP", "message": JSON.stringify({ 'sdp': rtcPeerConn.localDescription }), "room":SIGNAL_ROOM});
    }, logError);
}

function logError(error) {
    $('#error-area').append(`<div> ${error.name} : ${error.message}</div>`);
}

function displaySignalMessage(text  ){
    $('#signal-area').append(`<div>${text}</div>`);
}

我还使用一个简单的 nodejs 服务器发送信号并使用 socket.io 连接到服务器

------------------------ 编辑 - PEER.JS -------------- ----

这是我切换到 peerjs 后的代码

const SIGNAL_ROOM = 'zxsingalroom';
var MY_PEER_ID = '' ;
const CurrentPeers = [] ;
io = io.connect("" ,  {transports:['websocket']});
io.emit('ready' , { chat_room : ROOM , signaling_room : SIGNAL_ROOM});



var peer = new Peer({
    config: {'iceServers': [
            { url: 'stun:stun.l.google.com:19302' },
        ]} /* Sample servers, please use appropriate ones */
});

peer.on('open', function(id) {
    console.log('My peer ID is: ' + id);
    MY_PEER_ID = id ;
    io.emit('peer_id_offer' , {  chat_room  : ROOM , id : id});
});

peer.on('call' , function (call) {


    navigator.mediaDevices.getUserMedia({ video : true , audio : true })
        .then((stream) => {

            call.answer(stream);
            call.on('stream' , function(remoteStream){
                if(!CurrentPeers.includes(call.peer))
                {
                    CurrentPeers.push(call.peer);
                    addRemoteVideo(remoteStream);
                }

            })


        })
        .catch( (e)=>{
            console.log('error2' , e );
        });

})


io.on('peer_id_recived' , function(data){

    console.log(`peer id recived : `);
    console.log(data);


    for (let [key, value] of Object.entries(data.peer_ids)) {
        if(value.peer_id != MY_PEER_ID)
        {
            callPeer(value.peer_id);
        }
    }

});


function callPeer( id )
{
        console.log('calling peers 1 .... ');
        navigator.mediaDevices.getUserMedia({ video : true , audio : true })
        .then(  (stream) => {
            console.log('calling peers  2 .... ' + id);
            addOurVideo(stream);

            let  call = peer.call(id , stream);
            console.log( typeof call);
            call.on('stream' , function(remoteStream){
                console.log('calling peers  3 .... ');
                if(!CurrentPeers.includes(call.peer))
                {
                    CurrentPeers.push(call.peer);
                    addRemoteVideo(remoteStream);
                }

            })
        })
        .catch( (e)=>{
            console.log('error1' , e );
        });
}

function addRemoteVideo(stream){

    console.log(' adding remote stream!!!');
    let total_perrs = CurrentPeers.length ;
    let vid = `video-box-${total_perrs}`;

   $('#video-wrapper').append(`<video  id="${vid}" autoplay loop autobuffer muted playsinline controls></video>`);
    var theirVideoArea = $('#video-wrapper').find('#'+vid)[0];
   theirVideoArea.srcObject = stream ;
   theirVideoArea.play();

}
function addOurVideo(stream){

    console.log(' adding our stream');
    var ourVideArea = $('#video-wrapper').find('#our-video')[0];
    ourVideArea.srcObject = stream ;
    ourVideArea.play();

}

您应该使用某种 P2P 或媒体服务器来处理来自不同客户端的多个同时连接 PeerJS 是一个不错的选择。 对于 WebRTC: ICE failed, add a TURN server and see about:webrtc for more details 错误,它的确切含义是 STUN 服务器用于创建连接,但如果无法建立 P2P 连接,则回退是所有通信都通过 TURN 服务器,因此它们需要高资源和带宽. TURN 服务器通常不是免费的,但可能会解决您的问题的一个开源选项是使用 COTURN 服务器 https://github.com/coturn/coturn 您应该将以下示例配置放入您的 PeerJS 选项

"iceServers": [
         {
          "urls": "stun:vc.example.com:3478"
         },
         {
          "urls": "turn:vc.example.com:3478",
          "username": "coturnUser",
          "credential": "coturnUserPassword"
         }
      ],

您可以在 urls 之前指定 "iceTransportPolicy": "relay" 以仅使用中继服务器(无 P2P)