Skip to content

Migrate from Twilio to LiveSwitch

Oliver Hargreaves Dec 21, 2023 11:43:16 AM

Introduction

LiveSwitch is a programmable video platform that gives developers a level of flexibility and customization that other platforms do not. Each of the areas shown in this migration guide can be further expanded for more advanced needs.

This guide will focus on a JavaScript migration, but all topics here can be applied to iOS and Android based applications as well.

In this guide, I will walk through how to replace each piece of Twilio code with the LiveSwitch equivalent to get a multi-user audio and video session running with chat functionality.

 

Getting Started with LiveSwitch

The first step is to sign up for a LiveSwitch free trial account. To do so, please follow these steps:

  1. Go to https://www.liveswitch.io/start-building-better-webrtc-apps
  2. Fill out the form
  3. Click “Try it today!”
  4. You are all set

This account will give you access to the LiveSwitch Cloud Console. You can perform a number of actions from the console, but the two most important actions (for this guide at least) are being able to configure your application settings here and to download the SDKs for iOS and Android from here.

 

Adding LiveSwitch to Your Application

Now that you have your account set up, it is time to start migrating your application.

You can use NPM to install the LiveSwitch Javascript SDK.  To do so, run the following command in the root directory or your application:

npm install fm.liveswitch


You should also uninstall the Twilio Video SDK at the same time using the following command:

npm uninstall twilio-video


To best determine what Twilio code will need to be removed from your application please take a look at the official Twilio migration guide which identifies the core code that will need to be removed.

You will need to set up your application configurations using your LiveSwitch Account.

In your project, add a configuration file named liveswitch_config.json. In this file, you will have three values: gatewayURL, sharedSecret, and applicationId. All three of these values come from the applications page in your cloud console account. Here is an example of the values:

{

  "gatewayUrl": "https://v1.liveswitch.fm:8443/sync",
  "sharedSecret": "--replaceThisWithYourOwnSharedSecret--",
  "applicationId": "my-app-id"
}

 

Creating a Session

In LiveSwitch, a session consists of one or more channels. For most applications, you will use a single channel. All audio and video will connect to this channel to make up a session. In order to do so, there are a number of steps you must take.

Begin by creating and registering a client with the channel you want to create. This is where you pull in the first two values from your configuration file: the gatewayUrl and the applicationId.

// Create a client.

const client = new ls.Client(config.gatewayUrl, config.applicationId);
// Set display name
client.setUserAlias(displayName);


Next you will need to generate a token.  We recommend doing this as part of a server application running along with your video application as described here; however, we do support generating tokens client side.

For our migration, we will use the client side token generation.

  // Generate a token (do this on the server to avoid exposing your shared secret).

const token = ls.Token.generateClientRegisterToken(
  config.applicationId,
  client.getUserId(),
  client.getDeviceId(),
  client.getId(),
  [new ls.ChannelClaim(channelId)],
  config.sharedSecret
);


Notice in this token creation step you pass the channelId. This channelId is how the channel is identified and should be shareable across your users or should be managed by your application.  You should also notice that we are pulling in the sharedSecret from our configuration file as well.

The last step is to register your client using the token. Once you are registered, you will no longer need to use your configuration file values.

Twilio Reference

Now that we have established an initial connection to a channel or session, it is time to start setting up and streaming our local media.

 

Handling your Local Media

For this guide, we will be enabling both audio and video streaming. You can optionally choose just one of these depending on your use case.

To enable local video in LiveSwitch, first we want to define and start the local media using the following commands:

const localMedia = new ls.LocalMedia(true, true)

await localMedia.start()


The next step is to connect the local media to the channel to begin streaming. The default connection type recommended for LiveSwitch is an SFU Upstream Connection.  We will first create the connection.

// Create audio and video streams from local media.

const audioStream = new ls.AudioStream(localMedia);
const videoStream = new ls.VideoStream(localMedia);

// Create a SFU upstream connection with local audio and video.
const connection = channel.createSfuUpstreamConnection(
  audioStream,
  videoStream
);


Once the connection is created, the only other step is to open the connection.

connection.open();


You are now streaming your local video and audio but you do not have your local preview setup.

To add your local video to your layout, you simply call the getView() method on the media object which will return an HTMLElement structure that can be programmatically added to your layout using one of the insert calls, such as this:

// get UI element from media

videoNode = localMedia.value.getView();
// get first child since video element will need to be first child
firstChild = videoContainer[0].childNodes[0]
// perform insert of video element
videoContainer[0].insertBefore(videoNode, firstChild)

Twilio Reference

 

Handling Remote Media

While it is great that we can now share and view our local media, the most important aspect of any video based application is usually being able to see and hear others.  Let’s walk through how to do that using LiveSwitch.

Since we can have multiple remote media connections that can come in at any time or even be established before our local connection to the channel is created, we must take a slightly different approach here.

Once we have our channel established, we want to add a listener that will trigger each time a new remote connection is established on the channel. When a connection is made, we will want to establish a local object that defines the remote connection.

channel.addOnRemoteUpstreamConnectionOpen((remoteConnectionInfo: any) => {

  let downstreamConnection, remoteMedia;
  [downstreamConnection, remoteMedia] = openSfuDownstreamConnection(remoteConnectionInfo, channel);
  if (callback) {
    callback(downstreamConnection, remoteMedia);
  }
});


Notice that we will also be using an SFU type connection for the remote media, only this time it is a downstream connection.  There are two important values we need from this function call. First is the downstream connection itself. The connection is how we can listen for events from that user. The next is the remote object associated with the connection. This is what we need to add the remote video to our layout.

Because there are a number of actions we want to take per downstream connection, I recommend using a callback to store the data in a data structure that you are comfortable working with. In this case, I use a dictionary to store the connection, the remote media object, the index of the connection (for layout purposes), and then the related display name which will be used in the layout as well.

let displayName = downstreamConnection.getRemoteConnectionInfo().getUserAlias();

downstreamConnections.value[downstreamConnection.getId()] = {
  connection: downstreamConnection,
  remoteMedia: remoteMedia,
  index: remoteCounter.value++,
  displayName: displayName
};


Just like we set up a listener on the channel for when a new remote connection is added, we now need to add a listener on the downstream connection to make sure we know when the connection is closed.  Now that we have a downstream connection object saved, we can use our OnStateChange hook to listen for the connection being closed.

connection.addOnStateChange((conn) => {

  if (conn.getRemoteClosed()) {
    const downstreamData = downstreamConnections.value[connection.getId()];
    // delete the remote media
    downstreamData.remoteMedia.destroy();
    delete downstreamConnections.value[connection.getId()];
    //update the video tile index counter
    remoteCounter.value--;
  }
});


When the connection is closed, we want to delete it from our active connection list, clean up the remote media object, and update any variables that impact our layout (in this case remoteCounter).

Now that we are properly adding and removing connections to our application’s state, it is time for the most important step: displaying our remote media. The LiveSwitch Javascript SDK offers the same helper function that generates the HTML for the video and audio of a remoteMedia object as we have on the localMedia object.

This makes it convenient to create a video tile component that can be reused for both local and remote videos.

To add the media object to your layout, use the following code:

const videoNode = remoteMedia.value.getView();

videoContainer[insertPosition].insertBefore(videoNode, firstChild);


This will fetch the HTMLElement object and then the insert call is what programmatically adds it to our layout. The exact logic of where to add the video tile will differ based on your application’s layout.

Twilio Reference

 

Chat in LiveSwitch

Our next feature to cover is chat or messaging. While I recognize not all applications have a chat feature, most will or will need to use data channels to pass along additional information.

Chat is part of our meeting channel, and we will need to have that object handy to both send a chat message and to listen for when others send a message.

Sending a chat message can be as simple or complex as you want. All you need is a string of text and you can send a message. In this example, I want additional context, like who sent the message and when it was sent.

let dateObj = new Date();

var message = {
  from: displayName,
  timestamp: (new Date().getTime()),
  timestampHourFormat: dateObj.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }),
  timestampDateFormat: (dateObj.toLocaleString()),
  text: text
}
// if we have a channel, send the message
if (channel) {
  channel.sendMessage(JSON.stringify(message))
}


I generate my message object using a JSON format, set the values in my object, then stringify my JSON object before calling sendMessage().

We also need to set up a listener to catch the message. There is a hook on the channel for onMessage which we will use here.  In this case, I am just printing the message to the console, but you can output the information into your user interface or into a data structure for later use.

  channel.addOnMessage(function(sender: ls.ClientInfo, message: string){

  var data = JSON.parse(message)
  console.log("]From: " + data.from + " Message: " + data.message);
})

Twilio Reference

 

Additional User Interactions

While your application may have a plethora of actions that a user can take, I want to focus on the media actions for your local media and reacting to other user’s media actions.

In order to change the mute state of either the local audio or video, we will use the same pattern. For our upstream connection, we will fetch the config object from the connection, call setLocalAudioMuted() or setLocalVideoMuted() to set the flag to true or false depending on our desired state, and then update the connection with the new config state.

  const upstreamConnection = state.upstreamConnection as ls.SfuUpstreamConnection;

// need to send an event that we have changed mute state
// need to pull config off of the connection
let config = upstreamConnection.getConfig();
// update the property on the connection
config.setLocalAudioMuted(state.audioMuted);
// save the changes which will trigger the event to that can picked up by others in the channel
upstreamConnection.update(config);


In this example we use setLocalAudioMuted() to change the audio state. We could also change this or add a second call to setLocalVideoMuted() to change the mute state of the video stream.

The reason we use this pattern is to trigger an event on the connection when we perform local mute. In order for others to react to this (or for a local user to react to remote media state changes) we will create a handler based on the OnRemoteUpdate hook that comes as part of the downstream connection object we stored above.

  downstreamConnection.addOnRemoteUpdate((old, connectionInfo) => {

  // since we do not get specific handlers like we do on local media,
  // we need to assume both audio and video states could have changed
  micMuted.value = connectionInfo.getLocalAudioMuted();
  cameraMuted.value = connectionInfo.getLocalVideoMuted();
})


Since this handler will fire when any change is made to the connection, we will inspect the properties we care about, in this case audio and video mute states, and update our layout accordingly.

Twilio Reference

 

Leaving a Meeting

The last set of actions you will need to take is to properly leave and end a session.

There are three key tasks we need to complete to perform a full disconnect. First, we will loop through our list of active downstream connections. For each connection, we call close() assuming it is not already in the process of being closed. Once it is closed, we can remove the connection from our local list. Second, we want to unregister our client from the channel. Finally, we want to stop our local media with a simple call to stop().

  for (let key in downstreamConnections) {

  //pull of the connection object
  const dsConnection = downstreamConnections[key].connection;
  // if the connection is not closed or in a state of closing, close it
  if (dsConnection.getState() !== ls.ConnectionState.Closed.valueOf() || dsConnection.getState() !== ls.ConnectionState.Closing) {
    dsConnection.close();
  }
  // remove the connection from out local list
  delete downstreamConnections[key];
}
// handle local connection
if (client) {
  return client
    .unregister()
    .fail(() => ls.Log.error("Unregistration failed."));
}

localMedia.stop();

Twilio Reference

 

Additional Information

This guide is intended to cover the fundamentals of what may be needed to migrate from Twilio Video to the LiveSwitch Platform. If you do not see a piece of functionality described in this guide, please take a look at our developer documentation here. This also includes the equivalent code examples for our iOS and Android SDKs.

If you are interested in leveraging the flexibility of LiveSwitch, I recommend you take a look at some of our recent blog posts which describe how to manipulate audio and video during different stages of the media pipeline. Our blogs can be found here.

If you have any technical questions or run into issues with your migration, you can reach out to the support team at support@liveswitch.com.

Any questions about licensing or custom migration plans can be directed to sales@liveswitch.com.