WebRTC

WebRTC is a standard being put forth by the W3C to create an API to deliver realtime data, audio, and video between browsers in a peer to peer fashion.

PeerJS

Unfortunately, there is a fair amount of complexity involved in sending descriptions of the data, audio and video channels from peer to peer.

Fortunately, a few JavaScript libraries have popped up to aide in this. One, in particular that we'll go over is PeerJS.

Simple Video Conferencing Example

The first step is to do our normal getUserMedia, gaining access to a stream from the user's camera and microphone:

/* Get User Media */
let my_stream = null;

// Constraints - what do we want?
let constraints = { audio: false, video: true }

window.addEventListener('load', function() {
	// Prompt the user for permission, get the stream
	navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
		/* Use the stream */

		// Attach to our video object
		var videoElement = document.getElementById('myvideo');
		videoElement.srcObject = stream;

		// Global for stream
		my_stream = stream;

		// Wait for the stream to load enough to play
		videoElement.onloadedmetadata = function(e) {
			videoElement.play();
		};
	})
	.catch(function(err) {
		/* Handle the error */
		alert(err);
	});
});
		
This requires that a video element be present on the page with the id of "myvideo":
<video id="myvideo" width="320" height="240"></video>
		
Also, it should be run inside an onload event as the video element isn't available until it is rendered.

The next step is to include the PeerJS JavaScript library. (Download it from here: PeerJS GitHub, either get the peer.min.js from the dist directory or download a release and get it from the same sample place.)

<script src="peer.min.js"></script>
		
and to create an instance of a "peer" and connect to the PeerJS server
// We'll use a global variable to hold on to our id from PeerJS
var peer_id = null;

// I setup a peer server on a Digital Ocean instance for our use, you can use that with the following constructor:
var peer = new Peer({host: 'liveweb-new.itp.io', port: 9000, path: '/'});

// Get an ID from the PeerJS server
peer.on('open', function(id) {
  console.log('My peer ID is: ' + id);
  peer_id = id;
});

peer.on('error', function(err) {
  console.log(err);
});
		

This should be enough for us to receive a "call"

peer.on('call', function(incoming_call) {
	console.log("Got a call!");
	incoming_call.answer(my_stream); // Answer the call with our stream from getUserMedia
	incoming_call.on('stream', function(remoteStream) {  // we receive a getUserMedia stream from the remote caller
		// And attach it to a video object
		var ovideoElement = document.getElementById('othervideo');
		ovideoElement.src = window.URL.createObjectURL(remoteStream) || remoteStream;
		ovideoElement.setAttribute("autoplay", "true");
		ovideoElement.play();
	});
});
		

PeerJS runs a server which aides in the transmission of audio/video/data channel information but we'll still need to run a server to introduce the users to each-other. We can use a normal WebSocket server as we have been doing and use it to share the PeerJS ID value between users.

Here is a basic server that relays the peer_id's:

// HTTPS Portion - Required for getUserMedia
var https = require('https');
var fs = require('fs'); // Using the filesystem module
var url =  require('url');

// Include your key and certificate in the same directory
var options = {
  key: fs.readFileSync('my-key.pem'),
  cert: fs.readFileSync('my-cert.pem')
};

function handleIt(req, res) {
	var parsedUrl = url.parse(req.url);

	var path = parsedUrl.pathname;
	if (path == "/") {
		path = "index.html";
	}

	fs.readFile(__dirname + path,

		// Callback function for reading
		function (err, fileContents) {
			// if there is an error
			if (err) {
				res.writeHead(500);
				return res.end('Error loading ' + req.url);
			}
			// Otherwise, send the data, the contents of the file
			res.writeHead(200);
			res.end(fileContents);
  		}
  	);

	// Send a log message to the console
	console.log("Got a request " + req.url);
}

var httpServer = https.createServer(options, handleIt);
httpServer.listen(8080);


// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io').listen(httpServer);

var connectedSockets = [];

// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection',
	// We are given a websocket object in our function
	function (socket) {

		console.log("We have a new client: " + socket.id);

		// Add to the connectedSockets Array
		connectedSockets.push(socket);

		socket.on('peer_id', function(data) {
			console.log("Received: 'peer_id' " + data);

			// We can save this in the socket object if we like
			socket.peer_id = data;
			console.log("Saved: " + socket.peer_id);

			// We can loop through these if we like
			for (var i  = 0; i < connectedSockets.length; i++) {
				console.log("loop: " + i + " " + connectedSockets[i].peer_id);
			}

			// Tell everyone my peer_id
			socket.broadcast.emit('peer_id',data);
		});


		socket.on('disconnect', function() {
			console.log("Client has disconnected");
			var indexToRemove = connectedSockets.indexOf(socket);
			connectedSockets.splice(indexToRemove, 1); // Remove 1 from position, indexToRemove
		});
	}
);
		

To use the server, we'll use our normal Socket.io code on the client side:

/* Socket Server - Socket.io */
var socket = io.connect();

socket.on('connect', function() {
	console.log("Connected");

	// Our socket is connected, send out our peer id here
	console.log("sending out our peer id");
	socket.emit("peer_id",peer_id);
});

// Receive other folks peer ids
socket.on('peer_id', function (data) {
	console.log("Got a new peer: " + data);

	// Call them with our stream
	console.log("Calling peer: " + data);
	var call = peer.call(data, my_stream);

	call.on('stream', function(remoteStream) {
		console.log("Got remote stream");
		var ovideoElement = document.createElement('video');
		ovideoElement.src = window.URL.createObjectURL(remoteStream) || remoteStream;
		ovideoElement.setAttribute("autoplay", "true");
		ovideoElement.play();
	});
});
		

Putting these pieces all together, we get something like this: webrtc_new.zip.
And an example that doesn't use a socket server, instead you have to get the peer id of the person you want to call by some other means: videochat.html.