WebRTC Peer to Peer Tutorial

This tutorial demonstrates the complete process to establich a WebRTC audio/video connection between two peers. In contrast to lots of other tutorials in the WEB it provides as well a complete signaling server hello world. That essential part of WebRTC is often neglected in tutorials which concentrate on the browser's WebRTC Javascript API and let the reader alone when it comes to the topic signaling. These tutorials frequently demonstrate both peers in the same browser application window. That approach leads to confusion about the data flows and the sequences in which the WebRTC callback handlers are called. This tutorial concentrates on the essential issues only and omits everything which distracts from the basic working principles.

The belonging software project needs less the 100 lines of Javascript code to show all important aspects to get a basic audio/video communication between two browsers running. It may be used as as starting point for more complex projects. The project may be dowloaded from this github repository: https://github.com/medialan/WebRTC_SimpleP2P

Limitations

In favor of simplicity the tutorial and code project do not consider the following aspects of WebRTC applications:

  • Browser dependencies: Despite the fact that the WebRTC standard is now quite stable and mature there are still lots of details which are different in the WebRTC capable browsers. Google Chrome and Opera currently provide the most advanced support for WebRTC. The Javascript APIs are still not completely standard compliant. A major part of a WebRTC implementation handles the workarounds of these browser dependencies. That's why the demonstration code of the project will just run on Chrome or Opera but not on Firefox.

  • NAT traversal: That part of WebRTC covers technologies to connect peers in different intranets behind different routers with each other. For that so-called STUN and TURN servers are needed. The sample code does not consider that point. The peers can just connect with each other if they run in an intranet. It would be quite simple to make the sample "Internet-ready".

  • Real world signaling: Signaling comprises different methods how peers of a WebRTC connection exchange data which is necessary to establish the actual audio/video connection. It may be considered as a public Internet service to exchange addresses and information like video/audio format types which must be known by both peers of a connection in advance before they can establish a direct communication channel. Simplified, signaling may be considered as an Internet phone book service which is known by all peers which want to communicate with each other and where they can lookup information of the other peers they can reach. The sample contains a very simple signaling server. It is able to link exactly two peers with each other to exchange the necessary information before the peers establish an audio/video connection. A real world signaling service is a much more complex thing.

Installation and start

  • Download the git repository from here and install it in a folder on your disk.

  • node.js is used as signaling server. Its installation is very simple on Windows or Linux. That's why it is not explained here. The WEB provides lots of information to this.

  • The node.js signaling server application requires the installation of the two node.js modules express and socket.io. If node.js is already installed you can install these modules by entering the directory where the downloaded git project was installed. Open a console or terminal window and move to the folder where you have the files app.js and package.json of the tutorial project. Use this command to install the necessary node.js modules:

    			npm install
    		
  • Now you can start node.js in the console window with:

    			node app.js
    		
  • Finally you open two browser windows (Google Chrome or Opera) with this url:

    http://ip:7000/peer.html

    ip is the IP-adress of the computer where the node.js server is running. The browsers with the peers may run on the same machine as the node.js or on different machines. Both peers must run in an intranet. Internet connections are not supported to keep the sample as simple as possible.

  • The browsers with the peer applications will now prompt you to permit access to their local USB-cameras. After that you see the video of the local camera in the left video element. Clicking the dial button starts a connection to the other peer and you should see its video in the right video element of the peers.

Communication triangle

WebRTC_SimpleP2P

The image shows the typical base communication triangle of each WebRTC application. In our case we use node.js as signaling server. Both peers of the demo application are connected to the node.js server via socket.io connections. The peers exchange information via the node.js server which is necesary to establish direct WebRTC audio/video connections between them.

The node.js server acts as well as http-Web-server. It provides the Peer.html Web application of the demonstration.

The code - step by step

The application code of the WebRTC_SimpleP2P project is in these files:

  • app.js: This is the code for the node.js signaling server.

  • peer.html: Contains the Javascript code for the WebRTC peer and the html markup of the test application.

peer.html - the WebRTC client code

var SignalingServer = {
    socket  : null,
    Connect : function (cb) {
        this.socket = io.connect("http://" + location.hostname + ":7000");
        this.socket.on('msg', function (msg) { cb(msg);	});
    },
    Send    : function(msg) { this.socket.emit('msg',msg); }
};		

  • Creates an SignalingServer object which handles the socket.io based communication channel between the peer and the node.js server. Both peers are connected to the node.js server. They can send information to each other via the node.js server and their socket.io connections.

  • The connect function creates a socket object and establishes the connection to the node.js server. The cb parameter of the connect function is a callback which is called when the node.js server provides data from the remote peer to this peer. This callback receives the information which is needed by the browser's WebRTC API to establish a peer connection to the remote peer.

  • The Send function transmits a message to the node.js server which forwards it to the remote peer.

SignalingServer.Connect(onSignaling);
var pc = new webkitRTCPeerConnection({ iceServers: [] });
pc.onicecandidate = function (event) {
    if (event.candidate != null) 
	    SignalingServer.Send(JSON.stringify({ 'candidate': event.candidate }));
};
pc.onaddstream = function (event) {
    $("#remvideo").attr("src", window.URL.createObjectURL(event.stream));
};

  • Establishes the signaling connection to the node.js server.

  • Creates a PeerConnection object. RTCPeerConnection is a central part of the WebRTC API of the browser. It handles WebRTC aspects like stream control, encryption, session establishment or NAT traversal. The object hides lots of the complexity of WebRTC for the application programmer. The constructor of RTCPeerConnection gets a list of so-called iceServers which is in our case empty. With the help of these public Internet servers WebRTC is cabable to punch through NAT barriers to establish connections between peers in different intranets. Because we do not provide such a server in the test sample the peers can just establish intranet connections.

  • The RTCPeerConnection object provides the parameter onicecandidate. That parameter is assigned a callback function. RTCPeerConnection tries to find so-called ICE candidates. These are IP-address/port pairs which could be tested by a remote peer to connect directly to this peer. This information must be transmitted to the remote peer which is to get connected. That is one problem which is solved by signaling. Because the peers do not know which IP-adresses/ports could work to get directly connected they must exchange that information via the signaling channel which is in our case the node.js server and the socket.io channel. Each time when RTCPeerConnection finds a promising canditate which the remote peer could try to get direcly connected to this peer it calls the onicecandidate callback. That callback transmits the address information to our node.js signaling server which it forwards to the remote peer.

  • Another important property of the RTCPeerConnection object is the onaddstream callback. The PeerConnection object calls that function when it starts receiving a media stream from the remote peer. The task of that callback is to assign that remote media stream to a video element in the DOM of our peer to get it displayed. For that the src property of the target video element is set to the remote stream with the help of the createObjectURL function.

$(document).ready(function() {                	        	
    navigator.webkitGetUserMedia({ video: true, audio: true }, 
        function (stream) {
            $("#locvideo").attr("src", window.URL.createObjectURL(stream));
            pc.addStream(stream);
        },
        function () { console.log("ERROR"); }
    );
});

After the document is completely loaded we start the function GetUserMedia. This function tries to access the USB-camera. The options video and audio tell it that we want to use both. If that function is called the browser pops up a dialog which must be confirmed to permit access to the camera. That dialog is necessary for security reasons. It can just be avoided when the WebRTC application is hosted on an ssl domain. If the camera could be accessed successfully its audio and video stream is assigned to a video element. Furthermore it is added the our PeerConnection object to get it sent to the remote peer when the direct connection is established.

	  
function SendSDP(data) {
    pc.setLocalDescription(data);
    SignalingServer.Send(JSON.stringify({ 'sdp': data }));
};
        
function Dial() {
    pc.createOffer(SendSDP);
};

  • The audio/video Peer to Peer communication is started with the Dial function which must be called by one of the peers in the demonstration application.

  • Dial calls the createOffer function of PeerConnection. That function investigates the properties of the local media streams of the Web-camera and puts that information into a so-called SDP (Session Description Protocoll) format. After that it calls the callback SendSDP. SendSDP transmits that data to the remote peer via the signaling server. This is the so-called offer. The offer contains information about the media format which this peer supports. It is the base for the two peers to negotiate audio/video formats which are understood on both sides. After receiving the offer the remote peer sends and answer with the SDP information of the media formats it supports.

        
function onSignaling(msg) {			
    var signal = JSON.parse(msg);
    if (signal.sdp) {
        pc.setRemoteDescription(new RTCSessionDescription(signal.sdp));		
        if (signal.sdp.type == "offer") pc.createAnswer(SendSDP);
    } else if (signal.candidate) 
        pc.addIceCandidate(new RTCIceCandidate(signal.candidate));			
};

  • Finally the code provides the callback function of the signaling server which gets called if the remote peer sends either SDP- or ICE-candidate (IP-address/port) information about itself to this peer.

  • The callback checks whether the incoming information is SDP or ICE-candidate information. If it is SDP information it informs the PeerConnection object about the properties of the streams of the remote peer. Then it sends an answer to the remote peer if necessary.

  • If the received information is ICE-candidates then that information is provided to the PeerConnection object. Equipped with that information both peers have now a bunch of trial and error IP-address/port pairs which they investigate to find an optimal route for transmitting the actual media data.

app.js - the signaling server code

I will not discuss the general structure of a node.js application here. There are lots of very good tutorials in the WEB to that. So in the following I provide just explanations which are important for the WebRTC application topic.

var express = require("express");
var io      = require('socket.io');

var app = express();
app.use(express.static(__dirname + "/static/"));
var SocketServer = io.listen(app.listen(7000));

  • First we include the node.js modules which we need for our very basic signaling server. Express provides an http server which is necessary for the socket.io communication. Additionalily the express module provides the file peer.html to the browser.

  • The socket.io node.js module provides bidirectional direct communication channels between a Web browser and the node.js server. If the browser is an HTML5 capable browser - which is a precondition for WebRTC - then the socket.io communication runs via WebSockets.

  • The server listens on port 7000 simultaneously for http requests and for socket.io messages.

var PeerASockIO = null, PeerBSockIO = null;

SocketServer.on('connection', function (socket) {
    if ((PeerASockIO != null) && (PeerBSockIO != null)) return;    
    if (PeerASockIO == null) PeerASockIO = socket; else PeerBSockIO = socket;		
    
    socket.on('disconnect', function () {
        if (socket == PeerASockIO) PeerASockIO = null;
        if (socket == PeerBSockIO) PeerBSockIO = null;
    });
    
    socket.on('msg', function (msg) {
        if ((PeerASockIO == null) || (PeerBSockIO == null)) return;
        
        var remSock = PeerBSockIO;
        if (socket == PeerBSockIO) remSock = PeerASockIO;
        remSock.emit('msg', msg);	
    });
});

This code part respresents the signaling data channel.

  • When a peer connects to the server it allocates one of the two available sockets. The server is limited to two peers for simplicity. If two peers are connected the server denies further connection attempts.

  • The important part is the on-msg socket handler. This function is called by socket.io when it receives a message from one of the peers on its socket. If the other peer is already connected the incoming message is simply forwarded to it. If not then the message is ignored. The information content of the received message is not important for the server. It simply acts as a transparent link between the two peers which exchange the SPD and ICE-candidate information via that link.

Summary

Based on this tutorial it should be quite simple to start experimenting with WebRTC. One of the biggest obstacles for the newbee is signaling and the availability of a signaling server. The sample shows how to implement an own signaling server with just 26 lines of code. The necessary understanding of node.js assumed it should be quite easy to extend that to a server which can link a big number of peers together to establish WebRTC channels between them.