Implement Video Chat with Xirsys, WebRTC, and ChatEngine

In our previous tutorial, we showed you how to integrate WebRTC video calling into your chat app with ChatEngine. ChatEngine makes this easy, because there is an open source, community supported WebRTC plugin for integrating RTC in browser applications. PubNub provides an instant connection for all internet-capable devices, no matter what network they are on.

In this tutorial, we’ll make our WebRTC-powered video chat more reliable with a service called Xirsys (more on them in a bit). Why Xirsys?

A peer to peer connection with WebRTC isn’t always reliable. The streaming of peer to peer audio and video is not allowed on some networks for security reasons, and sometimes, they just fail. There is a safe, reliable workaround for these scenarios, so your users will always see their friend, regardless of LAN.

Video call with WebRTC on mobile

 

TURN Servers

WebRTC peer to peer connections fail frequently. They are also often restricted by security. TURN servers allow each WebRTC client to stream all of their audio and video through a 3rd party, so no matter what the security rules are for clients, they can always video conference. As a developer, you can build or buy.

WebRTC Relayed TURN Diagram

Establishing your own global TURN service with multiple points of presence is costly and risky. Xirsys provides on-demand WebRTC infrastructure with their globally distributed TURN server offerings.

This tutorial will expand on the WebRTC app from part 1 with a demonstration for adding Xirsys to make your video connections more reliable.

What do we need to do to accomplish this?

  1. Create a free PubNub account (on this page, below this text) and ChatEngine App, if you have not already.
  2. Set up your app’s web front end (if you don’t have one yet, you can use mine, hosted on GitHub: WebRTC with ChatEngine Demo app).
  3. Make a free Xirsys account.
  4. Make a PubNub Function endpoint with a secure, serverless, Xirsys token service.

If you have not already created a ChatEngine app, sign up, sign in, and click the deploy button below

In my example ChatEngine WebRTC application, there is JavaScript code that requests temporary Xirsys tokens from my Xirsys account. When you set up your own WebRTC application, you need to securely provide TURN access tokens to users using the Xirsys API.

A microservice that provides an elegant solution is needed. PubNub Functions makes this service extremely easy to deploy, monitor, and auto-scale. If you’re unfimilar with PubNub Functions, read my quick crash-course in PubNub Functions.

Here is the architecture of the token service:

Diagram for Xirsys token service with PubNub Functions

WebRTC TURN Service with Xirsys

First, we will make the serverless Xirsys token service using PubNub Functions. Go to your PubNub Admin Dashboard and click on your ChatEngine Application. Next, click on the Functions tab on the left side.

Next, click on the existing ChatEngine Function.

You should see the ChatEngine Function endpoint, and at the bottom of the screen, there is a create button. Click create and make a new API endpoint for your new service. Make sure that it is of type On Request.

Create a new PubNub Function

You should now see the endpoint alongside your ChatEngine Endpoint. Click on it so we can add the new JavaScript code.

PubNub Function Module Names

Once you see the Functions editor, you can add your Xirsys secret information. Next, go to the Xirsys admin panel and grab your IdentityCredential, and Channel for your account. Click on the API Info button to see your app’s credentials.

The Xirsys Admin Panel

Go back to your PubNub Functions editor and click the My Secrets button on the left side of the editor. Create a new key and value by entering text in the my secrets menu input. To make the new service work, add a key called xirsys. Set the xirsys key’s value to your_ident:your_secret from the Xirsys admin panel.

PubNub Functions Vault for Secret Keys

Serverless Microservice

Now we add the service code. Copy and paste the code from my Xirsys Token Service GitHub Gist:

const pubnub = require('pubnub');
const kvstore = require('kvstore');
const xhr = require('xhr');
const vault = require('vault');
const base64Codec = require('codec/base64');

// If you are still developing, set the permittedOrigin to * and remove the check on line 15
const permittedOrigin = 'https://your-website-origin.com';
const xirsysChannel = 'your-xirsys-channel-name';

export default (request, response) => {
    response.headers['Access-Control-Allow-Origin'] = permittedOrigin;
    response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept';
    
    if (request.headers.origin !== permittedOrigin) {
        response.status = 401;
        return response.send();
    }

    if (request.method.toUpperCase() === 'GET') {
        const uuid = newUuid() + newUuid() + newUuid() + newUuid();
        const tok = base64Codec.encodeString(uuid);
        
        return kvstore.set(tok, true, 60).then(() => {
            response.status = 200;
            return response.send(tok);
        });
        
    } else if (request.method.toUpperCase() === 'PUT') {
        if (!request.headers.tok) {
            response.status = 401;
            return response.send();
        }
        
        return kvstore.get(request.headers.tok).then((validTok) => {
            if (!validTok) {
                console.error('invlaidtok', request.headers.tok);
                response.status = 401;
                return response.send();
            }
            
            return vault.get('xirsys').then((xirsysCredential) => {
                const xirsysEndpoint = `https://${xirsysCredential}@global.xirsys.net/_turn/${xirsysChannel}`;
                return xhr.fetch(xirsysEndpoint, { 'method': 'PUT' })
                .then((res) => {
                    let rtcObj = flatten(JSON.parse(res.body).v);
                    response.status = 200;
                    return response.send(rtcObj);
                }).catch((err) => {
                    console.error(err);
                    response.status = 400;
                    return response.send();
                });
            });
        });
    } else {
        response.status = 401;
        return response.send();
    }
};

function newUuid() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

function flatten(rtc) {
    let newcred = { urls: [] };

    rtc.iceServers.forEach((obj) => {
        newcred.urls.push(obj.url);
        newcred.username = obj.username;
        newcred.credential = obj.credential;
    });
    return newcred;
}

At the top of the code file, change the xirsysChannel constant to your channel from the Xirsys admin panel. Also, change the permittedOrigin to your front end application’s URL. If you are still developing, you can set it to * which means any origin, and also delete the “if” that checks the origin “request.headers.origin !== permittedOrigin”.

Bring this back later when you have a permanent origin, for security reasons. If you want to add a monitoring service, check out this PubNub Functions monitoring guide.

To globally deploy your new service, click the Restart button in the top right corner. Clicking this button will not harm the state of your existing ChatEngine app, and it will start the 2nd service.

The Front End Web Application Code

Now that we have created the service for Xirsys, we need to edit the front end JavaScript of the web application. The following code is from the app.js file in the open source ChatEngine WebRTC example app.

const turnApiUrl = '_MY_PUBNUB_FUNCTIONS_XIRSYS_TOKEN_SERVICE_URL_';
let turnToken;
request(turnApiUrl, 'GET').then((response) => { turnToken = response });

Replace the turnApiUrl in your front end JavaScript to the URL for your new service. You can get this URL from the PubNub Functions editor. Click on the tab for your new service, and click the Copy URL button on the left side. It will copy the URL to your clipboard.

PubNub Functions Endpoint URL Path

Paste the URL in your front-end JavaScript code for turnApiUrl. Next, you need to enter your ChatEngine app’s publish and subscribe keys in the same file, around line 90.

// Init ChatEngine
const ChatEngine = ChatEngineCore.create({
    publishKey: 'YOUR_PUBLISH_KEY_HERE',
    subscribeKey: 'YOUR_SUBSCRIBE_KEY_HERE'
}, {
    globalChannel: 'chat-engine-webrtc-example'
});

Doing all of this adds Xirsys servers to your WebRTC connection automatically, like this example front-end JS code for the WebRTC API. This is what you would need to do if you were not using the ChatEngine WebRTC plugin (credentials are fake):

// Documentation on this here: https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration#Example
myPeerConnection = new RTCPeerConnection({
        iceServers: [     // Information about ICE servers - Use your own!
            {
                "urls": [
                    "stun:xirsys.com",
                    "turn:xirsys.com:80?transport=udp",
                    "turn:xirsys.com:80?transport=tcp",
                    "turns:xirsys.com:443?transport=tcp"
                ],
                "username": "c8b127fc-cc35-13e8-999a-123456789abc",
                "credential": "c8b12892-aa35-13e8-9274-123456789abc"
            }
        ]
});

If the app cannot connect users with peer to peer connections, the browser knows to instantly fallback to TURN with Xirsys.

 

Warning

The WebRTC Plugin referenced in this post is open source and community supported.

Use at your own risk!

 

The ChatEngine WebRTC plugin has a configuration option (ignoreNonTurn) to ignore all peer to peer connections if you strictly want to use TURN. This makes running your application more costly because all users will stream their audio and video data through Xirsys, instead of only the ones who need to.

// add the WebRTC plugin
let config = {
    rtcConfig,
    ignoreNonTurn: true, // Set this to false if you want to fall back to TURN when peer to peer fails
    myStream: localStream,
    onPeerStream,
    onIncomingCall,
    onCallResponse,
    onDisconnect
};

Deploy your front-end web app to a provider like GitHub pages and try it out!

Frequently Asked Questions (FAQ) about the WebRTC Plugin

Is the plugin officially a part of ChatEngine?

No. It is an open source project that is community supported. If you have questions or need help, reach out to devrel@pubnub.com. If you want to report a bug, do so on the GitHub Issues page.

Does ChatEngine stream audio or video data?

No. ChatEngine pairs very well with WebRTC as a signaling service. This means that PubNub signals events from client to client using the ChatEngine #direct events. These events include:

  • I, User A, would like to call you, User B
  • User A is currently trying to call you, User B
  • I, User B, accept your call User A
  • I, User B, reject your call User A
  • I, User B, would like to end our call User A
  • I, User A, would like to end our call User B
  • Text instant messaging like in Slack, Google Hangouts, Skype, Facebook Messenger, etc.

Can I make a group call with more than 2 participants?

Group calling is possible to develop with WebRTC and ChatEngine, however, the current ChatEngine WebRTC plugin can connect only 2 users in a private call. The community may develop this feature in the future but there are no plans for development to date.

I found a bug in the plugin. Where do I report it?

The ChatEngine WebRTC plugin is an open source, community supported project. This means that the best place to report bugs is on the GitHub Issues page in for the code repository. The community will tackle the bug fix at will, so there is no guarantee that a fix will be made. If you wish to provide a code fix, fork the GitHub repository to your GitHub account, push fixes, and make a pull request (process documented on GitHub).

For more ChatEngine examples and plugins, check out the ChatEngine Github. If you like this plugin, need some help, or want to build something similar, reach out to devrel@pubnub.com. We want to hear your feedback!

Try PubNub Today