Whoami
Whoami

Reputation: 14428

Why is a signaling server needed for WebRTC?

WebRTC is a protocol that defines the transport method for media data between peer-to-peer. Understood. Also it works on top of RTP/UDP. This also understood.

While getting the discussion about signalling server it is mentioned that it is required to do compatibility check/channel initiation... and so on works.

My Question is: having said above,

  1. Does it mean that a signaling server is mandatory?

  2. Does WebRTC not have the intelligence to talk directly to the other peer without a signaling server?

  3. Every article related with WebRTC starts with the statement that "It is between browser to browser communication?", does it mean, WebRTC can not be used between a) Embedded device with camera [Without Browser], b) Browser somewhere else.

  4. Also, what is the gain if WebRTC is used compared to the legacy way of streaming into the browser? [I honestly don't know the legacy way].

I know it is a theoretical question. Though, i see this kind of question probably in different context floats around in the internet. Hope this question gives some architecture-level answers. Thanks.

Upvotes: 54

Views: 25567

Answers (4)

jib
jib

Reputation: 42490

WebRTC doesn't solve discovery (nor should it).

WebRTC knows how to talk directly to another peer without a signaling server, but it doesn't know how to discover another peer. Discovery is an inherent problem, so I'm a bit baffled that people expect WebRTC to solve it for them.

Think about it: How are you going to call me? How are you going to direct your computer to initiate contact with me and not a billion other people? By GPS coordinates? email address? static IP? irc? instant message? facebook? telephone number?

Also, how will I know when you call? Will my computer "ring"? There are hundreds of ways to solve this with regular web technology, so WebRTC would be doing you a disservice if it dictated a specific way. The context of your application will likely inform the best means of contact. Maybe I encounter you in some online forum or virtual room in an online game?

Technically speaking, you don't strictly need a signaling server with WebRTC, as long as you have other means to get an SDP offer (a piece of text) to your peer, and receive the reciprocal SDP answer in return, be it by phone text, IM, irc, email, or carrier pigeon. Try this in Chrome or Firefox: https://jsfiddle.net/nnc13tw2 - click "Offer" (wait up to 20 seconds), send the output to your friend who pastes it into the same field on their end and hits Enter, and have them send back the answer, which you paste in the answer field and hit Enter. You should now be connected, and no connecting server was ever involved.

Why the jsfiddle works: It packages all ICE candidates in the SDP, which can take a few seconds, to give you everything you need in one go.

Some advanced features, like altering the number of video sources mid-call etc. also require signaling, but once a call has been established, an app could use its own data channels for any further signaling needs between the peers.

Here's a copy of the code in the linked JSfiddle: (if you're on Chrome, use the linked fiddle instead, as camera access doesn't seem to work in snippets):

var config = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }]};

var dc, pc = new RTCPeerConnection(config);
pc.onaddstream = e => v2.srcObject = e.stream;
pc.ondatachannel = e => dcInit(dc = e.channel);
v2.onloadedmetadata = e => log("Connected!");

var haveGum = navigator.mediaDevices.getUserMedia({video:true, audio:true})
  .then(stream => pc.addStream(v1.srcObject = stream))
  .catch(failed);

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

function createOffer() {
  button.disabled = true;
  dcInit(dc = pc.createDataChannel("chat"));
  haveGum.then(() => pc.createOffer()).then(d => pc.setLocalDescription(d)).catch(failed);
  pc.onicecandidate = e => {
    if (e.candidate) return;
    offer.value = pc.localDescription.sdp;
    offer.select();
    answer.placeholder = "Paste answer here";
  };
};

offer.onkeypress = e => {
  if (!enterPressed(e) || pc.signalingState != "stable") return;
  button.disabled = offer.disabled = true;
  var desc = new RTCSessionDescription({ type:"offer", sdp:offer.value });
  pc.setRemoteDescription(desc)
    .then(() => pc.createAnswer()).then(d => pc.setLocalDescription(d))
    .catch(failed);
  pc.onicecandidate = e => {
    if (e.candidate) return;
    answer.focus();
    answer.value = pc.localDescription.sdp;
    answer.select();
  };
};

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

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

var enterPressed = e => e.keyCode == 13;
var log = msg => div.innerHTML += "<p>" + msg + "</p>";
var failed = e => log(e);
<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><div id="div"></div>
Chat: <input id="chat"></input><br>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Upvotes: 83

Monday
Monday

Reputation: 165

Actually it is possible, but not usable.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>webrtc</title>
  </head>
  <body>
    <script>
      let channel = null

      const connection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); // ice (stun and turn) are optional

      connection.ondatachannel = (event) => {
        console.log('ondatachannel')
        channel = event.channel
        // channel.onopen = event => console.log('onopen', event);
        // channel.onmessage = event => console.log('onmessage', event);
        channel.onmessage = (event) => alert(event.data)
      }

      connection.onconnectionstatechange = (event) => (document.getElementById('connectionState').innerText = connection.connectionState) // console.log('onconnectionstatechange', connection.connectionState)
      connection.oniceconnectionstatechange = (event) =>
        (document.getElementById('iceConnectionState').innerText = connection.iceConnectionState) // console.log('oniceconnectionstatechange', connection.iceConnectionState)

      async function step_1_initiator_create_offer() {
        channel = connection.createDataChannel('data')
        // channel.onopen = event => console.log('onopen', event)
        // channel.onmessage = event => console.log('onmessage', event)
        channel.onmessage = (event) => alert(event.data)

        connection.onicecandidate = (event) => {
          // console.log('onicecandidate', event)
          if (!event.candidate) {
            document.getElementById('createdOffer').value = JSON.stringify(connection.localDescription)
            document.getElementById('createdOffer').hidden = false
          }
        }

        const offer = await connection.createOffer()
        await connection.setLocalDescription(offer)
      }

      async function step_2_accept_remote_offer() {
        const offer = JSON.parse(document.getElementById('remoteOffer').value)
        await connection.setRemoteDescription(offer)
      }

      async function step_3_create_answer() {
        connection.onicecandidate = (event) => {
          // console.log('onicecandidate', event)
          if (!event.candidate) {
            document.getElementById('createdAnswer').value = JSON.stringify(connection.localDescription)
            document.getElementById('createdAnswer').hidden = false
          }
        }

        const answer = await connection.createAnswer()
        await connection.setLocalDescription(answer)
      }

      async function step_4_accept_answer() {
        const answer = JSON.parse(document.getElementById('remoteAnswer').value)
        await connection.setRemoteDescription(answer)
      }

      async function send_text() {
        const text = document.getElementById('text').value

        channel.send(text)
      }
    </script>

    <table width="100%" border="1">
      <tr>
        <th>#</th>
        <th>initiator</th>
        <th>peer</th>
      </tr>
      <tr>
        <td>step 1</td>
        <td>
          <input type="button" value="create offer" onclick="step_1_initiator_create_offer()" />
          <input id="createdOffer" type="text" hidden />
        </td>
        <td></td>
      </tr>
      <tr>
        <td>step 2</td>
        <td></td>
        <td>
          <input id="remoteOffer" type="text" placeholder="offer from initiator" />
          <input type="button" value="accept offer" onclick="step_2_accept_remote_offer()" />
        </td>
      </tr>
      <tr>
        <td>step 3</td>
        <td></td>
        <td>
          <input type="button" value="create answer" onclick="step_3_create_answer()" />
          <input id="createdAnswer" type="text" hidden />
        </td>
      </tr>
      <tr>
        <td>step 4</td>
        <td>
          <input id="remoteAnswer" type="text" placeholder="answer from peer" />
          <input type="button" value="accept answer" onclick="step_4_accept_answer()" />
        </td>
        <td></td>
      </tr>
    </table>
    <hr />
    <input id="text" type="text" />
    <input type="button" value="send" onclick="send_text()" />
    <hr />
    <table border="1">
      <tr>
        <th colspan="2">connection</th>
      </tr>
      <tr>
        <th>connectionState</th>
        <td id="connectionState">unknown</td>
      </tr>
      <tr>
        <th>iceConnectionState</th>
        <td id="iceConnectionState">unknown</td>
      </tr>
    </table>
  </body>
</html>

Source: https://mac-blog.org.ua/webrtc-one-to-one-without-signaling-server

Demo

Upvotes: 4

deceze
deceze

Reputation: 522510

You need a signalling server in order to be able to establish a connection between two arbitrary peers; it is a simple reality of the internet architecture in use today.

In order to contact another peer on the web, you need to first know its IP address. There's the first problem already. You need to know what the IP address of your peer is. How are you going to get this information from peer A to peer B without the people sitting at these computers calling each other via phone and dictating IP addressees? To do this, each peer discovers its own address first, then sends it to the other peer. This opens two more problems: how does a peer discover what its outwards facing IP address is (which may be significantly different than its own IP), and how does it communicate this to the other peer of yet unknown address?

This is where a signalling server comes in. Both peers have a connection to the signalling server, before they have a connection to each other. So they use the signalling server to relay messages on their behalf until they have negotiated a direct way to talk. It would be possible to negotiate a connection without 3rd party help on local subnets; but this scenario is probably rare enough that I'm not even sure the spec is addressing it.

As for 3): WebRTC can be implemented on any device, it's just a protocol; it's not tied exclusively to browsers.

As for 4): the "legacy" way of streaming anything from one browser to another always involved a relay server in the middle. This server has big CPU and bandwidth requirements and is an expensive bottleneck. WebRTC enables direct P2P connections without middleman except for a lightweight signalling server. Also, there wasn't really an open standard before; most of the time you'd be paying some money to Adobe in one way or another.

Upvotes: 8

Benjamin Trent
Benjamin Trent

Reputation: 7566

  1. Yes, signalling is mandatory so that ICE candidates and the like are exchange so that the peer connection knows who its peer is
  2. No, how would it know its peer without some sort of exchange?
  3. No, it does not mean that. I have done numerous experiments working with raspis, and other native devices that I stream video to a browser page through a WebRTC peer connection.
  4. What are you talking about? You meaning the gain of using WebRTC vs Flash and a Central server? WebRTC is peer to peer and if you couple that with GetUserMedia and Html5, you get rid of the need for flash and a central media server to handle all the media exchanges.

Upvotes: 9

Related Questions