WebRTC enables real-time communication in the browser.
This tutorial explains how to build a simple video and text chat application.
For more information about WebRTC, see Getting started with WebRTC on HTML5 Rocks.
You can submit feedback about the WebRTC APIs at bit.ly/webrtcfeedback.
Basic knowledge:
- HTML, CSS and JavaScript
- git
- Chrome DevTools
Experience of Node.js and socket.io would also be useful.
Installed on your development machine:
- Google Chrome or Firefox.
- Code editor.
- Webcam.
- Git, in order to get the source code.
- The source code.
- Node.js with socket.io and node-static. (Node.js hosting would also be an advantage -- see below for some options.)
The instructions in this workshop assume you are using Mac OS, Linux or Windows. Unless you know what you're doing, it's probably easier not to attempt this workshop from a Chromebook!
It would also be useful to have an Android device with Google Chrome installed in order to try out the examples on mobile.
To run any of the examples provided in the examples
directory of this repo simply cd into the specific step directory you'd like to see locally and run
node server.js
Before you run the server, you'll need to run npm install
.
That directory's demo will then be live here.
Using git, clone the workshop repository onto your development computer. If you haven't used git before, there are several tutorials and reference guides available from the git website.
git clone https://github.com/LXJS/training-webrtc.git
cd training-webrtc
- Create a bare-bones HTML document called
index.html
. - Use node-static to create a simple static file server that listens on port
2014
and serves index.html (and all files in the same folder). - Test it out by visiting http://localhost:2014.
When you're done (or if you get stuck), check your solution against the solution in the examples/step1
folder.
- Add a video element to your page.
- Use
navigator.getUserMedia
to capture the user's webcam. - Test it out locally (see instructions above on running demos).
To make getUserMedia
work across browsers, you need to add the following code:
navigator.getUserMedia = navigator.getUserMedia
|| navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia
|| navigator.msGetUserMedia;
getUserMedia
is called like this:
navigator.getUserMedia(constraints, successCallback, errorCallback);
The constraints argument allows us to specify the media to get, in this case video only:
var constraints = { video: true }
If successful, the success callback is called with the video stream from the webcam. You can then set it as the source of a video tag.
function successCallback (localMediaStream) {
window.stream = localMediaStream; // stream available to console
var video = document.querySelector('video');
video.src = window.URL.createObjectURL(localMediaStream);
video.play();
}
If there was an error (like the user denied access to their webcam), then the error callback is called with an error object.
function errorCallback (err) {
console.error('navigator.getUserMedia error: ', err);
}
- Inspect the stream object from the console.
- Try calling
stream.stop()
. - What does
stream.getVideoTracks()
return? - Look at the constraints object: what happens when you change it to
{ audio: true, video: true }
? - What size is the video element? How can you get the video's natural size from JavaScript? Use the Chrome Dev Tools to check. Use CSS to make the video full width. How would you ensure the video is no higher than the viewport?
- Try adding CSS filters to the video element (more ideas here). For example:
video {
filter: hue-rotate(180deg) saturate(200%);
-moz-filter: hue-rotate(180deg) saturate(200%);
-webkit-filter: hue-rotate(180deg) saturate(200%);
}
- Try changing constraints: see the sample at googlechrome.github.io/webrtc/samples/web/content/getusermedia-resolution/.
RTCPeerConnection
is the WebRTC API for video and audio calling. RTCPeerConnection
instances need to exchange metadata in order to set up and maintain a WebRTC 'call':
- Candidate (network, i.e. IP address and port) information.
- Offer and answer messages providing information about media such as resolution and codecs.
In other words, an exchange of metadata is required before peer-to-peer audio, video or data streaming can take place. This process is called signaling.
In this activity, the 'sender' and 'receiver' RTCPeerConnection objects are on the same page, so signaling is simply a matter of passing objects between methods.
In a real world application, the sender and receiver RTCPeerConnections are not on the same page, and we need a way for them to communicate metadata.
Here is a rough diagram of the flow of methods and events:
Of course, for this code challenge, there is no server. Everything is done locally, within the same page.
Your task is to set up a connection between two RTCPeerConnection
instances on the same
page. Not much use in the real world, but good for understanding how RTCPeerConnection
works!
- Take a look at the munge SDP example. This will give you a concrete idea of the steps involved in a complete "signaling exchange".
- Edit the HTML so there are two video elements and three buttons: Start, Call and Hang Up:
<!-- "muted" will prevent horrible audio feedback :) -->
<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay muted></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
- Use
getUserMedia
andRTCPeerConnection
to capture a video stream and send it to a "remote" RTCPeerConnection. The buttons in the UI should do the following:
- start button: captures webcam video stream
- call button: creates two RTCPeerConnection objects, sets up listeners
- hangup button: closes the peer connections
- Test it out locally (see instructions above on running demos).
-
To make
RTCPeerConnection
work across browsers, you need to add the following code:var RTCPeerConnection = window.mozRTCPeerConnection || window.RTCPeerConnection || window.webkitRTCPeerConnection; var RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription || window.webkitRTCSessionDescription; var RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate || window.webkitRTCIceCandidate;
-
The "offers" and "answers" are in SDP (Session Description Protocol) format. They specify the capabilities and media of each
RTCPeerConnection
. -
The "ice candidates" are messages from the ICE (Interactive Connectivity Establishment Protocol). They are basically IP address and port pairs that the remote peer should attempt to connect to.
- Take a look at chrome://webrtc-internals. (There is a full list of Chrome URLs at chrome://about.)
- Style the page with CSS:
- Put the videos side by side.
- Make the buttons the same width, with bigger text.
- Make sure it works on mobile.
- From the Chrome Dev Tools console, inspect localStream, localPeerConnection and remotePeerConnection.
- Take a look at localPeerConnection.localDescription. What does SDP format look like?
For this step, we'll use RTCDataChannel to send text between two textareas on the same page. Not very useful, except to demonstrate how the API works.
- Create a new document and add the following HTML:
<textarea id="dataChannelSend" disabled></textarea>
<textarea id="dataChannelReceive" disabled></textarea>
<div id="buttons">
<button id="startButton">Start</button>
<button id="sendButton">Send</button>
<button id="closeButton">Stop</button>
</div>
- Set up two RTCPeerConnections, just like you did for step 3.
- Call
createDataChannel(channelName)
on the first peer connection. It will return a data channel. - To get the data channel from the remote peer connection, listen for the
datachannel
event. - Test it out locally (see instructions above on running demos).
Use RTCPeerConnection and RTCDataChannel to enable exchange of text messages.
Most of the code in this section is the same as for the RTCPeerConnection example. Additional code is as follows:
function sendData(){
var data = document.getElementById("dataChannelSend").value;
sendChannel.send(data);
}
...
localPeerConnection = new RTCPeerConnection({ iceServers: [] });
sendChannel = localPeerConnection.createDataChannel('sendDataChannel');
sendChannel.onopen = handleSendChannelStateChange;
sendChannel.onclose = handleSendChannelStateChange;
...
remotePeerConnection = new RTCPeerConnection({ iceServers: [] });
function gotReceiveChannel(event) {
receiveChannel = event.channel;
receiveChannel.onmessage = gotMessage;
}
...
remotePeerConnection.ondatachannel = gotReceiveChannel;
function gotMessage(event) {
document.getElementById("dataChannelReceive").value = event.data;
}
The syntax of RTCDataChannel is deliberately similar to WebSocket, with a send()
method and a message
event.
- Try out RTCDataChannel file sharing with Sharefest. When would RTCDataChannel need to provide reliable delivery of data, and when might performance be more important -- even if that means losing some data?
- Use CSS to improve page layout, and add a placeholder attribute to the dataChannelReceive textarea.
- Test the page on a mobile device.
RTCPeerConnection instances need to exchange metadata (candidates, offers, answers) in order to set up and maintain a WebRTC 'call'. This metadata exchange is required before peer-to-peer audio, video or data streaming can take place. As discussed before, this process is called signaling.
In the examples already completed, the 'sender' and 'receiver' RTCPeerConnection objects are on the same page, so signaling is simply a matter of passing objects between methods.
In a real world application, the sender and receiver RTCPeerConnections are not on the same page, and we need a way for them to communicate metadata.
For this, we use a signaling server: a server that can exchange messages between a WebRTC app (client) running in one browser and a client in another browser. The actual messages are stringified JavaScript objects.
To reiterate: metadata exchange between WebRTC clients (via a signaling server) is required for RTCPeerConnection to do audio, video and data streaming (peer to peer).
In this step we'll build a simple Node.js signaling server, using the socket.io
Node module and JavaScript library for messaging. Experience of Node.js and socket.io will be useful, but not crucial -- the messaging components are very simple. In this example, the server (the Node app) is server.js and the client (the web app) is index.html.
The Node server application in this step has two tasks.
To act as a messaging intermediary:
socket.on('message', function (message) {
log('Got message: ', message);
socket.broadcast.emit('message', message);
});
To manage WebRTC video chat 'rooms':
if (numClients == 0) {
socket.join(room);
socket.emit('created', room);
} else if (numClients == 1) {
io.sockets.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room);
} else { // max two clients
socket.emit('full', room);
}
Our simple WebRTC application will only permit a maximum of two peers to share a room.
- Ensure you have Node, socket.io, node-static installed.
npm install socket.io
npm install node-static
- Write a simple signalling server that allows two clients to exchange signalling messages. For this step, you don't need to use any WebRTC APIs on the client-side.
- To start your server when you're finished, run the following command from a terminal in your application directory:
node server.js
- From your browser, open localhost:2014. Open a new tab page or window in any browser and open localhost:2014 again, then repeat.
- To see what's happening, check the Chrome DevTools console (Command-Option-J, or Ctrl-Shift-J).
- Take a look at the code in the examples/step5 directory if you get stuck.
-
Try deploying your messaging server so you can access it via a public URL. (Free trials and easy deployment options for Node are available on several hosting sites including nodejitsu, heroku and nodester.)
-
What alternative messaging mechanisms are available? (Take a look at apprtc.appspot.com.) What problems might we encounter using 'pure' WebSocket? (Take a look at Arnout Kazemier's presentation, WebSuckets.)
-
What issues might be involved with scaling this application? Can you develop a method for testing thousands or millions of simultaneous room requests.
-
Try out Remy Sharp's tool nodemon. This monitors any changes in your Node.js application and automatically restarts the server when changes are saved.
-
The solution uses a JavaScript prompt to get a room name. Work out a way to get the room name from the URL, for example localhost:2014/foo would give the room name foo.
In this step, we build a video chat client, using the signaling server we created in Step 5 and the RTCPeerConnection code from Step 3.
- Ensure you have Node, socket.io and [node-static](https://github.com/cloudhead/node- static) installed and working. If in doubt, try the code in Step 5.
- Using the code from your step 3 and step 5 solutions as a starting point, write a complete app that allows two clients to do a video/audio chat.
- Run your server with:
node server.js
- From your browser, open localhost:2014. Open a new tab page or window and open localhost:2014 again.
- View logging from the Chrome DevTools console and WebRTC debug information from chrome://webrtc-internals.
This solution users adapter.js. This is a JavaScript shim, maintained by Google, that abstracts away browser differences and spec changes.
-
This application only supports one-to-one video chat. How might you change the design to enable more than one person to share the same video chat room? (Look at talky.io for an example of this in action.)
-
The solution has the room name foo hard coded. What would be the best way to enable other room names?
-
Does the app work on mobile? Try it out on a phone, on a 7" and a 10" tablet. What layout, UI and UX changes would be required to ensure a good mobile experience?
-
Deploy your app at a public URL (see above for hosting options). Try different network configurations, for example with one user on wifi and another on 3G. Any problems?
-
How would users share the room name? Try to build an alternative to sharing room names.
This is a DIY step!
- Take a look at the app you built in step 4.
- Add the RTCDataChannel code to your Step 6 app to create a complete application.
- Consider separating the WebRTC complexity into it's own separate file or "module". This way, the rest of the video chat application doesn't need to understand the WebRTC internals. An example of doing this well is simple-peer.
- The app hasn't had any work done on layout. Make it look pretty!
- Make sure your app works well on different devices.
- Test out Chrome <-> Firefox interop. It should work!
Abstraction libraries such as SimpleWebRTC make it simple to create WebRTC applications.
- Create a new document using the code from examples/step8/index.html.
- Open the document in multiple windows or tab.
- Find a WebRTC library for RTCDataChannel. (Hint: there's one named PeerJS!)
- Set up your own signaling server using the SimpleWebRTC server signalmaster.
This workshop was adapted by @feross and @HCornflower from the excellent webrtc/workshop repo, by the webrtc project. It was updated and expanded for this LXJS workshop!