简体   繁体   English

WebRTC:无法接收远程视频或音频

[英]WebRTC: Unable to to receive remote video or audio

The goal is to have a webrtc video/audio p2p connection. 目的是建立一个webrtc视频/音频p2p连接。 I did take some examples and put them together and manage to get far enough that "most" exchange (via eventsource on NodeJS) is done, just remote stream is not received. 我确实举了一些例子,将它们放在一起,设法达到足够的距离(通过NodeJS上的eventsource)完成“大多数”交换,只是没有接收到远程流。

I found that Chrome browser had a bug in which the request is send twice (any fix for this?). 我发现Chrome浏览器存在一个错误,该错误将请求发送两次(对此是否有任何修复?)。

But even when I use firefox (on laptop and phone), there is just no stream received. 但是,即使我使用Firefox(在笔记本电脑和手机上),也没有收到任何视频流。 with no error on both sides. 双方都没有错误。

This is the full script: 这是完整的脚本:

    if (!console || !console.log) {
       var console = {
        log: function() {}
      };
    }

    // Ugh, globals.
    // const signaling = new SignalingChannel(); hier moet een module voor komen, als een EventServerSide en/of Websocket...
    var peerc;
    var source = new EventSource("events");
    var selfView = document.getElementById('localvideo');
    var remoteView = document.getElementById('remotevideo');
    //var TextHistory = ["apple", "orange", "cherry"]; //[ ];
    var audio = null;
    var audioSendTrack = null;
    var video = null;
    var videoSendTrack = null;
    var chatbox = document.getElementById("chatbox");
    var constraints = {video:true, audio:true};
    var configuration = { 
      iceServers: [{
        'urls': 'stun:stun.l.google.com:19302'
      }]
    };
    var started = false;
    var pc;


    $("#incomingCall").modal();
    $("#incomingCall").modal("hide");

    $("#incomingCall").on("hidden", function() {
      document.getElementById("incomingRing").pause();
    });

    source.addEventListener("ping", function(e) {}, false);

    source.addEventListener("userjoined", function(e) {
      appendUser(e.data);
    }, false);

    source.addEventListener("userleft", function(e) {
      removeUser(e.data);
    }, false);

    source.addEventListener("offer", async function(e) {
      log("offer received");
      if (!pc) {
        log("PeerConnection ontbreekt");
        warmup(false); 
      } else {
        warmup(true); 
      }

      try {
        const message = JSON.parse(e.data);  
        if (message.desc) {
          log("Offer: Desc");
          const desc = message.desc;

          // if we get an offer, we need to reply with an answer
          if (desc.type == 'offer') {
            log("Offer: Desc offer");
            var connectionState = pc.connectionState;
            log(connectionState);
            await pc.setRemoteDescription(JSON.parse(desc));  // Parse nodig?
            await pc.setLocalDescription(await pc.createAnswer());
            var connectionState = pc.connectionState;
            log(connectionState);
            //signaling.send(JSON.stringify({desc: pc.localDescription}));
              jQuery.post(
                "offer", {
                  to: message.from,
                  from: document.getElementById("user").innerHTML,
                  desc: JSON.stringify(pc.localDescription)
                },
                function() { log("Offer sent!"); }
              ).error("desc", error);
          } else {
            log("Offer: Desc answer");
            await pc.setRemoteDescription(JSON.parse(desc));
          }
        } else if (message.start) {
          log("Offer: Start received from: " + message.from);
          started = true;
          voor = message.from
          van  = message.to
          if (audio && audioSendTrack) {
            log("Wacht op audio");
            await audio.sender.replaceTrack(audioSendTrack);
          }
          if (video && videoSendTrack) {
            log("Wacht op video");
            await video.sender.replaceTrack(videoSendTrack);
          }
          log("Offer: Started....wat next???");
        } else {
          log("Offer: wacht op candidate");
          log(message.candidate);
          await pc.addIceCandidate(message.candidate);
        }
      } catch (err) {
        console.log(err);
        log("Error in Offer event: " + err);
      }
    }, false);

    source.addEventListener("message", function(e) {
      var message = JSON.parse(e.data);
      text = "User: " + message.from + " - Say: " + message.text;
      WriteChat(text);
    }, false);

    source.addEventListener("answer", function(e) {
      var answer = JSON.parse(e.data);
      peerc.setRemoteDescription(new RTCSessionDescription(JSON.parse(answer.answer)), function() {
        console.log("Call established!");
      }, error);
    }, false);

    function log(info) {
      var d = document.getElementById("debug");
      d.innerHTML += info + "\n\n";
    }

    function appendUser(user) {
      // If user already exists, ignore it
      var index = users.indexOf(user);
      if (index > -1)
        return;

      users.push(user);
      console.log("appendUser: user = " + user + ", users.length = " + users.length);


      var d = document.createElement("div");
      d.setAttribute("id", btoa(user));

      var a = document.createElement("button");
      a.setAttribute("class", "vertical-align");
      a.setAttribute("onclick", "initiateCall('" + user + "');"); //Dit moet dus prive chat worden.
      a.innerHTML = "<i class='icon-user icon-white'></i> " + user;

      d.appendChild(a);
      d.appendChild(document.createElement("br"));
      document.getElementById("users").appendChild(d);
    }

    function removeUser(user) {
      // If user already exists, ignore it
      var index = users.indexOf(user);
      if (index == -1)
        return;

      users.splice(index, 1)

      var d = document.getElementById(btoa(user));
      if (d) {
        document.getElementById("users").removeChild(d);
      }
    }

    function sendPrive(user) {
      log("Prive message"); // + JSON.stringify(offer));
      if(!user) { user = "niets!"};
      jQuery.post(
        "message", {
          to: user,
          from: document.getElementById("user").innerHTML,
          text: JSON.stringify(message)
        },
        function() { console.log("Message sent!"); }
      ).error("privemsg",error);

    }

    function offer(data){
      log("Offer to send message: " + JSON.stringify(data));
    // start
    // desc + type
    // candidate


      jQuery.post(
        "offer", {data: JSON.stringify(data)},
        function() { console.log("Message sent!"); }
      ).error("offer",error);

    }

    function BroadcastMessage() {
      log("Broadcast Message"); // + JSON.stringify(offer));  //function uitbreiden met argumentinvoer
      message = document.getElementById("text_input").value;
      jQuery.post(
        "message", {
          to: user,
          from: document.getElementById("user").innerHTML,
          text: JSON.stringify(message)
        },
        function() { console.log("Message sent!"); }
      ).error("BroadcastMessage",error);
      //WriteChat(msg);
    }

    // Dit is een interne, dit moet dus via events gaan!!
    function WriteChat(text){
      // kan nog user en tijd bijkomen...
      chatbox.innerHTML += text + "<br>";   // deze werkt dus niet meer, canvas wil andere methode
    }

    // Call warmup() to warm-up ICE, DTLS, and media, but not send media yet.
    async function warmup(isAnswerer) {
      log("Warming up...");
      pc = new RTCPeerConnection(configuration);
      if (!isAnswerer) {                          //uitzoeken waarom deze uitgeschakelen...
        audio = pc.addTransceiver('audio');
        video = pc.addTransceiver('video');
      }

      // send any ice candidates to the other peer
      pc.onicecandidate = (event) => {
        log("Offer: onicecandidate...");
        //signaling.send(JSON.stringify({candidate: event.candidate}));
        log(event.candidate)
        if(event.candidate){
          jQuery.post(
            "offer", {
            to: voor,
            from: document.getElementById("user").innerHTML,
            data: event.candidate,              // debugging...
            candidate: event.candidate
          }, // hier zijn we nu...candidate blijft leeg????
          function() { log("Offer: onicecandidate sent!"); }
        ).error("onicecandidate",error);
        } else {
          log("geen candidate");
        }
      };

      pc.onnegotiationneeded = function() {
        log("negotiation nodig...");
        //var connectionState = RTCPeerConnection.connectionState;
        pc.createOffer().then(function(aanbod) {
          var connectionState = pc.connectionState;
          log(connectionState);
          log(JSON.stringify(aanbod));
          return pc.setLocalDescription(aanbod);
        })
        .then(function() {
          log(JSON.stringify(pc.localDescription));
          jQuery.post(
            "offer", {
              to: document.getElementById("user").innerHTML,
              from: voor,
              desc: JSON.stringify(pc.localDescription)
            },
            function() { log("Offer: localDescription sent!"); }
          ).error("onnegotiationneeded",error);
        })
        .catch(error);
      }

    log("Remote video");
      // once media for the remote track arrives, show it in the remote video element
      pc.ontrack = async (event) => {
        log("start ontrack...");
        remoteView.srcObject = event.streams[0];
        remoteView.play();
        selfView.play();
        log("oude ontrack...");
        try {
          log(event.track.kind);
          if (event.track.kind == 'audio') {
            log("Track heeft audio");
            if (isAnswerer) {
              log("beantwoord audio");
              audio = event.transceiver;
              audio.direction = 'sendrecv';
              if (started && audioSendTrack) {
                await audio.sender.replaceTrack(audioSendTrack);
              }
            }
          }
        } catch (err) {
          console.log(err);
          log("Error in audio ontrack: " + err);
        }
        try {
          log(event.track.kind);
          if (event.track.kind == 'video') {
            log("Track heeft video");
            if (isAnswerer) {
              log("beantwoord video");
              video = event.transceiver;
              video.direction = 'sendrecv';
              if (started && videoSendTrack) {
                await video.sender.replaceTrack(videoSendTrack);
              }
            }
          }
        } catch (err) {
          console.log(err);
          log("Error in video ontrack: " + err);
        }
        try {
          // don't set srcObject again if it is already set.
          if (!remoteView.srcObject) {
            log("Nog geen remote video...");
            remoteView.srcObject = new MediaStream();
          }
          log("Voeg remote video toe...");
          log(event.track);
          remoteView.srcObject.addTrack(event.track);
        } catch (err) {
          console.log(err);
          log("Error in last ontrack: " + err);
        }
      };
    log("Local video & audio");
      try {
        // get a local stream, show it in a self-view and add it to be sent
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        selfView.srcObject = stream;
        audioSendTrack = stream.getAudioTracks()[0];
        if (started) {
          log("Started = True => audio");
          log(audio)
          await audio.sender.replaceTrack(audioSendTrack);
        }
        videoSendTrack = stream.getVideoTracks()[0];
        if (started) {
          log("Started = True => video");
          log(video)
          await video.sender.replaceTrack(videoSendTrack);
        }
      } catch (err) {
        console.log(err);
        log("Error in local video & audio: " + err);
      }
      log("Warmup completed");
    }


    function initiateCall(user) {
      log("Start van video verzoek");
      // Verander UI
      //document.getElementById("main").style.display = "none";
      //document.getElementById("call").style.display = "block";
      log("Voor gebruiker: " + user);
      voor = user;
      log("Van gebruiker:  " + document.getElementById("user").innerHTML);
      van = document.getElementById("user").innerHTML;
      started = true;
      //offer(JSON.stringify({start: true}));
      jQuery.post(
        "offer", {
          to: user,
          from: document.getElementById("user").innerHTML,
          start: true
        },
        function() { console.log("Offer sent!"); }
      ).error("initiateCall",error);
    }


    function endCall() {
      log("Ending call");
      document.getElementById("call").style.display = "none";
      document.getElementById("main").style.display = "block";

      document.getElementById("localvideo").mozSrcObject.stop();
      document.getElementById("localvideo").mozSrcObject = null;
      document.getElementById("remotevideo").mozSrcObject = null;

      peerc.close();
      peerc = null;
    }

    function error(from,e) {
      if (typeof e == typeof {}) {
        //alert("Oh no! " + JSON.stringify(e));
        log("Oh no! " + from + " ERROR: " + JSON.stringify(e));
      } else {
        alert("Oh no!!!! " + from + " ERROR: " + e);
      }
      //endCall();
    }

    var users = [];
    users.push(document.getElementById("user").innerHTML);

Please note that I combined examples for: 请注意,我结合了以下示例:

  • developer.mozilla.org developer.mozilla.org
  • w3c.github.io w3c.github.io
  • www.html5rocks.com So there might be unnecessary code If required, I can put all code in repository and share that for full view. www.html5rocks.com因此,可能会有不必要的代码如果需要,我可以将所有代码放在存储库中并共享以得到完整的视图。

Additional logging shows the following errors: DOMException: "Cannot add ICE candidate when there is no remote SDP" InvalidModificationError: Cannot set local offer when createOffer has not been called. 其他日志记录显示以下错误:DOMException:“在没有远程SDP时无法添加ICE候选者” InvalidModificationError:在未调用createOffer时无法设置本地商品。

You should try to use a TURN-Server. 您应该尝试使用TURN服务器。 Even for usage in development. 甚至用于开发中。 The traffic will be relayed if a direct P2P-connection can't be stablished. 如果无法建立直接的P2P连接,则将中继流量。

Have a look at Coturn. 看看Coturn。 I can't say that this will be the solution for the current problem, but in my opinion most of the issues will be solved - and for production use, you will need it definitely. 我不能说这将是当前问题的解决方案,但我认为大多数问题都将得到解决-对于生产用途,您肯定需要它。

I found the problem and solution. 我找到了问题和解决方案。

First: The STUN/TURN server was not a problem, this 第一:STUN / TURN服务器不是问题,这
urls: 'stun:stun.l.google.com:19302' Is enough for local network development. urls:'stun:stun.l.google.com:19302'就足以进行本地网络开发。 But I did install turnserver (ubuntu 18) which seems fine. 但是我确实安装了turnserver(ubuntu 18),看起来不错。

Second: My problem was first in de message exchange, you need to make sure the OFFER/ANSWER go to right clients. 第二:我的问题首先是在消息交换中,您需要确保将OFFER / ANSWER发送给合适的客户。

Last: The negotiation needs to be stable before sending/receiving media. 最后:在发送/接收媒体之前,协商必须保持稳定。 I modified my code that connection and start video sending are separated. 我修改了连接和开始视频发送分开的代码。 Also this allows for just one side to send media or not. 而且,这仅允许一侧发送或不发送媒体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM