Bring phone calls to your web browser with Bandwidth and WebRTC

A computer screen showing windows with in-app video and lines of code

Let’s get one thing straight, when talking about getting work done, people like things fast and easy. When users are in your application they don’t want three tools when one will do. To help them in that aim, you should drop the handset and bring telephony right into the browser. The good news is that it has never been easier to make or receive calls from directly inside your own web app.

WebRTC is the key

Web Real Time Communications, WebRTC, has been around since 2011, and over the last few years it has reached a level of maturity and penetration that makes it the easy choice for web-based calling.

WebRTC provides a standards-based way for developers to access microphones and cameras from within a browser, as well as methods to send that data securely back to a media server. Couple that with a robust interconnect to the PSTN (like the one Bandwidth can provide you) and you’re ready to go. 

Best of all, we have the tools to reduce getting started to just a few lines of code.

A primer on the pieces

There are a few moving parts in this equation. Let’s start with the main actors: Clients and a Server. The Clients can also be called Participants, and they are all the people who are involved in the call. Participants can come in two flavors: web-based and PSTN-based (those on the phone).

The Server is where we do the Call Control—this means that all calls are started here and participants are granted access to calls via the Server as well. This is also called 3rd Party Call-Control and is a great way to manage the security of your call (and reduce unwanted video call bombings).

We need to round this out with Streams and Subscriptions: Streams being the media that each participant generates from their input devices and the Subscriptions being the media received by Participants from the other people on the call.

I’ve been calling this a “call”, but we often refer to these as Sessions as well. This is in part because they can contain audio or video and also have two or more participants, so it challenges some people’s notion of a call. 

If you want to dive a bit deeper and better understand the details on these entities, please take a read through our WebRTC Overview on our development site.

Flowing some media

Please note, we’ll be going over an example in NodeJS and Javascript. I’ve chosen to keep this as vanilla JS as possible to keep things simple.

While the browser may put the Web in WebRTC, it isn’t setting up the call. First we need to set things up on the server.

We’re going to do four things here, and they’re all pretty straightforward:
  1. Create a Session – as we said before, this is a synonym for the call
  2. Create the Participant – in this case, it’s our browser Participant
  3. Add the Participant to the Session – this will automatically connect the audio streams with other Participants that are also added to this Session, this is accomplished by placing a Subscription to the session in the body parameter
  4. Return the browser token – we’ll need that token to join the call from the browser
try {
   // create the session
   let sessionBody = new BandwidthWebRTC.Session({ "tag" : "example-session"});
   let sessionResponse = await webRTCController.createSession(account_id,sessionBody);
   // create the participant
   let participantBody = new BandwidthWebRTC.Participant({
tag: "Browser-User-ID", publishPermissions: ["AUDIO"]});
   let createResponse = await webRTCController.createParticipant(accountId, participantBody);
   // add the participant to the session, will setup subscriptions for all
   let addBody = new BandwidthWebRTC.Subscriptions({ sessionId: sessionResponse.id });
   await webRTCController.addParticipantToSession( accountId, session_id, participant_id, body);
} catch (error) { ... }
Now back in the browser we have to do some work to get things ready. 
  1. Import the SDKs and instantiate the clients
  2. Create 2 event listeners: one for when streams become available and the other for when they go away.
  3. Create an <Audio> tag with autoPlay set to true
  4. Make a call to getUserMedia with audio set to true
<script src="BandwidthRtc.bundle.js"></script>
<script language="javascript">
   const bandwidthRtc = new BandwidthRtc();
 
   bandwidthRtc.onStreamAvailable((rtcStream) => {
       console.log("receiving audio!");
       remoteVideoComponent = document.getElementById("mediaPlayer")
       remoteVideoComponent.srcObject = rtcStream.mediaStream
   });
   bandwidthRtc.onStreamUnavailable((endpointId) => {
       console.log("no longer receiving audio");
       remoteVideoComponent = document.getElementById("mediaPlayer")
       remoteVideoComponent.srcObject = undefined
   });
</script>
<body>
  <audio id="mediaPlayer" autoPlay></audio>
  <script>
	const constraints = {audio: true};
	navigator.mediaDevices.getUserMedia(constraints)
  </script>
</body>

There is some complicated negotiation that needs to take place between the browser (client) and the media server—we need to sort out ICE to deal with firewalls and NATs and we also need to deal with SDP.

But that’s actually all taken care of via our browser SDK, neatly handled by one call to connect(). Wasn’t that easy? You’ll also notice that we’re using the token here that we generated and returned in the server code previously.

  bandwidthRtc
       .connect({deviceToken: token})
       .then(async () => {
           // Publish the browser's microphone
           await bandwidthRtc.publish({
               audio: true,
               video: false,
           });
           console.log("browser mic is streaming");
       });

At this point we’re actually flowing media, but there’s no one to receive it. Now we’re ready to get the PSTN caller connected. To do this we’re going to need another important piece: a SIP Interconnect. The SIP Interconnect does the translation from SIP protocol to the proprietary signaling used by our WebRTC platform. No surprise, yet again, Bandwidth has you covered here.

All you need to do is transfer any inbound or outbound call that is active in our Voice API to our WebRTC system. We provide helper methods to include the Participant ID and Session ID so they get into the right call. (Just to be clear, this is all server side.)

In more detail:
  1. Again we create a Participant, this time for the PSTN caller, noting the token
  2. And again, we add the participant to the Session
  3. Initiate an outbound Call, using the voiceController
try {
   // create the PSTN participant
   let participantBody = new BandwidthWebRTC.Participant({
tag: "PSTN-User-ID", publishPermissions: ["AUDIO"]});
   let createResponse = await webRTCController.createParticipant(accountId, participantBody);
   // again add the participant to the session, using session Id from before
   let addBody = new BandwidthWebRTC.Subscriptions({ sessionId: sessionResponse.id });
   await webRTCController.addParticipantToSession( accountId, session_id, participant_id, body);
   // initiate the call to the PSTN, using Vars from our config (or http request)
   let body = new BandwidthVoice.ApiCreateCallRequest({
       "from": FROM_NUMBER,
       "to": OUTBOUND_PHONE_NUMBER,
       "applicationId": VOICE_APPLICATION_ID,
       "answerUrl": VOICE_CALLBACK_URL,
       "answerMethod": "POST",
       "callTimeout": 10
   });
   await voiceController.createCall(accountId, body);
} catch (error) { ... }

Lastly, we’ll need to transfer the call to the WebRTC session when we receive a callback from the Voice API telling us the call has been answered.

var participant = calls.get(callId);
// response payload sent back to the Voice API to transfer the call into the WebRTC
const bxml = await generateTransferBxml(participant.token);
 
// Send the payload back to the Voice API
res.contentType("application/xml").send(bxml);

And now you have media flowing and your caller is connected with your browser—enjoy the call!

Looking for comms APIs?

Leaders trust Bandwidth’s communication APIs to perform at enterprise scale and with 24/7 support.