Reputation: 14428
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,
Does it mean that a signaling server is mandatory?
Does WebRTC not have the intelligence to talk directly to the other peer without a signaling server?
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.
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
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
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
Upvotes: 4
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
Reputation: 7566
Upvotes: 9