Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RTCDataChannel for signaling?

I've been reading this article for a signaling solution. The author mentions about signaling with RTCDataChannel when connections are established.

Using RTCDataChannel for signaling

A signaling service is required to initiate a WebRTC session.

However, once a connection has been established between two peers, RTCDataChannel could, in theory, take over as the signaling channel. This might reduce latency for signaling — since messages fly direct — and help reduce signaling server bandwidth and processing costs. We don't have a demo, but watch this space!

Why is signaling needed since connections are already established?

like image 356
Lewis Avatar asked May 03 '15 16:05

Lewis


People also ask

What is RTCDataChannel?

The RTCDataChannel interface is a feature of the WebRTC API which lets you open a channel between two peers over which you may send and receive arbitrary data. The API is intentionally similar to the WebSocket API, so that the same programming model can be used for each.

What is data channel in WebRTC?

What is a data channel? A WebRTC data channel lets you send text or binary data over an active connection to a peer. In the context of a game, this lets players send data to each other, whether text chat or game status information.

How do I create a WebRTC connection?

To establish a WebRTC connection, one peer (the caller) has to call CreateOffer() , but not both (the callee just waits). Since the signaler implementation NamedPipeSignaler already provides a way to distinguish between the two peers, we use that information to select which peer will automatically initiate the call.


1 Answers

Each side initially declares which audio and/or video tracks it is going to send, so that the right number of ports can be opened, and resolutions and formats that work for both peers can be determined. A signaling channel is needed to send the resulting SDP offer/answer, as well as trickle ICE candidates for each of the ports, to the other side.

Once connected, if you leave this setup alone - basically never add tracks to the connection, remove any, or alter track attributes significantly - then you wont need the signaling server again.

If you do change any of those things however, then a re-negotiation is needed, which is just what it sounds like: another round over the signaling channel much like the first one.

Reasons to add a track may be a second camera, another video-source (from another participant perhaps), or maybe screen-sharing, something like that.

The article is correct that a data channel may be used. Here's a demo! (Firefox only for now.)

The article is wrong about a signaling service being required - provided you have another means of discovery - as this demo lamely proves.

The initial connection is chat-only, but either side can push to add video to the mix. The re-negotiation for this is done over a data channel (since there's no signaling server!)

Instructions for using the fiddle:

  1. There is no server (since it's a fiddle), so press the Offer button and copy the offer.
  2. Paste the offer to the same spot in the same fiddle in another tab or on another machine.
  3. Press ENTER, then copy the answer you get and paste it back in the first fiddle.
  4. Press ENTER again (not addTrack yet!)
  5. You are now connected with two data-channels: one for chat and another for signaling.
  6. Now press addTrack on either end and video should show up on the other end.
  7. Press addTrack in the other direction, and you should have video going both ways.

var dc = null, sc = null, pc = new mozRTCPeerConnection(), live = false;
pc.onaddstream = e => v2.mozSrcObject = e.stream;
pc.ondatachannel = e => dc? scInit(sc = e.channel) : dcInit(dc = e.channel);
v2.onloadedmetadata = e => { log("Face time!"); };

function addTrack() {
  navigator.mediaDevices.getUserMedia({video:true, audio:true})
  .then(stream => pc.addStream(v1.mozSrcObject = stream));
}

pc.onnegotiationneeded = e => {
  pc.createOffer().then(d => pc.setLocalDescription(d)).then(() => {
    if (live) sc.send(JSON.stringify({ "sdp": pc.localDescription }));
  }).catch(failed);
};

function scInit() {
  sc.onmessage = e => {
    var msg = JSON.parse(e.data);
    if (msg.sdp) {
      var desc = new mozRTCSessionDescription(JSON.parse(e.data).sdp);
      if (desc.type == "offer") {
        pc.setRemoteDescription(desc).then(() => pc.createAnswer())
        .then(answer => pc.setLocalDescription(answer)).then(() => {
          sc.send(JSON.stringify({ "sdp": pc.localDescription }));
        }).catch(failed);
      } else {
        pc.setRemoteDescription(desc).catch(failed);
      }
    } else if (msg.candidate) {
      pc.addIceCandidate(new mozRTCIceCandidate(msg.candidate)).catch(failed);
    }
  };
}

function dcInit() {
  dc.onopen = () => { live = true; log("Chat!"); };
  dc.onmessage = e => log(e.data);
}

function createOffer() {
  button.disabled = true;
  dcInit(dc = pc.createDataChannel("chat"));
  scInit(sc = pc.createDataChannel("signaling"));
  pc.createOffer().then(d => pc.setLocalDescription(d)).catch(failed);
  pc.onicecandidate = e => {
    if (e.candidate) return;
    if (!live) {
      offer.value = pc.localDescription.sdp;
      offer.select();
      answer.placeholder = "Paste answer here";
    } else {
      sc.send(JSON.stringify({ "candidate": e.candidate }));
    }
  };
};

offer.onkeypress = e => {
  if (e.keyCode != 13 || pc.signalingState != "stable") return;
  button.disabled = offer.disabled = true;
  var obj = { type:"offer", sdp:offer.value };
  pc.setRemoteDescription(new mozRTCSessionDescription(obj))
  .then(() => pc.createAnswer()).then(d => pc.setLocalDescription(d))
  .catch(failed);
  pc.onicecandidate = e => {
    if (e.candidate) return;
    if (!live) {
      answer.focus();
      answer.value = pc.localDescription.sdp;
      answer.select();
    } else {
      sc.send(JSON.stringify({ "candidate": e.candidate }));
    }
  };
};

answer.onkeypress = e => {
  if (e.keyCode != 13 || pc.signalingState != "have-local-offer") return;
  answer.disabled = true;
  var obj = { type:"answer", sdp:answer.value };
  pc.setRemoteDescription(new mozRTCSessionDescription(obj)).catch(failed);
};

chat.onkeypress = e => {
  if (e.keyCode != 13) return;
  dc.send(chat.value);
  log(chat.value);
  chat.value = "";
};

var log = msg => div.innerHTML += "<p>" + msg + "</p>";
var failed = e => log(e.name + ": " + e.message + " line " + e.lineNumber);
<video id="v1" height="120" width="160" autoplay muted></video>
<video id="v2" height="120" width="160" autoplay></video><br>
<button id="button" onclick="createOffer()">Offer:</button>
<textarea id="offer" placeholder="Paste offer here"></textarea><br>
Answer: <textarea id="answer"></textarea><br>
<button id="button" onclick="addTrack()">AddTrack</button>
<div id="div"></div><br>
Chat: <input id="chat"></input><br>
like image 55
jib Avatar answered Oct 14 '22 07:10

jib