47

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.

Whoami
  • 13,930
  • 19
  • 84
  • 140

5 Answers5

77

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>
Unknown
  • 97
  • 1
  • 2
  • 8
jib
  • 40,579
  • 17
  • 100
  • 158
  • Just to be clear. WebRTC sends data such as codec, ip address, ports, etc through signaling. But to find right address to connect to you could need ICE/TURN server's help. So all initial interaction via TURN server would also happen during this signalling handshake. There is no second communication via TURN server once signaling is finished. both users if can will have information on formats, connection, security, etc after signalling which will include right path to each other that could have been gotten via TURN server – Muhammad Umer Sep 13 '17 at 07:49
  • It's not as if signalling happens, then turn server is contacted for network information. because within SDP there is information on ip address, ports and so on. so it'd make not much sense – Muhammad Umer Sep 13 '17 at 07:50
  • @MuhammadUmer The question of signaling channel seems orthogonal to the use of TURN. Offers and answers will include *all* ICE candidates if allowed sufficient time (trickle signaling is only an optimization), like I show here, including all relay candidates as well, if the client's configuration happens to mention TURN servers. So no special accommodation should be needed for TURN here. – jib Sep 14 '17 at 13:50
  • the fiddle is not working anymore can u fix the issue !! – Atul Mathew Jul 25 '19 at 06:43
  • @marcelo Still works for me. I just tested Chrome<->Chrome, Firefox<->Firefox and Chrome<->Firefox. What browser? Any error messages? How quickly are you doing it? Maybe there's a timeout? – jib Jul 25 '19 at 13:22
  • @jib sorry my bad I tested it in the wrong way it is working perfectly!! I do have one more question about how can I utilize this method for a two pc that connected in the same LAN network without internet – Atul Mathew Jul 26 '19 at 03:54
  • @marcelo The [simplest way](https://stackoverflow.com/a/33559801/918910) is probably to install a web socket server somewhere on your LAN, even on one of the peers. – jib Jul 26 '19 at 14:29
  • @jib thank you for the great example. Can I have my carrier pigeon take the SDP from each client and save it in a traditional database to have it accessible by other clients? Are SDPs generated by each client independent of the other client's SDP or do they take the other SDPs into account when generating their own? – Mohammad Apr 08 '20 at 03:57
  • 1
    @Mohammad No, because these particular offers and answers contain ICE candidates w/port numbers allocated specifically for the media and the target it is to be sent to, and thus can't be reused. See [this answer](https://stackoverflow.com/a/36903829/918910). Browsers also generally time out resources held for them, so these offers and answers aren't good forever. – jib Apr 08 '20 at 16:11
  • I am new to this. I am trying to understand why can't the browser send the data directly to another browser using HTTP. Why is there a need for signalling server ? – Newbie Dec 20 '20 at 14:46
  • @Newbie Do you own a [static IP](https://www.lifewire.com/what-is-a-static-ip-address-2626012)? If not, then we have to contend with [firewalls](https://stackoverflow.com/a/39841681/918910). And even if you did own a static IP, I wouldn't know it. We haven't met, except here. So stackoverflow solved discovery, not WebRTC. – jib Dec 22 '20 at 18:04
  • That's awesome, thanks for sharing the JSFiddle! I was getting an error saying something like "invalid sdp line". According to https://stackoverflow.com/questions/59413830/android-kotlin-webrtc-failed-to-parse-reason-invalid-sdp-line, this is due to the "offer" not ending in "\r\n". Here's a fork with a one line change to ensure the "offer" ends in "\r\n" https://jsfiddle.net/67uzmfax/1/ – mowwwalker May 21 '22 at 03:38
  • The first half of the answer has too much verbiage. Could easily reduce that to a sentence or two without any information loss. – Unknown Apr 20 '23 at 15:48
9
  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.
Benjamin Trent
  • 7,378
  • 3
  • 31
  • 41
  • 1
    Thanks Benjamin, Particularly on the 3rd Point, Which WebRTC stack Implementation can be used? If i consider the embedded ARM board running linux and have a camera, can signalling server also be part of the same embedded device? – Whoami Mar 13 '15 at 13:25
  • It COULD be. A direct connection would have to be made but I have had a gateway, signalling server, webpage and a media stream all coming from the same device before and it worked great. Any of those elements could be moved to a separate device given that the network architecture supports it. – Benjamin Trent Mar 13 '15 at 13:27
  • I want to try that . please easy go on me. Which gateway i can use?. if you have some links to share, that would be great help. – Whoami Mar 13 '15 at 13:32
  • 1
    For embedded devices you should take a look at Janus: http://janus.conf.meetecho.com/ Also, you should read more about how WebRTC works and its ecosystem. This is a good starting point: https://webrtchacks.com/ – sdude Mar 13 '15 at 13:35
  • The Janus-gateway is the one I used. – Benjamin Trent Mar 13 '15 at 14:27
7

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.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    Not really. If you have a user's IP, that's all you need to connect to them. With WebRTC however you need more than their IP address, and this extra info is provided by the signaling server (challenge/offer). – Luca Matteis Jul 24 '15 at 09:34
  • The signalling server is exclusively a message relay, it does not "offer" or "challenge" anything. ICE candidates and SDP offers are not generated by the signalling server, the peers themselves create those, possibly with help from a STUN/TURN server. – I'll agree that a signalling server is not necessary **if you have an alternative way to exchange messages directly**; but if you already have a direct connection, then why are you trying to establish a direct connection? – deceze Jul 24 '15 at 10:26
  • how are you going to connect to a peer behind a NAT? – EralpB Jan 30 '19 at 21:17
2

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

Monday
  • 41
  • 5
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 17 '21 at 09:15
  • Agree it's not clear, but the linked demo is, and works well. – havlock Aug 10 '22 at 06:51
-1

Most of the answer has been covered, just thought I'd add a little something. When Google first created webRTC and open sourced it 4 years ago, it did so strictly on it's own without any signaling capabilities.

However, recently Google purchased Firebase, so I would wager that soon they will be open sourcing a complete end-to-end solution for WebRTC so all of us can have an even easier time implementing it.

Speaking of Firebase, I tried it out and it's not bad, got the basic job done: http://antonvolt.com/prototype2/

AntonV
  • 9
  • 3