2

Justin Uberti posted this response to a StackOverflow question about how WebRTC connections work:

why doesn't "onicecandidate" work?

Now I've spent the better part of a week setting up a stun-turn server, and when it comes to setting the connections, they tell you to send the object as a string made from JSON.stringify(description)

Now for the life of me I cannot get this to work in his JS example, which I've changed here:

var pc1, pc2, offer, answer, offer_str, offer_prsd, answer_str, answer_prsd;


pc1 = new webkitRTCPeerConnection(getServerOptionsFromUsername("mr_boombosstik"));
pc2 = new webkitRTCPeerConnection(getServerOptionsFromUsername("countdracula"));

pc1.onicecandidate = function(candidate) {
  pc2.addIceCandidate(candidate);
};

pc2.onicecandidate = function(candidate) {
  pc1.addIceCandidate(candidate);
};

//PC1 KICKS OFF THE PROCESS WITH AN OFFER!!
pc1.createOffer(onOfferCreated, onError);

function onError(err) {
  window.alert(err.message);
}

function onOfferCreated(description) {
  offer = description;
  //HERE IS THE OFFER CREATED!!!
  pc1.setLocalDescription(offer, onPc1LocalDescriptionSet, onError);
  offer_str = JSON.stringify(offer);
  //WE SET IT AS "OUR LOCAL DESCRIPTIION
}

function onPc1LocalDescriptionSet() {
    offer_prsd = JSON.parse(offer_str);
    console.log(offer_prsd.sdp);
  // after this function returns, pc1 will start firing icecandidate events
  pc2.setRemoteDescription(offer_prsd, onPc2RemoteDescriptionSet, onError);
}

function onPc2RemoteDescriptionSet() {
  pc2.createAnswer(onAnswerCreated, onError);
}

function onAnswerCreated(description) {
  answer = description;
  pc2.setLocalDescription(answer, onPc2LocalDescriptionSet, onError);
}


function onPc2LocalDescriptionSet() {
  // after this function returns, you'll start getting icecandidate events on pc2
  pc1.setRemoteDescription(answer, onPc1RemoteDescriptionSet, onError);
}

function onPc1RemoteDescriptionSet() {
  window.alert('Yay, we finished signaling offers and answers');
}

So can anyone tell me why this won't work? If I can’t get the session description set locally, how am I going to get it done through a signalling service of some kind?

Community
  • 1
  • 1
user3238414
  • 125
  • 2
  • 12
  • Are you wanting to see a connection in the same window or two different ones? Are you handling Ice-trickling(Ice candidates gathered over time)? Why on earth are you using JSon but not exchanging anything with a signalling server? – Benjamin Trent May 07 '14 at 13:04
  • Are you creating streams before starting the negotiation sequence ? Creating a SDP without streams attached means nothing, and I guess it would fail. Also, it seems you're trying to call another peer in the same page : my guess would be "You don't need JSON, nor a signaling server". – Jb Drucker May 07 '14 at 14:07
  • Sure I want to, but I tried doing a rough trial using ajax and couldnt get the information to parse out correctly or whatnot. If I can't get it to work without sending it through a signalling service or something, its going to be a lot harder to figure out later on – user3238414 May 07 '14 at 15:35
  • do you get any exception ? – Muath May 07 '14 at 16:18
  • Mismatched type exception – user3238414 May 07 '14 at 17:28
  • @JbDrucker, no, it would not fail without a stream. There being a stream has nothing to do with the descriptions be exchanged. Now, there will be no media to stream between them but the SDPs are still created and set. – Benjamin Trent Sep 03 '14 at 12:52
  • Yeah, but I slightly remember a Chrome and/or Firefox bug which caused an error if no stream (be it audio/video or data) was created or attached before creating the offer. Maybe it's fixed now. – Jb Drucker Sep 03 '14 at 18:04

3 Answers3

3

I found this question while googling this exact problem. I'm 99% certain the cause here is the JSON stringifying/parsing of the description.

Here's proof. Take the simple RTCPeerConnection example at http://simpl.info/rtcpeerconnection/

In main.js, insert this line before line 87:

description = JSON.parse(JSON.stringify(description));

[So it'll be inserted before the line remotePeerConnection.setRemoteDescription(description); in the function gotLocalDescription(description).]

This will cause the following error:

Uncaught TypeMismatchError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': The 1st argument provided is either null, or an invalid RTCSessionDescription object.

Apparently Javascript's string/JSON handling is shitty enough to alter the description object when converting it to and from JSON. I spent a lot of time thinking the problem was with my signaling code, but it's this JSON conversion that is the culprit.

Edit: I take back my unkind and libelous words against Javascript. My emotions got the better of me in the heat of the moment. Here's the solution.

It seems as though JSON stringify/parse cannot handle objects that have embedded methods, which makes sense. The description object has embedded _proto_ methods, which you don't need to transmit to the peer via signaling. You only need to deliver the sdp portion of the object. Actually, I don't really know if all of that is exactly true, but whatever, it works.

Example from peer A:

var msg = description.sdp;
...transmit message to peer B...

Peer B:

...receive message from peer A...
remote_descr = new RTCSessionDescription();
remote_descr.type = "offer";
remote_descr.sdp  = msg

If you want, you could use JSON stringify/parse and send both the type and sdp together in one object, instead of setting the type manually.

paulwal222
  • 1,488
  • 11
  • 18
  • Would inputting a standard object, obtained from parsing the JSON string, to RTCPeerConnection.setRemoteDescription(input) work, as it worked what you did of making a new RTCSessionDescription object to input there? The detail is that here https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription/RTCSessionDescription it says that the constructor is deprecated, and now it is unclear to me what kind of input would work. – algo Jan 14 '23 at 03:42
0

I found some errors in the code. I will highlight them below. The following code adds all the needed descriptions and ICE candidates just fine in Chrome(obviously because of webkit prefix).

    STUN = {'stun:stun.l.google.com:19302'};
    iceServers = { iceServers: [STUN] };
    DtlsSrtpKeyAgreement = { DtlsSrtpKeyAgreement: true };
    optional = { optional: [{ DtlsSrtpKeyAgreement: true}] };

    var pc1, pc2, offer, answer, offer_str, offer_prsd, answer_str, answer_prsd;


    pc1 = new webkitRTCPeerConnection(iceServers, optional);
    pc2 = new webkitRTCPeerConnection(iceServers, optional);

    pc1.onicecandidate = function (evt) { //this callback returns an ICE EVENT not a candidate
      if (pc2.remoteDescription !== null && evt.candidate !== null) //want to make sure remote description is set and that the candidate is not null(the last one will be null to indicate it being the last candidate for that signalling period)
        pc2.addIceCandidate(evt.candidate, function () { console.log("added ice", evt.candidate); }, function () { console.log("failed to addice"); }); //handle the callback functions here to be sure ice is set
    };

    pc2.onicecandidate = function (evt) {  //SAME HERE, the last candidate is always NULL
      if (pc1.remoteDescription !== null && evt.candidate !== null)
        pc1.addIceCandidate(evt.candidate, function () { console.log("added ice", evt.candidate); }, function () { console.log("failed to addice"); });
    };

    //PC1 KICKS OFF THE PROCESS WITH AN OFFER!!
    pc1.createOffer(onOfferCreated, onError);

    function onError(err) {
      window.alert(err.message);
    }

    function onOfferCreated(description) {
      offer = description;
      //HERE IS THE OFFER CREATED!!!
      offer_str = JSON.stringify(offer); //stringify and set before calling the call back. Yes, callbacks are asynchronous but you want to be sure and not get strange errors by things being called out of order
      pc1.setLocalDescription(offer, onPc1LocalDescriptionSet, onError);
      //WE SET IT AS "OUR LOCAL DESCRIPTIION
    }

    function onPc1LocalDescriptionSet() {
      offer_prsd = JSON.parse(offer_str);
      //console.log(offer_prsd.sdp);
      var desc = new RTCSessionDescription(offer_prsd); //create a SessionDescription object given the parsed object
      // after this function returns, pc1 will start firing icecandidate events
      pc2.setRemoteDescription(desc, onPc2RemoteDescriptionSet, onError);
    }

    function onPc2RemoteDescriptionSet() {
      pc2.createAnswer(onAnswerCreated, onError);
    }

    function onAnswerCreated(description) {
      answer = description;
      pc2.setLocalDescription(answer, onPc2LocalDescriptionSet, onError);
    }


    function onPc2LocalDescriptionSet() {
      // after this function returns, you'll start getting icecandidate events on pc2
      pc1.setRemoteDescription(answer, onPc1RemoteDescriptionSet, onError);
    }

    function onPc1RemoteDescriptionSet() {
      window.alert('Yay, we finished signaling offers and answers');
    }
Benjamin Trent
  • 7,378
  • 3
  • 31
  • 41
-2

Do you mean Technical Lead/Manager for the WebRTC project at Google, W3C and IETF standard committee member, previously Tech Lead for Google+ Hangouts, Google video chat, and AOL Instant Messenger's Justin Uberti?

  • the code he gave is the example you can find from webrtc.org, whose source code is now hosted in github here.
  • you'd better off using more recent code than a post made in 2013 as most if not all the interfaces have changed since. Example: you are using the prefixed version of the objects, which prevents you from testing cross browser, you might want to leverage adapter.js. Note: the original code needed it.
  • there are more than 80 companies out there that are using this technology today. The technology works. if you cannot make it work, you might want to contact them , or use one of their solution.
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Dr. Alex Gouaillard
  • 2,078
  • 14
  • 13
  • 2
    I'm not sure this qualifies as an answer. Regardless of when this was posted, it still works, just not when I'm trying to convert this string into an object. I don't think Justin would mind my brash nature. He seems pretty even-keel. – user3238414 May 07 '14 at 15:36