Webrtc 视频流在本地主机中正常工作但在生产中不工作(使用 google 转服务器)

Webrtc video streaming working properly in localhost but not working in production (using google turn server)

在本地服务器中,视频流 运行 在两端都是正确的,但是当网络发生变化时,一个节点的视频不会显示给另一个节点。

我正在使用 google stun 服务器将对等点连接到另一个网络,但是一个用户的视频流不可见 这是我的代码

我无法理解我做错了什么here.I我是 webrtc 的新手

<!DOCTYPE html>
<html lang="en">
<body>
{#######  variable Initialization #######}
<script>
    let webSocket;
    let mapPeers = {};
    let username = 'user_{{ request.user.id }}';
</script>

<script>

    const iceConfiguration = {
    iceServers: [
        {
            urls: 'stun:stun.l.google.com:19302',
        }
        ]
    }

    function webSocketOnMessage(event){

        let parsedData = JSON.parse(event.data);
        console.log(parsedData);
        let peerUserName = parsedData['peer'];
        let action = parsedData['action'];
        console.log(parsedData);
        if(username === peerUserName){
            return;
        }

        let receiver_channel_name = parsedData['message']['receiver_channel_name'];

        if(action === 'new-peer'){
            if (!(peerUserName in mapPeers)){
                createOfferer(peerUserName, receiver_channel_name);
                return;
            }
            else {
                setTimeout(() => {
                    if (!(peerUserName in mapPeers)){
                        createOfferer(peerUserName, receiver_channel_name);
                        return
                    }
                }, 1000);
            }
        }

        if(action === 'new-offer'){
            let offer = parsedData['message']['sdp'];
            createAnswerer(offer, peerUserName, receiver_channel_name);
            return;
        }

        if(action === 'new-answer'){
            let answer = parsedData['message']['sdp'];
            let peer  = mapPeers[peerUserName][0];

            peer.setRemoteDescription(answer);
            return;
        }

        if(action === 'video-on'){
            manageVideoEl('on', peerUserName)
            return;
        }

        if(action === 'video-off'){
            manageVideoEl('off', peerUserName)
            return;
        }
    }
</script>


<script>

$(document).ready(function(){

    let loc = window.location;
    let wsStart = 'ws://';

    if(loc.protocol === 'https:'){
        wsStart = 'wss://';
    }

    let endPoint = wsStart + loc.host + '/ws/video/meet/'+ '{{ group_name }}/';

    webSocket = new WebSocket(endPoint);
    webSocket.addEventListener('open', (e)=>{
        sendSignal('new-peer', {'receiver_channel_name': '{{ group_name }}'});

    });
    webSocket.addEventListener('message', webSocketOnMessage);
    webSocket.addEventListener('close', (e)=>{
    });
    webSocket.addEventListener('error', (e)=>{
    });

    {% if is_group_creator %}
        sendNotificationOnMessage();
    {% endif %}
});


let localStream = new MediaStream();

    const constraints = {
        'video': true,
        'audio': true
    }

    const localVideo = document.querySelector('#local-video');

    const btnToggleAudio = document.querySelector('#btn-toggle-audio');
    const btnToggleVideo = document.querySelector('#btn-toggle-video');



    let userMedia = navigator.mediaDevices.getUserMedia(constraints)
        .then(stream => {

            localStream = stream;
            localVideo.srcObject = localStream;
            localVideo.muted = true;


            let audioTracks = stream.getAudioTracks();
            let videoTracks = stream.getVideoTracks();

            audioTracks[0].enabled = true;
            videoTracks[0].enabled = true;

            btnToggleAudio.addEventListener('click', ()=>{
                audioTracks[0].enabled = !audioTracks[0].enabled;

                if(audioTracks[0].enabled){
                    btnToggleAudio.classList.replace('mic-off', 'mic-on')
                    return;
                }
                btnToggleAudio.classList.replace('mic-on','mic-off');
            });

            btnToggleVideo.addEventListener('click', ()=>{
                videoTracks[0].enabled = !videoTracks[0].enabled;

                if(videoTracks[0].enabled){
                    btnToggleVideo.classList.replace('camera-off','camera-on');
                    sendSignal('video-on', {})
                    localVideo.srcObject = localStream;
                    return;
                }
                sendSignal('video-off', {})
                localVideo.srcObject = null;
                btnToggleVideo.classList.replace('camera-on','camera-off');
            });
        })
        .catch(error =>{
            {#console.log('Error accessing media devices', error);#}
        });

</script>


<script>

    function sendSignal(action, message){
        console.log("Sending message to other end");
        let jsonStr = JSON.stringify({
        'peer': username,
        'action': action,
        'message': message
        });
        console.log(jsonStr);
        webSocket.send(jsonStr);
    }

    function createOfferer(peerUserName, receiver_channel_name){
        console.log("creating offer");

        let peer = new RTCPeerConnection(iceConfiguration);
        addLocalTracks(peer);
        peer.addEventListener("icegatheringstatechange", ev => {
  switch(peer.iceGatheringState) {
    case "new":
      console.log("gathering is either just starting or has been reset");
      break;
    case "gathering":
      console.log("gathering has begun or is ongoing");
      break;
    case "complete":
      console.log("gathering has ended");
      break;
  }
});
        peer.addEventListener('icecandidate', (event)=>{

           if(event.candidate){
               console.log('new ice candidate', JSON.stringify(peer.localDescription));
               return;
           }
           sendSignal('new-offer', {
               'sdp':peer.localDescription,
               'receiver_channel_name': receiver_channel_name
           });
           // to notify video status of other users when new users join
           if(!localStream.getVideoTracks()[0].enabled){
               sendSignal('video-off', {})
           }

        });


        let dc = peer.createDataChannel('channel');
        dc.addEventListener('open', ()=>{
            console.log("dc connection opened");
        });
        dc.addEventListener('message', dcOnMessage);
        console.log("Creating video");
        let remoteVideo = createVideo(peerUserName);
        console.log("video created, setting track");
        setOnTrack(peer, remoteVideo);
        console.log("track setted");
        mapPeers[peerUserName] = [peer, dc];

        peer.addEventListener('iceconnectionstatechange', ()=>{
           let iceconnectionState = peer.iceConnectionState;

           if(iceconnectionState === 'failed' || iceconnectionState === 'disconnected' || iceconnectionState === 'closed'){
               delete mapPeers[peerUserName];

               if(iceconnectionState !== 'closed'){
                   peer.close();
               }

               removeVideo(remoteVideo);
           }
        });



        peer.createOffer()
            .then(o => peer.setLocalDescription(o))
            .then(() => {
                {#console.log("local description set successfully");#}
            });


    }


    function createAnswerer(offer, peerUserName, receiver_channel_name){
        let peer = new RTCPeerConnection(iceConfiguration);

        addLocalTracks(peer);
        let remoteVideo = createVideo(peerUserName);

        setOnTrack(peer, remoteVideo);
        peer.addEventListener('datachannel', e=>{
           peer.dc = e.channel;
            peer.dc.addEventListener('open', ()=>{
                {#console.log("dc connection opened");#}
            });
            peer.dc.addEventListener('message', dcOnMessage);
            mapPeers[peerUserName] = [peer, peer.dc];

        });

        peer.addEventListener('iceconnectionstatechange', ()=>{
           let iceconnectionState = peer.iceConnectionState;

           if(iceconnectionState === 'failed' || iceconnectionState === 'disconnected' || iceconnectionState === 'closed'){
               delete mapPeers[peerUserName];

               if(iceconnectionState !== 'closed'){
                   peer.close();
               }

               removeVideo(remoteVideo);
           }
        });

        peer.addEventListener('icecandidate', (event)=>{
           if(event.candidate){
               {#console.log('new ice candidate', JSON.stringify(peer.localDescription));#}

               return;
           }
           sendSignal('new-answer', {
               'sdp':peer.localDescription,
               'receiver_channel_name': receiver_channel_name
           });
        });

        peer.setRemoteDescription(offer)
        .then(() => {
            {#console.log('Remote description set successfully for %s', peerUserName);#}

            return peer.createAnswer();
        })
        .then(a => {
            {#console.log('Answer created');#}

            peer.setLocalDescription(a);
        })
    }

    function addLocalTracks(peer){
        localStream.getTracks().forEach(track => {
            peer.addTrack(track, localStream);
        });
        return;
    }


    function  createVideo(peerUserName){
        userId = peerUserName.split('_')[1]

        // Video element Creation
        let remoteVideo = document.createElement('video');
        remoteVideo.id = peerUserName + '-video';
        remoteVideo.autoplay = true;
        remoteVideo.playsInline = true;
        remoteVideo.classList.add('custom-video');
        remoteVideo.setAttribute('data-id', userId);
        addVideoToDOM(remoteVideo, userId)

        return remoteVideo;
    }

    function addVideoToDOM(video, userId){
        let videoContainer = document.querySelector('#video-container');
        $.getJSON(`/chat/call/participant/${userId}`, function(data){
            // Styling Elements
            video.style.backgroundImage = `url('${data.profile_image_url}')` // if video off then show this bg
            let nameTag = document.createElement('span');
            nameTag.classList.add('name-tag');
            nameTag.innerText = data.username;
            let participantActionsEl =  document.createElement('div');
            participantActionsEl.classList.add('participant-actions');
            let videoParticipantEl = document.createElement('div');
            videoParticipantEl.classList.add('video-participant');
            videoParticipantEl.appendChild(participantActionsEl);
            videoParticipantEl.appendChild(nameTag);
            videoParticipantEl.appendChild(video);
            videoParticipantEl.setAttribute('data-delete', 'true') // For removing element
            videoParticipantEl.setAttribute('data-id', userId) // For showing feature
            videoContainer.appendChild(videoParticipantEl);
            addMemberToList(data)
        })
    }

   

    function manageVideoEl(status, peerUserName) {
        const userId = peerUserName.split('_')[1]
        // After element in DOM update video element
        setTimeout(() => {
            let videoEl = document.querySelector(`video[data-id="${userId}"]`)
            if (videoEl !== null){
                // Saving source
                if (mapPeers[peerUserName]){
                    if (videoEl.srcObject != null){
                        mapPeers[peerUserName][2] = videoEl.srcObject;
                    }

                    if (status==='on'){
                        videoEl.srcObject = mapPeers[peerUserName][2] || null;
                    } else if (status==='off'){
                        videoEl.srcObject = null;
                    }
                }
            }
        }, 1000);
    }

    function setOnTrack(peer, remoteVideo){
        let remoteStream = new MediaStream();
        remoteVideo.srcObject = remoteStream;
        peer.addEventListener('track', async (event)=>{
            console.log(remoteStream);
           remoteStream.addTrack(event.track, remoteStream);
        });
    }

    function removeVideo(video){
        removeMemberFromList(video.dataset.id)
        video.closest(`[data-delete='true']`).remove()
    }

</script>



{######### script to talk with group consumer #######}
<script>
    let group_chatSocket_chat;
    let receiver_group = "{{group.id }}";
{% if is_call_starter and send_notifications %}
    function sendNotificationOnMessage()
    {

        group_chatSocket_chat = new ReconnectingWebSocket(
            'ws://' + window.location.host +
            '/ws/chat/group/' + "{{ group.name }}" + '/'+ "{{ request.user.id }}" +'/');

        group_chatSocket_chat.onopen = function(e){
            {#console.log("Connection open");#}
            let url = `${location.protocol + '//' + location.host}/chat/video/{{ join_url }}`;
            group_chatSocket_chat.send(JSON.stringify({
                            'message': `New Video call is started! Join Now Link: ${url}`,
                            'receiver_id': receiver_group,
                            'command': 'new_message',
                            'bucket_id': 0,
                        }));
        }


    }
{% endif %}


</script>

</body>

当您移出网络一侧时,webrtc ICE 候选收集过程可能会因路由器 NAT 和防火墙而失败,因此您必须在配置中有一个转向服务器,如果直接 p2p 连接建立失败,它将中继流量

我认为您的网络防火墙不支持 STUN(udp 打洞) 所以你必须在你的 iceConfiguration 中添加 turn 服务器地址。

您可以获得转服服务器地址https://www.twilio.com/stun-turn