Create a Video Lobby Feature

September 12, 2022
Written by
Reviewed by
Abby Ford
Twilion

Video Lobby Title

Ahoy! As you might know, Twilio works on a pay-as-you-go model and so does Twilio Video, meaning it charges per minute per participant for each video room. Hence, in most business cases it is not a good idea to start a video room until one of the following is true:

  • The host joins.
  • There are more than one participant in the room.
  • You can validate the schedule of the meeting before you start.

In this article you are going to learn how to build a cost effective lobby (waiting) area feature for your Twilio Video application using Twilio Sync.

What you’ll build

The example scenario I will consider in this article is where the video room starts when a host joins the room. But this approach can also apply to starting a video room when more than one participant joins, or if it is a scheduled meeting and the backend application validates the schedule before meeting. 

Here’s an example of what a lobby for a guest participant might look like before the host participant joins the room.

Example of what a lobby or waiting room looks like

And now you see that a guest joined the video room after the host joined and started the room.

 

Example of what it looks when a the video meeting starts after both participants have joined

Prerequisites

Let's start building!

Here is a visual overview of the steps the application will follow once you have completed building this feature.

The steps involved in transitioning form a lobby area to live meeting

Twilio Sync

The Twilio Sync feature provides a way to maintain real-time (millisecond level) state synchronization. There are three ways you can use Twilio Sync: Sync Document, Sync List and Sync Map. I will be using the Sync Document feature to receive status of the video room on the client side.

Implementation Steps

Follow the steps below to build your lobby feature. You can find code repo here.

Create Sync service

Create the Twilio Sync Service by clicking on Create new Sync Service from Console or via the Sync API . I used the name, “SyncServiceForWaitingRoom.”

Make a note of the Sync Service SID because you will use these in the following steps.


Image of the "Create a new Sync Service" screen

Create Sync document

Create a separate Sync document for every meeting you create and if a non-host participant is joining before the host of the meeting. Note that the host and non-host participants are logically different user types. You would need to write business logic for such role-based access. Twilio does not model participant roles natively.

Keep the friendly name of the Sync document the same as the video room unique name. This will help clients to subscribe to the events from the relevant room easily.

The code snippet below is from get_token.js file for your understanding. Check to see if the Sync document exists.

async function isSyncDocExists(docName) {
   let res = false;
   let status = false;
   try {
       res = await client.sync.v1.services(process.env.SYNC_SERVICE_SID)
                       .documents(docName)
                       .fetch()
       status = true;
   } catch (error) {
      console.log(error);
   }
   return { status, data: res };
}

Create a Sync Document with room: not_started data. This parameter will be monitored on the updated event on the client side to check if the video room has started.

async function createSyncDoc(docName) {
   let res = false;
   let status = false;
   try {
       res = await client.sync.v1.services(process.env.SYNC_SERVICE_SID)
                   .documents
                   .create({ uniqueName: docName, data: { room: 'not_started' } })
       status = true;
   } catch (error) {
       console.log(error);
   }
   return { status, data: res };
}

Get Token

Expose the API endpoint as /get_token to serve tokens (for Video and Sync). Each participant would request this API to get the tokens and this API endpoint will check if the video room exists. If yes, then there’s no need to pass the Sync token as a response to the client application. Otherwise, the client can expect an access token with both Sync and Video grants.

exports.handler = async function(context, event, callback) {
 let roomName = event.roomname;
 let videoRoomStatus = false;
 let AccessToken = require('twilio').jwt.AccessToken;
 const SyncGrant = AccessToken.SyncGrant;
 let isSyncDocExists;
  const accessToken = new twilio.jwt.AccessToken(
   context.ACCOUNT_SID, context.API_KEY_SID, context.API_KEY_SECRET
 );
  accessToken.identity = event.username;
 const videoGrant = new twilio.jwt.AccessToken.VideoGrant({
   room: roomName
 });
  accessToken.addGrant(videoGrant);
  if(event.role == 'host' || event.role == 'co_host'){  //If host or co-host joined the room
   //update sync document
    isSyncDocExists = await ifSyncDocExists(roomName);
   if(isSyncDocExists.status === false){
       await createSyncDoc(roomName);
   }
   await updateSyncDoc(roomName);
   videoRoomStatus = true;
 }else{                                                //If any guest joins the room
   let isRoomExists = await isVideoRoomExists(roomName);
   if(isRoomExists.status === true){                   //Check if room exists
     videoRoomStatus = true;
   }else{                                              //If room does not exists then check if Sync document exists
     videoRoomStatus = false;
     isSyncDocExists = await ifSyncDocExists(roomName);
     if(isSyncDocExists.status != true){
       await createSyncDoc(roomName);
     }
   }
 }
 if(!videoRoomStatus){                                 //create and add Sync grant only when Video room does not exists
   let syncGrant = new SyncGrant({
       serviceSid: context.SYNC_SERVICE_SID,
   });
   accessToken.addGrant(syncGrant);
 }
  return callback(null, {
   token: accessToken.toJwt(),
   videoRoom: videoRoomStatus
 });
}

Subscribe to the Sync document from client

Using a token, the client application subscribes to the document with the same friendlyName as the video room unique name for the updated event and it will monitor for event.data.room = started.  As soon as the client receives this updated event, it makes participants join the room using Video.connect().

Below you see the code snippet from app.js:

try {
  syncClient = new Twilio.Sync.Client(data.token);
  syncClient.document(roomname)
    .then(function (document) {
      // Listen to updates on the Document
      document.on('updated', async function (event) {     //Received Document update event.
        if (event.data.room == 'started') {               //If room is 'started' then make participant join the video room
          meetingRoomNotStarted.style.display = "none";
          container.style.display = "block"
          addLocalVideo();
          room = await Twilio.Video.connect(data.token);
          room.participants.forEach(participantConnected);
          room.on('participantConnected', participantConnected);
          room.on('participantDisconnected', participantDisconnected);
          connected = true;
          updateParticipantCount();
        }
      });
    })
    .catch(function (error) {
      console.error('Unexpected error', error)
    });
} catch (error) { console.log(error) };

Update Sync document

Whenever a host participant requests a token to API /get_token video room, create the new meeting room with the given Identity and update the Sync document with friendlyName as video room unique name.


Below you see the code snippet from get_token.js:

async function updateSyncDoc(docName) {
   let res = false;
   let status = false;
   try {
       res = await client.sync.v1.services(process.env.SYNC_SERVICE_SID)
           .documents(docName)
           .update({ data: { room: 'started' } });
       status = true;
   } catch (error) {
       console.log(error);
   }
   return { status, data: res };
}

Unsubscribe Sync Document

Unsubscribe to the Sync document to not receive further events from the same Sync document. This needs to be done on the client side as document.close().

See the code sample from app.js during room disconnect():

const disconnect = async () => {
 if(syncClient != undefined){                          
   //Close subscription of sync Document for guests participants
   var document = await syncClient.document(room.name);
   document.close();
 }
 room.disconnect();
 while (container.lastChild.id != 'local') {
   container.removeChild(container.lastChild);
 }
 button.innerHTML = 'Join';
 connected = false;
 updateParticipantCount();
};

Next steps for your video waiting room

Congratulations!! You have learned how to add a lobby (waiting) area feature on your Twilio Video app. Doing so will give your participants a positive experience while they wait for the meeting to start, and it’ll save you the cost of paying for a meeting that isn’t in session.

Below are a few things to consider as you implement and evolve this feature to suit your business needs.

  • Based on business use cases and As-Is infrastructure, you can also replace Twilio Sync with plain Websockets for the lobby area feature.
  • You can also consider adding business logic validating the schedule of a meeting before you create a video room. If a participant tries to join the meeting room before the scheduled time, then that participant can wait in the lobby area.
  • Lobby areas can also be used for advertising, marketing, or giving important information while participants are waiting for the  get video meeting to start.
  • It is highly recommended to use the Status callback event webhook for Twilio Video and perform necessary cleanup actions on room-ended or other relevant events, such as deleting the Sync document and recording compression related activities.

For assistance with any aspect of your Twilio implementation, reach out to Twilio Professional Services. We have plenty of solutions like this one in our back pocket and we can’t wait to help you build!

Viral Gandhi is a Strategic Onboarding Engineer at Twilio Professional Services. He mainly helps customers to get onboarded with Twilio products and implement them in their solution. He can be reached at vigandhi [at] twilio [dot] com.