Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reset the WebRTC state in Chrome/node-webkit, without refreshing the page?

Question:

How does one go about resetting the state of WebRTC components in Chrome -- without reloading the page -- when they're kicked into an invalid state? See below for more details about how I'm replicating this state, and why I'm asking this:

Problem description:

I'm getting the following error in Chrome 35/node-webkit 0.10.0 when trying to set Ice Candidates when making a call:

Failed to execute 'addIceCandidate' on 'RTCPeerConnection': The ICE candidate could not be added.

Now, I know why it's happening. I'm working on making a ROBUST WebRTC application that can handle some normal user abuse. To replicate this state, I basically have to make a couple WebRTC calls and then kill them real fast and then immediately try another call. I'm guessing this must kick the PeerConnection and other components into a different state where it's expecting B to happen, but I'm starting over again with A. This is evidenced by the following error message:

Failed to set session description: Failed to set remote answer sdp: Called in wrong state: STATE_INIT

Now, Most of the WebRTC demos we see on the Internet, like http://apprtc.appspot.com, are stateless, and the browser is refreshed often, which results in resetting the DOM state. So, for those developers, the answer is easy. Just reload the page and call it good.

Currently, I have to reload the application when the DOM enters this state. However, that's not an acceptable solution, since I'm building a single page application, not a website,

I'm wondering if there is a way to make a call to the API to tell it to reset the state of whatever it is that is throwing these errors?

Some troubleshooting steps:

I tried the following, from the JavaScript console in node-webkit (Chrome 35), to see if I can manually reset the state of the PeerConnection, but it isn't helping:

 var properties = {};
 properties.pcConfig = {
     "iceServers": [{
         "url": "stun:stun.l.google.com:19302"
     }]
 };
 properties.pcConstraints = {
    "optional": []
 };

 peerConn = new RTCPeerConnection(properties.pcConfig, properties.pcConstraints);
 peerConn.close();

Here's the output of some of the peerConnection properties:

peerConn.signalingState     -->  "closed"
peerConn.iceConnectionState -->  "closed"
peerConn.iceGatheringState  -->  "complete"
like image 677
jmort253 Avatar asked Aug 19 '14 22:08

jmort253


1 Answers

You are supposed to be able to rollback changes. That can only happen when roles are not changed, i.e. when the caller in the first call is still the caller in the subsequent calls, and might not be appropriate in your case, as the error message you are getting is related to a peer connection receiving an answer before emiting an offer (i.e. a mismatch between caller/callee).

Note that close states are final, and a closed peer connection should be deleted as it cannot be reused.

In your case, deleting the original peer connection and making a new one is a must, but not enough. You would need to reinitiate the handshake, and to make sure that the messages targeted to the original peer connection is not captured and used by other peer connections. Multiparty clients have the same design problem. One way to solve it, and also to solve the glare problem, is to add to the "offer", "answer", "candidate" messages that you exchange offline an "origin" and potentially a "target" field. You must generate IDs yourself, as the peer connection object does not have unique ID by default.

here is the normal dance (with trickle ICE):

  • A is coming online, creating a peer connection and sore it in a map at the index A_0
  • B is coming online, same as above with B_0 (make sure IDs are unique)
  • A is calling B
  • A send an offer, with an additional field "origin":"A_0" and a "target":"B_0"
  • B receive offer, setLocalDescription (starts ICE gathering), send answer with origin:B_0, target:A_0, Done.
  • A receive answer, set local description (starts ICE gathering), Done.
  • ICE candidates are being exchanged with the target and origin field set appropriately.

now the problematic case:

  • A deletes its A_0 peer connection before receiving B's answer.
  • B sends answers and ICE candidates for A_0, that are discarded by A as A_0 does not exist.
  • A restart the dance with A_1

glare case:

  • A and B send simultaneously an offer to each other
  • compare the IDs and chose one according to a reproducible arbitrary rule (the bigger number, the bigger string, use alphabetical order, ….)

Note that this design also allow for multiparty calls, as each call would be isolated by the map, and routed by the messaging logic.

function handleGenericMsg(msg){
  if(  msg.origin  === username 
    ) {
    trace( 'MAIN - [' + username + '] Ignoring self message: ' + msg.type + '.' );
    return;
  }
  switch(msg.type){
    case 'offer':
      offerHandler(msg);
      break;
    default:
      trace( 'MAIN - [' + targetMid + '] Unsupported message received: ' +     JSON.stringify(msg));
      break;
  }
};

function offerHandler( msg ){
  var targetMid = msg.origin;
  offer = new RTCSessionDescription(msg);
  trace( 'PC   - [' + targetMid + '] Received offer.' )
  var pc = peerConnections[ targetMid ];
  if( !pc ) {
    openPeer( targetMid, false );
    pc = peerConnections[ targetMid ];
  } else {
    // we already had a PC, let's reuse it
  }
  pc.setRemoteDescription( offer );    
  doAnswer( targetMid );
};
like image 140
Dr. Alex Gouaillard Avatar answered Sep 30 '22 08:09

Dr. Alex Gouaillard