The first thing of any WebRTC app is creating an RTCPeerConnection
. Creating an RTCPeerConnection
will help us to understand the inner workings of peer connections inside the browser.
WebRTC uses UDP(User Datagram Protocol) as the transport protocol, but most web apps nowadays are using TCP(Transmission Control Protocol).
TCP guarantees that:
1. Any data sent will be marked as received
2. If your data is failed to send it is going to be resent blocking the sending of any more data
3. No data will be duplicated on the other side
UDP does not guarantee:
1. The order of your data
2. The receiving of data on the other side
3. The integrity of your data
Anyway, WebRTC uses UDP because it is faster than TCP and we may afford missing few video frames.
The RTCPeerConnection
is the core object of the WebRTC API. It handles initializing connections, connection to peers and attaching media streams.
You can create it simply:
1 2 3 4 |
var conn = new RTCPeerConnection(configuration); conn.onaddstream = function(stream){ //using stream here }; |
The onaddstream
event is fired when the remote user adds video or audio stream to their peer connection.
Connecting to another browser means finding where the other browser is located on the Web. Your browser needs to get the IP address, the port number and device information from another browser. This means exchanging data about which protocols your device supports. This process is called as signalling and negotiation in WebRTC. It consists of few steps:
1. Create a list of potential candidates(users we can connect to) for a peer connection
2. The user or an app selects a user to make a connection with
3. The signalling layer notifies that user that someone wants to connect to him, and he can accept or decline
4. The first user is notified of the acceptance of the offer to connect
5. If accepted, the first user initializes RTCPeerConenction
with the other user
6. Both users exchange hardware and software information over the signalling level
7. Both users exchange location information over signalling level
8. The connection succeeds or fails
It is just one example. In reality the WebRTC specification does not contain any information of how to exchange data.
To connect to another user we need to know information about his device. This is what SDP(Session Description Protocol) provides us with. The SDP is a string-based data provided by the browser in the key-value format. The SDP is given by the RTCPeerConenction
during the establishing a connection with another user. It may look like this:
1 2 3 4 5 6 7 8 9 10 11 |
v=0 o=Andrew 2890844526 2890844526 IN IP4 10.120.42.3 s= SDP Blog c=IN IP4 10.120.42.3 t=0 0 m=audio 49170 RTP/AVP 0 8 97 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 32 a=rtpmap:31 H261/90000 |
So SDP is just an information card of your device.
Connecting to another user means finding a clear path not just around your own network but the other user’s network as well. Three technologies are used here:
1. STUN(Session Traversal Utilities for NAT)
2. TURN(Traversal Using Relays around NAT)
3. ICE(Interactive Connectivity Establishment)
STUN makes a request to the server, enabled with the STUN protocol. The server identifies the IP address of the client and sends it back. Currently, in Chrome and Firefox default servers are provided directly from the browser vendors.
In some cases firewall might not allow any STUN traffic to the other user. In this case, we need TURN. It works by adding a relay in between the clients that acts as a peer to peer connection on behalf of the client.
ICE is the process that utilizes STUN and TURN. It works by finding a range of addressed available to each user and testing each address in sorted order until it finds a combination that will work for both users. When the browser finds a new candidate, it notifies the client app that it needs to send the ICE candidate through the signalling channel.
We are going to create an app that will get 2 video streams, one coming from the webcam directly and one coming from a WebRTC connection that the browser has made locally.
Create an index.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <style> body { background-color: #3D6DF2; margin-top: 15px; } video { background: black; border: 1px solid gray; } #container { position: relative; display: block; margin: 0 auto; width: 500px; height: 500px; } #we { width: 150px; height: 150px; position: absolute; top: 15px; right: 15px; } #him { width: 500px; height: 500px; } </style> <body> <div id="container"> <video id="we" autoplay></video> <video id="him" autoplay></video> </div> <script src="main.js"></script> </body> </html> |
There are 2 video
elements. we
will be considered the local user. him
will be considered the remote user we are making a connection to.
Our app will be working in this way:
1. Check if the browser supports the WebRTC
2. Get User Media
3. Create peer connection
4. Add ICE handlers
5. Start offer/response
6. Make a connection
Add main.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
//checks if the browser supports WebRTC function hasUserMedia(){ navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; return !!navigator.getUserMedia; } //checks if the browser supports RTCPeerConnection function hasRTCPeerConnection(){ window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; return !!window.RTCPeerConnection; } var ourVideo = document.querySelector('#we'), hisVideo = document.querySelector('#him'), ourConnection, hisConnection; if(hasUserMedia()){ //getting the stream from a webcam navigator.getUserMedia({video: true, audio: true}, function(stream){ //setting local video ourVideo.src = window.URL.createObjectURL(stream); if(hasRTCPeerConnection()){ startPeerConnection(stream); } else { alert("Sorry, your browser does not support WebRTC"); } }, function(error){ alert("Sorry, we failed to capture your camera, please try again"); }); } else { alert("Sorry, your browser does not support WebRTC"); } function startPeerConnection(stream){ //using google ICE servers var configuration = { iceServers: [ {url: "stun:23.21.150.121"}, {url: "stun:stun.1.google.com:19302"} ] }; ourConnection = new webkitRTCPeerConnection(configuration); hisConnection = new webkitRTCPeerConnection(configuration); //setup stream listening ourConnection.addStream(stream); /* * When the user adds a stream to their peer connection, this * notification is sent across the connection. The browser than * calls onaddstream to notify the user that a stream has been added * */ hisConnection.onaddstream = function(e){ hisVideo.src = window.URL.createObjectURL(e.stream); }; //creating the SDP offer ourConnection.createOffer(function(offer){ ourConnection.setLocalDescription(offer); hisConnection.setRemoteDescription(offer); //creating answer from remote to local RTCPeerConnection hisConnection.createAnswer(function(offer){ hisConnection.setLocalDescription(offer); ourConnection.setRemoteDescription(offer); }); }); //setup ice handling ourConnection.onicecandidate = function(event){ if(event.candidate){ hisConnection.addIceCandidate(new RTCIceCandidate(event.candidate)); } }; hisConnection.onicecandidate = function(event){ if(event.candidate){ ourConnection.addIceCandidate(new RTCIceCandidate(event.candidate)); } }; } |
That’s all for today 🙂