Wern Ancheta

Adventures in Web Development.

Implementing Audio Calls With PeerJS

| Comments

These past few days I’ve been playing around with WebRTC. For the uninitiated, WebRTC basically means Web Real Time Communication. Things like chat, audio or video calling comes to mind when you say real time. And that is what really WebRTC is. It gives real time super powers for the web. In this tutorial I’ll be showing you how to implement audio calls with PeerJS. PeerJS is a JavaScript library that allows us to easily implement peer to peer communications with WebRTC.

Things We Need

Before we start, go ahead and download the things we’ll need for this tutorial:

  • jQuery – I know right! who still uses jQuery these days? Raise your left foot. Kidding aside, yes we still need jQuery. In this tutorial we’ll only be using it to handle click events. So if you’re confident with your Vanilla JavaScript-Fu then feel free to skip it.

  • PeerJS – In case you missed it earlier, were gonna need PeerJS so that we can easily implement WebRTC.

  • RecordRTC.js – This library mainly provides recording functionalities (e.g taking screenshots and webcam photos, recording audio and video) but it also doubles as a shim provider. We won’t really use the recording functionalities in this tutorial so were only using it to be able to request the use of the microphone in the device.

Overview of the App

Were going to build an app that would allow 2 users to call each other through the web via WebRTC. This app can use the PeerServer Cloud or you can implement your own PeerJS server. As for the outputting the audio coming from the microphones of each peer, we will use HTML5 Audio. So all we have to do is convert the audio stream to a format that HTML5 Audio can understand so that we can have each of the users listen to the audio coming from the other side.

Building the App

Now that have a basic overview of how the app will work, its time to actually build it.

First, link all the things that we’ll need:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <script src="//cdn.peerjs.com/0.3/peer.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script src="//www.WebRTC-Experiment.com/RecordRTC.js"></script>
</head>

Yes, you can also put those script tags right before the closing body tag if performance is your thing.

Next is the HTML that the user will actually see:

1
2
3
<body>
    <button id="start-call">start call</button>
    <audio controls></audio>

Yup! I didn’t miss anything. That’s all we need. A button to start the call to another peer and an HTML5 audio tag to output the audio on the other end.

Now let’s proceed with the JavaScript. First declare a method that will get the query parameters by name.

1
2
3
4
5
6
function getParameterByName(name){
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

The way this app works is by using from and to as query parameters. Where from is the id that you want to give to the peer whose currently using the device and to is the id of the peer on the other side. So we use the method above to easily get those values. To emphasize further, here’s how the URL that we will use to access the app will look like on our side (john):

1
http://mysite.com/call-app.html?from=john&to=jane

And on the other side (jane), it would look like this:

1
http://mysite.com/call-app.html?from=jane&to=john

We’ve basically just interchanged the two peers so we know exactly where the request is coming from and where its going to.

Next we declare the method that will ask a permission to the user for the page to use the microphone. This method takes up 2 parameters, the successCallback and the errorCallback. The successCallback is called when the page has been granted permission to use the microphone. And the errorCallback is called when the user declined.

1
2
3
4
5
6
function getAudio(successCallback, errorCallback){
    navigator.getUserMedia({
        audio: true,
        video: false
    }, successCallback, errorCallback);
}

Next declare the method that will be called when a call is received from a peer. This method has the call object as its parameter. We use this call object to initiate an answer to the call. But first we need to ask permission to the user to use the microphone by calling the getAudio method. Once we get the permission, we can then answer the call by calling the answer method in the call object. This method takes up the MediaStream as its argument. If we didn’t get the permission to use the microphone, we just log that an error occurred and then output the actual error. Finally, we listen to the stream event in the call and then we call the onReceiveStream method when the event happens. This stream event can be triggered in 2 ways. First is when a peer initiates a call to another peer. And the second is when the other peer actually answers the call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function onReceiveCall(call){

    console.log('peer is calling...');
    console.log(call);

    getAudio(
        function(MediaStream){
            call.answer(MediaStream);
            console.log('answering call started...');
        },
        function(err){
            console.log('an error occured while getting the audio');
            console.log(err);
        }
    );

    call.on('stream', onReceiveStream);
}

Next is the onReceiveStream method. This method is called when a media stream is received from the other peer. This is where we convert the media stream to a URL which we use as the source for the audio tag. The stream is basically an object which contains the current audio data. And we convert it to a URL by using the window.URL.createObjectURL method. Once all the meta data is loaded, we then play the audio.

1
2
3
4
5
6
7
8
function onReceiveStream(stream){
    var audio = document.querySelector('audio');
    audio.src = window.URL.createObjectURL(stream);
    audio.onloadedmetadata = function(e){
        console.log('now playing the audio');
        audio.play();
    }
}

Now that were done with all the method declarations, its time to actually call them. First we need to know where the request is coming from and who will it be sent to.

1
2
var from = getParameterByName('from');
var to = getParameterByName('to');

Next we declare a new peer. This takes up the id of the peer as its first argument and the second argument is an object containing the PeerJS key. If you do not have a key yet, you can register for the PeerJS Cloud Service. Its free for up to 50 concurrent connections. After that, we also need to set the ice server config. This ensures that we can get the peers to connect to each other without having to worry about external IP’s assigned by routers, firewalls, proxies and other kinds of network security which can get in the way. You need to have at least one stun server and one turn server configuration added. You can get a list of freely available stun and turn servers here.

1
2
3
4
5
6
7
8
9
10
var peer = new Peer(
    from,
    {
        key: 'Your PeerJS API Key',
        config: {'iceServers': [
            { url: 'stun:stun1.l.google.com:19302' },
            { url: 'turn:numb.viagenie.ca', credential: 'muazkh', username: 'webrtc@live.com' }
        ]}
    }
);

If you want to use your own server and get through the 50 concurrent connections limit of the PeerServer cloud. You can install PeerJS Server on your existing Express app in node.

1
npm install peer --save

And then use it like so:

1
2
3
4
5
6
7
8
9
10
11
var express = require('express');
var express_peer_server = require('peer').ExpressPeerServer;
var peer_options = {
    debug: true
};

var app = express();

var server = app.listen(3000);

app.use('/peerjs', express_peer_server(server, peer_options));

And from the client side you can now use your shiny new PeerJS server:

1
2
3
4
5
6
7
8
var peer = new Peer(from, {
        host: 'your-peerjs-server.com', port: 3000, path: '/peerjs',
        config: {'iceServers': [
            { url: 'stun:stun1.l.google.com:19302' },
            { url: 'turn:numb.viagenie.ca', credential: 'muazkh', username: 'webrtc@live.com' }
        ]}
    }
);

Next is an optional code. We only use it to determine if the peer we created was actually created. Here we simply listen to the open event on the peer object. And once it happens, we just output the peer id.

1
2
3
peer.on('open', function(id){
    console.log('My peer ID is: ' + id);
});

Next we listen to the call event. This is triggered when a peer tries to make call to the current user.

1
peer.on('call', onReceiveCall);

Finally, here’s the code we use when we initiate the call ourselves:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$('#start-call').click(function(){

    console.log('starting call...');

    getAudio(
        function(MediaStream){

            console.log('now calling ' + to);
            var call = peer.call(to, MediaStream);
            call.on('stream', onReceiveStream);
        },
        function(err){
            console.log('an error occured while getting the audio');
            console.log(err);
        }
    );

});

What this does is listen to the click event on the start-call button. It then calls the getAudio method to ask the user for permission to use the microphone. If the user allows then the call is made to the peer using the call method. This takes up the id of the peer on the other side and the MediaStream. Next, we just listen for the stream event and then call the onReceiveStream method if it happens. Note that this stream would be the audio stream from the peer on the other side and not the audio stream of the current user. Otherwise we would also hear our own voice. The same is true with the stream that were getting from the onReceiveCall method.

Conclusion

That’s it! In this tutorial we’ve learned how to implement audio calls with WebRTC and PeerJS. Be sure to check out the resources below if you want to learn more.

Resources

Comments