WebRTC applications using PeerJS
PeerJS is a client-side JavaScript library that provides an easy-to-use API to work with WebRTC. It only provides an API to exchange MediaStream
and arbitrary data between peers. It doesn't provide an API to work with MediaStream
.
PeerServer
PeerServer is an open source signaling server used by PeerJS to establish a peer-to-peer connection. PeerServer is written in Node.js. If you don't want to run your own PeerServer instance, then you can use PeerServer cloud, which hosts PeerServer for public use. PeerServer cloud allows you to establish a maximum of 50 concurrent connections for free.
A unique ID identifies every peer connected to PeerServer. PeerServer itself can generate the ID, or else the peers can provide their own ID. For a peer to establish a peer-to-peer connection with another peer, it just needs to know the other peer's ID.
You might want to run your own PeerServer instance when you want to add more functionality to PeerServer or you want to support more than 50 concurrent connections. For example, if you want to check whether the user is logged in to PeerServer, then you need to add this feature and host your own customized PeerServer.
In this chapter, we will use PeerServer cloud, but in the next chapter, we will create our own instance of PeerServer. Therefore, to continue further with this chapter, create an account on the PeerServer cloud and retrieve the API key. Every application gets an API key to access the PeerServer cloud. If you are hosting your own PeerServer, then you won't need an API key. The API key is used by PeerServer cloud to track the total connections established by an application. To create an account and retrieve an API key, visit http://peerjs.com/peerserver.
PeerJS API
Let's discuss the PeerJS API by creating a simple app that allows the users to exchange video and text messages with any user whose ID they have.
Create a peerjs-demo
directory in your web server and place a file named index.html
in it.
In the index.html
file, we need to first enqueue the PeerJS
library. Download PeerJS
from http://peerjs.com/. At the time of writing, the latest version of PeerJS was 0.3.14. I would recommend that you stick to this version for the following examples. Place this starting code in the index.html
file:
<!doctype html> <html> <head> <title>PeerJS Demo</title> </head> <body> <!-- Place HTML code here --> <script src="peer.min.js"></script> <script> //place JavaScript code here </script> </body> </html>
Here, I enqueued the minified version of PeerJS.
PeerJS API comprises of three main constructors, as follows:
Peer
: An instance ofPeer
represents a peer in the network. A peer is connected to the signaling server and STUN, and optionally, to a TURN.DataConnection
: DataConnection (that is, the instance ofDataConnection
) represents a peer-to-peer connection, which is used to exchange the arbitrary data. Technically, it wrapsRTCDataChannel
.MediaConnection
: MediaConnection (that is, the instance ofMediaConnection
) represents a peer-to-peer connection that is used to exchangeMediaStream
. Technically, it wrapsRTCPeerConnection
.
If a peer wants to establish DataConnection
or MediaConnection
with another peer, then it simply needs to know the other peer's ID. PeerJS doesn't give the other peer an option to accept or reject DataConnection
. Also, in the case of MediaConnection
, PeerJS doesn't give the other peer an option to accept or reject MediaConnection
, but MediaConnection
will be inactive until it is activated programmatically by the other peer so that MediaStream
can be transferred, otherwise MediaStream
will not be transferred. So, we can write our own logic to let the other user accept or reject DataConnection
or MediaConnecton
, that is, as soon as DataConnection
or MediaConnection
is established, we can cancel it by asking the user for their opinion.
Note
At present, one MediaConnection
can transfer only one MediaStream
. In future releases of PeerJS, a single MediaConnection
will support the transfer of multiple MediaStreams.
Now, we need to create a <video>
tag where the video will be displayed, a button to connect to a peer, and also a text box to send message. Here is the HTML code to display all these:
<video id="remoteVideo"></video> <br> <button onclick="connect()">Connect</button> <br> <input type="text" id="message"> <button onclick="send_message()">Send Message</button>
Now as soon as the page loads, we need to connect to PeerServer
and ICE servers so that other peers can talk to us, and also when a user clicks on the connect button, we can establish DataConnection
and MediaConnection
. The following is the code for this:
var peer = null; window.addEventListener("load", function(){ var id = prompt("Please enter an unique name"); peer = new Peer(id, {key: "io3esxy6y43zyqfr"}); peer.on("open", function(id){ alert("Connected to PeerServer successfully with ID: " + id); }); peer.on("error", function(err){ alert("An error occured. Error type: " + err.type); }) peer.on("disconnected", function(){ alert("Disconnected from signaling server. You ID is taken away. Peer-to-peer connections is still intact"); }) peer.on("close", function(){ alert("Connection to signaling server and peer-to-peer connections have been killed. You ID is taken away. You have been destroyed"); }) peer.on("connection", function(dataConnection){ setTimeout(function(){ if(confirm(dataConnection.peer + " wants to send data to you. Do you want to accept?")) { acceptDataConnection(dataConnection); } else { dataConnection.close(); } }, 100) }) peer.on("call", function(mediaConnection){ setTimeout(function(){ if(confirm("Got a call from " + mediaConnection.peer + ". Do you want to pick the call?")) { acceptMediaConnection(mediaConnection); } else { mediaConnection.close(); } }, 100); }) });
Here is how the code works:
- First we displayed a prompt box to take the ID as an input so that every peer can decide their own ID.
- Then we created an instance of
Peer
with ID and PeerServer cloud key. Here we didn't provide signaling and ICE server's URLs, therefore, PeerJS will use PeerServer cloud as the signaling server and Google's public STUN server. It will not use any TURN server. As soon as aPeer
instance is created, the instance connects to the signaling server and registers the given ID. - Then we attached five event handlers to the
peer
object. - The
open
event is triggered when the connection toPeerServer
was successful. - The
error
event is triggered for errors on thepeer
object. - The
disconnected
event is triggered when the connection with the signaling server is disconnected. The connection with the signaling server may get disconnected due to network problem or if you manually call thepeer.disconnect()
method. Once you are disconnected, your ID can be taken by someone else. You can try to reconnect with the same ID using thepeer.reconnect()
method. You can check whetherpeer
is connected to the signaling server using thepeer.disconnect
Boolean property. - The
close
event is triggered whenpeer
is destroyed, that is, it cannot be used anymore, allMediaConnections
andDataConnections
are killed, connection with the signaling server is killed, the ID is taken away, and so on. You may want to manually destroypeer
when you don't need it anymore. You can destroy a peer using thepeer.destroy()
method. - The
connection
event is triggered when some other peer establishesDataConnection
with you. As I said earlier,DataConnection
is established without further permission, but you can close it as soon as it's established if you want. Here we let the user decide if they want to continue or closeDataConnection
established by another peer. The event handler attached to the event receives an instance ofDataConnection
via the parameter that represents the currently establishedDataConnection
. - The
call
event is triggered when some other peer establishesMediaConnection
with you. Here, we also let the user decide if they want to continue or closeMediaConnection
established by another peer. The event handler attached to the event receives an instance ofMediaConnection
via the parameter that represents the currently establishedMediaConnection
. - Here, in the
call
andconnection
event handlers, we asynchronously displayed the confirm popup boxes to prevent blocking the execution of the event handler that causes issues in some browsers, that is, blocking it fails to establishDataConnection
andMediaConnection
.
Now, let's implement the acceptDataConnection()
and acceptMediaConnection()
functions so that we can display the text messages and remote MediaStream
when other peer establishes DataConnection
or MediaConnection
with us. Here's the code:
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; var myDataConnection = null; var myMediaConnection = null; function acceptDataConnection(dataConnection) { myDataConnection = dataConnection; dataConnection.on("data", function(data){ alert("Message from " + dataConnection.peer + ".\n" + data) }) dataConnection.on("close", function(data){ alert("DataConnecion closed"); }) dataConnection.on("error", function(err){ alert("Error occured on DataConnection. Error: " + err); }) } function acceptMediaConnection(mediaConnection) { myMediaConnection = mediaConnection; mediaConnection.on("stream", function(remoteStream){ document.getElementById("remoteVideo").setAttribute("src", URL.createObjectURL(remoteStream)); document.getElementById("remoteVideo").play(); }) mediaConnection.on("close", function(data){ alert("MediaConnecion closed"); }) mediaConnection.on("error", function(err){ alert("Error occured on MediaConnection. Error: " + err); }) navigator.getUserMedia({video: true, audio: true}, function(mediaStream) { mediaConnection.answer(mediaStream); }, function(e){ alert("Error with MediaStream: " + e); }); }
This is how the preceding code works:
- In the
acceptDataConnection()
function, we attached three event handlers toDataConnection
. Thedata
event is triggered when the other peer sends us data. Theclose
event is triggered whenDataConnection
is closed. Finally, theerror
event is triggered when an error occurs onDataConnection
. We can manually closeDataConnection
using thedataConnection.close()
method. - In the
acceptMediaConnection()
function, we attached three event handlers and transferred ourMediaStream
to the other peer. Thestream
event is triggered when other peer sends usMediaStream
. Theclose
event is triggered whenMediaConnection
is closed. Finally, we activatedMediaConnection
using themediaConnection.answer()
method by passing ourMediaStream
. AfterMediaConnection
is activated, thestream
event will be triggered.
We finished writing the code to handle MediaConnection
or DataConnection
established by another peer with us. Now we need to write a code to create MediaConnection
and DataConnection
that a user clicks on the connect button. Here is the code:
function connect() { var id = prompt("Please enter other peer ID"); establishDataConnection(id); establishMediaConnection(id); } function establishDataConnection(id) { var dataConnection = peer.connect(id, {reliable: true, ordered: true}); myDataConnection = dataConnection; dataConnection.on("open", function(){ alert("DataConnecion Established"); }); dataConnection.on("data", function(data){ alert("Message from " + dataConnection.peer + ".\n" + data) }) dataConnection.on("close", function(data){ alert("DataConnecion closed"); }) dataConnection.on("error", function(err){ alert("Error occured on DataConnection. Error: " + err); }) } function establishMediaConnection(id) { var mediaConnection = null; navigator.getUserMedia({video: true, audio: true}, function(mediaStream) { mediaConnection = peer.call(id, mediaStream); myMediaConnection = mediaConnection; mediaConnection.on("stream", function(remoteStream){ document.getElementById("remoteVideo").setAttribute("src", URL.createObjectURL(remoteStream)); document.getElementById("remoteVideo").play(); }) mediaConnection.on("error", function(err){ alert("Error occured on MediaConnection. Error: " + err); }) mediaConnection.on("close", function(data){ alert("MediaConnecion closed"); }) }, function(e){ alert("Error with MediaStream: " + e); }); }
Here is how the code works:
- First we asked the user to input another user's ID.
- Then we established
DataConnection
. To establish aDataConnection
with another user, we need to invoke theconnect()
method of thePeer
instance with other peer's ID. We also madeDataConnection
reliable and ordered. Then, we attached the event handlers. We also saw howdata
,close
, anderror
events work. Theopen
event is triggered whenDataConnection
is established. - After establishing the
DataConnection
, we establishedMediaConnection
. To establishMediaConnection
, we need to call thecall()
method of thePeer
instance. We need to passMediaStream
to thecall()
method. Finally, we attached the event handlers. Thestream
event will be triggered when the other user calls theanswer()
method of theMediaConnection
instance, that is, when the MediaConnection is activated.
Now the last thing we need to do is write the code to send the message when a user clicks on the send message button. Here is the code for this:
function send_message() { var text = document.getElementById("message").value; myDataConnection.send(text); }
To send data via MediaConnection
, we need to call the send()
method of the MediaConnection
instance. Here, we are sending a string, but you can pass any type of data including blobs and objects.
Now, to test the application, open the index.html
page URL in two different browsers, devices, or tabs. I am assuming that you have opened the URL in two different devices. In each device, provide a different ID to identify the user. Then click on the connect button in any one device and enter the other peer's ID. Now accept the request on the other device. Once this is done, both the devices will be able to display each other's webcam video and microphone audio. You can also send messages between them.
Note
You can find the official documentation of PeerJS API at http://peerjs.com/docs/#api.