Lewis
Lewis

Reputation: 14926

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?

Upvotes: 6

Views: 671

Answers (1)

jib
jib

Reputation: 42520

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>

Upvotes: 6

Related Questions