Guide A - Embed a WebRTC Button

Building custom applications with WebRTC and OnSIP is easy. To illustrate this, let’s build a WebRTC button from scratch. This small widget will invite a preconfigured destination to join an audio or video session. We will also add audio controls, so you can adjust the volume and mute the audio during the session. To add video, you’ll need to add a video element, which we will cover in the next guide (Guide B - Build a Webphone App).

If you prefer to see the final result before starting, you can view the live demo here. The entire application is also available in the OnSIP tutorials repo on GitHub. Here, we will be walking through it step by step.

OnSIP maintains a WebRTC button app called OnSIP InstaCall, which is free with an OnSIP account. Check it out!

Before we get started, there are a few tools you will need. Since we are building a WebRTC application, you will need a modern version of a browser with WebRTC support and be working on a device with a microphone. Since WebRTC APIs are blocked for security reasons when run from a local file, you will also need a web server to access your application over HTTP or HTTPS.

Application Structure

Our WebRTC button app will be very simple. It will contain a single HTML page, index.html, which loads in all of its behavior from a JavaScript file main.js. In a clean working directory, let’s initialize those files.

In index.html, we have a little bit of boilerplate code and a <script> element for loading in our JavaScript file:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My App</title>
    <link rel="stylesheet" href="onsip.css">
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

At the moment, main.js will be empty. Go ahead and create it to make sure there are no errors loading it.

In the snippet above, we included onsip.css. This is optional and will style the tutorials each step of the way. You can download onsip.css and save it to your working directory.

An Interface to Fill In

To get a picture of where we are going, let’s start with the UI for our WebRTC button. We will have a button that initiates an Invite and a button to terminate the Session, as well a slider to raise and lower volume and another button to mute or unmute your microphone. Lastly, we add an <audio> media element which will play the remote party’s audio stream to us.

In index.html, we add the new HTML to the <body>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
  <div id="user-agent">
    <button id="invite-button">Invite Session</button>
    <audio id="remote-media"></audio>
    
    <button id="terminate-button">Terminate</button>

    <div id="volume-bar">
      <input type="range" id="volume-range" min="0" max="100" value="50"/>
      <button id="mute-button" class="off">Mute</button>
    </div>
  </div>

  <script src="main.js"></script>
</body>

Include SIP.js

Our interface is primed and ready. It is time to start making things happen! To handle the heavy lifting of the WebRTC APIs and to connect to OnSIP, we use SIP.js - an open source JavaScript library created by OnSIP. You can download the latest minified version of SIP.js from the Download page. Save it in your working directory alongside index.html, then make sure index.html loads it:

1
2
3
4
5
<body>
  ...
  <script src="sip-0.6.4.min.js"></script>
  <script src="main.js"></script>
</body>

That’s it on the HTML side. Next stop, JavaScript!

Adding WebRTC

We have the beginnings of our WebRTC button app in place, but a blank interface with an unused library is not going to wow users any time soon. It’s time to start creating sessions!

Connecting to OnSIP

In SIP, User Agents are used to send and receive messages over a SIP network. Creating a User Agent with SIP.js is as simple as new SIP.UA(). This will create an anonymous user agent and automatically connect it to the OnSIP platform. Anonymous user agents have no credentials and do not have to be configured in advance. This makes them useful for quickly sending messages to other OnSIP endpoints. Their anonymity, however, prevents them from receiving incoming session invitations.

In main.js, let’s create a new UA. We wrap it in a namespace called MyApp that will keep all of our code in a manageable container:

1
2
3
4
5
6
7
8
9
10
11
12
/* Definition of the MyApp namespace */
function MyApp() {}

MyApp.prototype = {
  createUA: function () {
    this.ua = new SIP.UA();
  },
};

/* Initialize the app by creating a new MyApp */
var myApp = new MyApp();
myApp.createUA();

Loading the application in your browser, you should see SIP.js logging messages in the JavaScript console as it initializes and connects the UA.

Sending Your First Invite

Now that we are connected to OnSIP, let’s call something! First, we will need a SIP address to call. A SIP address looks like an email address (user@domain), but instead of sending text compositions, you use a SIP address to send voice, video, and other real time media. Pretty cool, huh?

If you are an existing OnSIP Customer with an Attendant Menu, you can use the Admin Portal to find the SIP address of your Attendant Menu.

If you are not yet an OnSIP Customer, or if you do not have an Attendant Menu, you can use the OnSIP Welcome menu: welcome@onsip.com.

When we send an INVITE request, SIP.js creates a Session and returns it for us. We can save that session away for later use and define callbacks that run when status events occur on the session. We will define a method setSession to handle this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyApp.prototype = {
  createUA: function () {...},

  sendInvite: function () {
    // Replace `welcome@onsip.com` with your own SIP address
    var session = this.ua.invite('welcome@onsip.com', this.remoteMedia);

    // Save the session so we can listen for status events.
    this.setSession(session);

    // Prevent creating multiple calls.
    this.inviteButton.disabled = true;
  },
};

Once we have created a session, there are several events that we will want to listen for. SIP allows for progress updates to be sent while the session is being set up to indicate, for example, that the remote party’s phone has begun ringing. We also want to know if the other party accepts the invitation or if the session is rejected and fails. If the invitation is accepted, it will eventually end when you or the other end says bye (via a SIP request), so let’s listen for that as well.

For now, our event callbacks will simply update the UI to indicate the current state of the call. This will let us watch as the invited session progresses through to acceptance (or rejection).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
MyApp.prototype = {
  createUA: function () {...},
  sendInvite: function () {...},

  /* Initialize event listeners and save the session for later access. */
  setSession: function (session) {
    session.on('progress', function () {
      this.setStatus('Ringing...', true);
    }.bind(this));

    session.on('accepted', function () {
      this.setStatus('Connected!', true);
    }.bind(this));

    session.on('failed', function () {
      this.setStatus('Call failed. Try again?', false);
      if (session === this.session) {
        delete this.session;
      }
    }.bind(this));

    session.on('bye', function () {
      this.setStatus('Bye! Invite Another?', false);
      if (session === this.session) {
        delete this.session;
      }
    }.bind(this));

    this.session = session;
  },

  /* Update the UI, enabling or disabling new Invites as necessary. */
  setStatus: function (status, disable) {
    this.inviteButton.innerHTML = status;
    this.inviteButton.disabled = disable;
  },
};

We should also provide a way to end, or terminate a session:

1
2
3
4
5
6
7
8
9
10
11
12
13
MyApp.prototype = {
  createUA: function () {...},
  sendInvite: function () {...},
  setSession: function () {...},
  setStatus: function () {...},

  terminateSession: function () {
    if (!this.session) { return; }

    this.session.terminate();
  },

};

Now that our methods are all set up, we just need to wire it up with our UI. We do this in the MyApp constructor:

1
2
3
4
5
6
7
8
9
function MyApp() {
  this.remoteMedia = document.getElementById('remote-media');

  this.inviteButton = document.getElementById('invite-button');
  this.inviteButton.addEventListener('click', this.sendInvite.bind(this), false);
  
  this.terminateButton = document.getElementById('terminate-button');
  this.terminateButton.addEventListener('click', this.terminateSession.bind(this), false);
}

Go ahead and load the page. When you click the Invite button now, the Attendant Menu will begin playing its recording!

Interacting with OnSIP Applications

Hearing the menu announcement is cool, but menus are not very useful unless we can interact with them. There are two simple things missing from our application to let us do this: DTMF input and Refer handling.

DTMF is a common way of interacting with telephony applications, normally using a dialpad. Standing for Dual-Tone Multi-Frequency, it originally provided a way for analog phones to send signals to automated systems, right in the audio channel. Nowadays, DTMF is all digital. SIP.js provides an API method to send DTMF “tones” using SIP INFO requests.

In our demo application, we do not have a full dialpad. Instead, we will use key presses to indicate the letters, numbers, or symbols to send.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function MyApp() {
  // ...

  document.addEventListener('keydown', function (e) {
    this.sendDTMF(String.fromCharCode(e.keyCode));
  }.bind(this), false);
}

MyApp.prototype = {
  createUA: function () {...},
  sendInvite: function () {...},
  setSession: function () {...},
  setStatus: function () {...},
  terminateSession: function () {...},

  sendDTMF: function (tone) {
    if (this.session) {
      this.session.dtmf(tone);
    }
  },
};

By sending DTMF, we can navigate the options of an OnSIP Attendant Menu, Dial-By-Name Directory, or other application. Eventually, however, you will likely select an option to leave the menu and reach a new destination. This is done by a refer mechanism.

When referring you to another destination, the menu will send SIP.js a SIP request asking us to refer to a different address. That is, it tells us where to find the endpoint we selected using DTMF. To follow a REFER with SIP.js, we just listen for it in setSession along with our other events. SIP.js even provides a default handler that will follow the Refer to the destination.

1
2
3
4
5
/* In setSession */
session.on('refer', session.followRefer(function (req, newSession) {
  this.setStatus('Refer!', true);
  this.setSession(newSession);
}.bind(this)));

Now you should be able to interact with the menu. Have at it!

Audio Control with Volume and Mute

You can now invite and interact with any OnSIP endpoint. The final step in this guide is to provide a few basic audio controls. Let’s add volume sliders that can raise and lower the volume of the call to a comfortable level. After, we will add a mute button to instantly stop sending audio. This can be useful when used with conference suites, where loud background noise threatens to drown out other participants.

Just like our other UI elements, the methods controlling the behavior will be defined in the MyApp prototype object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MyApp.prototype = {
  createUA: function () {...},
  sendInvite: function () {...},
  setSession: function () {...},
  setStatus: function () {...},
  terminateSession: function () {...},
  sendDTMF: function (tone) {...},

  setVolume: function () {
    this.remoteMedia.volume = (parseInt(this.volumeRange.value, 10) || 0) / 100;
  },

  toggleMute: function () {
    if (!this.session) { return; }

    // Mute and unmute are toggled based on the button's CSS class.
    if (this.muteButton.classList.contains('on')) {
      this.session.unmute();
      this.muteButton.classList.remove('on');
    } else {
      this.session.mute();
      this.muteButton.classList.add('on');
    }
  },
};

We again just hook up the inputs to call the methods when clicked. Let’s pick a moderate initial volume, as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
function MyApp() {
  // Set the remote media volume to 50%
  this.remoteMedia = document.getElementById('remote-media');
  this.remoteMedia.volume = 0.5;

  // ...

  this.volumeRange = document.getElementById('volume-range');
  this.volumeRange.addEventListener('change', this.setVolume.bind(this), false);

  this.muteButton = document.getElementById('mute-button');
  this.muteButton.addEventListener('click', this.toggleMute.bind(this), false);
}

Go ahead and make another call to your Attendant Menu. While the recording is playing, you should be able to adjust the volume louder and softer. You can also try toggling the mute option after being referred to a live person.

All Done!

Congratulations! You have successfully built your own WebRTC button application! To add more features like video to your new app and create a fully operational phone, continue on to Guide B - Build A Webphone App.

Or, you can take off from this launching point and fully customize your WebRTC button application to suit your needs. Check out some of the customization options we’ve come up with for inspiration!

Continue to the next guide…

Topics: Developer Docs