Migrate from Twilio Video to Azure Communication Services

This article describes how to migrate an existing Twilio Video implementation to the Azure Communication Services Calling SDK. Both Twilio Video and Azure Communication Services Calling SDK are cloud-based platforms that enable developers to add voice and video calling features to their web applications.

However, there are some key differences between them that may affect your choice of platform or require some changes to your existing code if you decide to migrate. In this article, we compare the main features and functions of both platforms and provide some guidance on how to migrate an existing Twilio Video implementation to Azure Communication Services Calling SDK.

Key features available in Azure Communication Services Calling SDK

Feature Web (JavaScript) iOS Android Platform neutral
Install ✔️ ✔️ ✔️
Import ✔️ ✔️ ✔️
Auth ✔️
Join ✔️ ✔️ ✔️
Start Audio/Speaker ✔️ ✔️ ✔️
Mute ✔️ ✔️ ✔️
Unmute ✔️ ✔️ ✔️
Start Video ✔️ ✔️ ✔️
Stop Video ✔️ ✔️ ✔️
Virtual Background ✔️ ✔️ ✔️
Render User Video ✔️ ✔️ ✔️
Recording ✔️
Network Bandwidth Management ✔️ ✔️ ✔️
Quality of Service ✔️
Data Center Selection ✔️
Preview ✔️ ✔️ ✔️
Security ✔️
Networking ✔️
Screen Share ✔️
Rest APIs ✔️
Webhooks ✔️
Raw Data ✔️ ✔️ ✔️
Codecs ✔️
WebView ✔️ ✔️
Video Devices ✔️ ✔️ ✔️
Speaker Devices ✔️ ✔️ ✔️
Microphone Devices ✔️ ✔️ ✔️
Data Channel API ✔️ ✔️ ✔️
Analytics/Video Insights ✔️
Diagnostic Tooling ✔️
Reports ✔️
CallKit (iOS Only) ✔️
Picture-in-picture ✔️ ✔️

Prerequisites

  1. Azure Account: Make sure that your Azure account is active. New users can create a free account at Microsoft Azure.
  2. Node.js 18: Ensure Node.js 18 is installed on your system. Download from Node.js.
  3. Communication Services Resource: Set up a Communication Services Resource via your Azure portal and note your connection string.
  4. Azure CLI: Follow the instructions to Install Azure CLI on Windows..
  5. User Access Token: Generate a user access token to instantiate the call client. You can create one using the Azure CLI as follows:
az communication identity token issue --scope voip --connection-string "yourConnectionString"

For more information, see Use Azure CLI to Create and Manage Access Tokens.

For Video Calling as a Teams user:

UI library

The Azure Communication Services UI library simplifies the process of creating modern communication user interfaces using Azure Communication Services. It offers a collection of ready-to-use UI components that you can easily integrate into your application.

This open source prebuilt set of controls enables you to create aesthetically pleasing designs using Fluent UI SDK components and develop high quality audio/video communication experiences. For more information, check out the Azure Communications Services UI Library overview. The overview includes comprehensive information about both web and mobile platforms.

Installation

Install the Azure Communication Services Calling SDK

Use the npm install command to install the Azure Communication Services Calling SDK for JavaScript.

npm install @azure/communication-common npm install @azure/communication-calling

Remove the Twilio SDK from the project

You can remove the Twilio SDK from your project by uninstalling the package.

npm uninstall twilio-video

Object Model

The following classes and interfaces handle some of the main features of the Azure Communication Services Calling SDK:

Name Description
CallClient The main entry point to the Calling SDK.
AzureCommunicationTokenCredential Implements the CommunicationTokenCredential interface, which is used to instantiate the CallAgent.
CallAgent Start and manage calls.
Device Manager Manage media devices.
Call Represents a Call.
LocalVideoStream Create a local video stream for a camera device on the local system.
RemoteParticipant Represents a remote participant in the Call.
RemoteVideoStream Represents a remote video stream from a Remote Participant.
LocalAudioStream Represents a local audio stream for a local microphone device.
AudioOptions Audio options, provided to a participant when making an outgoing call or joining a group call.
AudioIssue Represents the end of call survey audio issues. Example responses might be NoLocalAudio - the other participants were unable to hear me, or LowVolume - the call audio volume was too low.

When using ACS calling in a Teams call, there are a few differences:

  • Instead of CallAgent - use TeamsCallAgent for starting and managing Teams calls.
  • Instead of Call - use TeamsCall for representing a Teams Call.

Initialize the Calling SDK (CallClient/CallAgent)

Using the CallClient, initialize a CallAgent instance. The createCallAgent method uses CommunicationTokenCredential as an argument. It accepts a user access token.

Device manager

Twilio

Twilio doesn't have a Device Manager analog. Tracks are created using the system’s default device. To customize a device, obtain the desired source track via:

navigator.mediaDevices.getUserMedia()

And pass it to the track creation method.

Azure Communication Services

const { CallClient } = require('@azure/communication-calling');  
const { AzureCommunicationTokenCredential} = require('@azure/communication-common'); 

const userToken = '<USER_TOKEN>';  
const tokenCredential = new AzureCommunicationTokenCredential(userToken); 

callClient = new CallClient(); 
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional user name'});

You can use the getDeviceManager method on the CallClient instance to access deviceManager.

const deviceManager = await callClient.getDeviceManager();

// Get a list of available video devices for use.  
const localCameras = await deviceManager.getCameras(); 

// Get a list of available microphone devices for use.  
const localMicrophones = await deviceManager.getMicrophones();  

// Get a list of available speaker devices for use.  
const localSpeakers = await deviceManager.getSpeakers();

Get device permissions

Twilio

Twilio Video asks for device permissions on track creation.

Azure Communication Services

Prompt a user to grant camera and/or microphone permissions:

const result = await deviceManager.askDevicePermission({audio: true, video: true});

The output returns with an object that indicates whether audio and video permissions were granted:

console.log(result.audio);  console.log(result.video);

Starting a call

Twilio

import * as TwilioVideo from 'twilio-video';

const twilioVideo = TwilioVideo; 
let twilioRoom; 

twilioRoom = await twilioVideo.connect('token', { name: 'roomName', audio: false, video: false });

Azure Communication Services

To create and start a call, use one of the callAgent APIs and provide a user that you created through the Communication Services identity SDK.

Call creation and start are synchronous. The call instance enables you to subscribe to call events. Subscribe to the stateChanged event for value changes.

call.on('stateChanged', async () =\> {  console.log(\`Call state changed: \${call.state}\`) });

1:1 Call

To call another Azure Communication Services user, use the startCall method on callAgent and pass the recipient's CommunicationUserIdentifier that you created with the Communication Services administration library.

const userCallee = { communicationUserId: '\<Azure_Communication_Services_USER_ID\>' };
const oneToOneCall = callAgent.startCall([userCallee]);

Rooms Call

To join a Room call, you can instantiate a context object with the roomId property as the room identifier. To join the call, use the join method and pass the context instance.

const context = { roomId: '\<RoomId\>' };
const call = callAgent.join(context);

A Room offers application developers better control over who can join a call, when they meet and how they collaborate. To learn more about Rooms, see the Rooms overview, or see Quickstart: Join a room call.

Group Call

To start a new group call or join an ongoing group call, use the join method and pass an object with a groupId property. The groupId value must be a GUID.

const context = { groupId: '\<GUID\>'};
const call = callAgent.join(context);

Teams call

Start a synchronous one-to-one or group call using the startCall API on teamsCallAgent. You can provide MicrosoftTeamsUserIdentifier or PhoneNumberIdentifier as a parameter to define the target of the call. The method returns the TeamsCall instance that allows you to subscribe to call events.

const userCallee = { microsoftTeamsUserId: '\<MICROSOFT_TEAMS_USER_ID\>' };
const oneToOneCall = teamsCallAgent.startCall(userCallee);

Accepting and joining a call

Twilio

When using Twilio Video SDK, the Participant is created after joining the room; and it doesn't have any information about other rooms.

Azure Communication Services

Azure Communication Services has the CallAgent instance, which emits an incomingCall event when the logged-in identity receives an incoming call.

callAgent.on('incomingCall', async (call) =\>{
    // Incoming call
    });

The incomingCall event includes an incomingCall instance that you can accept or reject.

When starting, joining, or accepting a call with video on, if the specified video camera device is being used by another process or if the camera is disabled in the system, the call starts with video off, and returns a cameraStartFailed: true call diagnostic.

const incomingCallHandler = async (args: { incomingCall: IncomingCall }) => {  
  const incomingCall = args.incomingCall;  

  // Get incoming call ID  
  var incomingCallId = incomingCall.id  

  // Get information about this Call.  
  var callInfo = incomingCall.info;  

  // Get information about caller  
  var callerInfo = incomingCall.callerInfo  
    
  // Accept the call  
  var call = await incomingCall.accept();  

  // Reject the call  
  incomingCall.reject();  

  // Subscribe to callEnded event and get the call end reason  
  incomingCall.on('callEnded', args =>  
  	{ console.log(args.callEndReason);  
  });  

  // callEndReason is also a property of IncomingCall  
  var callEndReason = incomingCall.callEndReason;  
};  

callAgentInstance.on('incomingCall', incomingCallHandler);

After starting a call, joining a call, or accepting a call, you can also use the callAgent callsUpdated event to be notified of the new Call object and start subscribing to it.

callAgent.on('callsUpdated', (event) => { 
  event.added.forEach((call) => { 
    // User joined call 
  }); 
  
  event.removed.forEach((call) => { 
    // User left call 
  }); 
});

For Azure Communication Services Teams implementation, see how to Receive a Teams Incoming Call.

Adding and removing participants to a call

Twilio

Participants can't be added or removed from Twilio Room, they need to join the Room or disconnect from it themselves.

Local Participant in Twilio Room can be accessed this way:

let localParticipant = twilioRoom.localParticipant;

Remote Participants in Twilio Room are represented with a map that has unique Participant SID as a key:

twilioRoom.participants;

Azure Communication Services

All remote participants are represented by RemoteParticipant type and available through remoteParticipants collection on a call instance.

The remoteParticipants collection returns a list of remote participants in a call:

call.remoteParticipants; // [remoteParticipant, remoteParticipant....]

Add participant:

To add a participant to a call, you can use addParticipant. Provide one of the Identifier types. It synchronously returns the remoteParticipant instance.

The remoteParticipantsUpdated event from Call is raised when a participant is successfully added to the call.

const userIdentifier = { communicationUserId: '<Azure_Communication_Services_USER_ID>' }; 
const remoteParticipant = call.addParticipant(userIdentifier);

Remove participant:

To remove a participant from a call, use removeParticipant. You need to pass one of the Identifier types. This method resolves asynchronously after the participant is removed from the call. The participant is also removed from the remoteParticipants collection.

const userIdentifier = { communicationUserId: '<Azure_Communication_Services_USER_ID>' }; 
await call.removeParticipant(userIdentifier);

Subscribe to the call's remoteParticipantsUpdated event to be notified when new participants are added to the call or removed from the call.

call.on('remoteParticipantsUpdated', e => {
    e.added.forEach(remoteParticipant => {
        // Subscribe to new remote participants that are added to the call
    });
 
    e.removed.forEach(remoteParticipant => {
        // Unsubscribe from participants that are removed from the call
    })

});

Subscribe to remote participant's stateChanged event for value changes.

remoteParticipant.on('stateChanged', () => {
    console.log(`Remote participants state changed: ${remoteParticipant.state}`)
});

Video calling

Starting and stopping video

Twilio

const videoTrack = await twilioVideo.createLocalVideoTrack({ constraints }); 
const videoTrackPublication = await localParticipant.publishTrack(videoTrack, { options });

The camera is enabled by default. It can be disabled and enabled back if necessary:

videoTrack.disable();

Or:

videoTrack.enable();

If there's a later created video track, attach it locally:

const videoElement = videoTrack.attach();
const localVideoContainer = document.getElementById( localVideoContainerId );
localVideoContainer.appendChild(videoElement);

Twilio Tracks rely on default input devices and reflect the changes in defaults. To change an input device, you need to unpublish the previous Video Track:

localParticipant.unpublishTrack(videoTrack);

Then create a new Video Track with the correct constraints.

Azure Communication Services

To start a video while on a call, you need to enumerate cameras using the getCameras method on the deviceManager object. Then create a new instance of LocalVideoStream with the desired camera and pass the LocalVideoStream object into the startVideo method of an existing call object:

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);

After you successfully start sending video, a LocalVideoStream instance of type Video is added to the localVideoStreams collection on a call instance.

const localVideoStream = call.localVideoStreams.find( (stream) =\> { return stream.mediaStreamType === 'Video'} );

To stop local video while on a call, pass the localVideoStream instance that's being used for video:

await call.stopVideo(localVideoStream);

You can switch to a different camera device while a video is sending by calling switchSource on a localVideoStream instance:

const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);

If the specified video device is being used by another process, or if it's disabled in the system:

  • While in a call, if your video is off and you start video using call.startVideo(), this method returns a SourceUnavailableError and cameraStartFailed are set to true.
  • A call to the localVideoStream.switchSource() method causes cameraStartFailed to be set to true. See the Call Diagnostics guide for more information about how to diagnose call-related issues.

To verify whether the local video is on or off you can use the isLocalVideoStarted API, which returns true or false:

call.isLocalVideoStarted;

To listen for changes to the local video, you can subscribe and unsubscribe to the isLocalVideoStartedChanged event:

// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
    // Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
    // Callback();
});

Rendering a remote user's video

Twilio

As soon as a Remote Participant publishes a Video Track, it needs to be attached. The trackSubscribed event on Room or Remote Participant enables you to detect when the track can be attached:

twilioRoom.on('participantConneted', (participant) => {
 participant.on('trackSubscribed', (track) => {
   const remoteVideoElement = track.attach();
   const remoteVideoContainer = document.getElementById(remoteVideoContainerId + participant.identity);
   remoteVideoContainer.appendChild(remoteVideoElement);
 });
});

Or

twilioRoom..on('trackSubscribed', (track, publication, participant) => {
   const remoteVideoElement = track.attach();
   const remoteVideoContainer = document.getElementById(remoteVideoContainerId + participant.identity);
   remoteVideoContainer.appendChild(remoteVideoElement);
 });
});

Azure Communication Services

To list the video streams and screen sharing streams of remote participants, inspect the videoStreams collections:

const remoteVideoStream: RemoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType: MediaStreamType = remoteVideoStream.mediaStreamType;

To render RemoteVideoStream, you need to subscribe to its isAvailableChanged event. If the isAvailable property changes to true, a remote participant is sending a stream. After that happens, create a new instance of VideoStreamRenderer, and then create a new VideoStreamRendererView instance by using the asynchronous createView method. You can then attach view.target to any UI element.

Whenever availability of a remote stream changes, you can destroy the whole VideoStreamRenderer or a specific VideoStreamRendererView. If you do decide to keep them, it displays a blank video frame.

// Reference to the html's div where we would display a grid of all remote video streams from all participants.
let remoteVideosGallery = document.getElementById('remoteVideosGallery');

subscribeToRemoteVideoStream = async (remoteVideoStream) => {
   let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    let remoteVideoContainer = document.createElement('div');
    remoteVideoContainer.className = 'remote-video-container';

    let loadingSpinner = document.createElement('div');
    // See the css example below for styling the loading spinner.
    loadingSpinner.className = 'loading-spinner';
    remoteVideoStream.on('isReceivingChanged', () => {
        try {
            if (remoteVideoStream.isAvailable) {
                const isReceiving = remoteVideoStream.isReceiving;
                const isLoadingSpinnerActive = remoteVideoContainer.contains(loadingSpinner);
                if (!isReceiving && !isLoadingSpinnerActive) {
                    remoteVideoContainer.appendChild(loadingSpinner);
                } else if (isReceiving && isLoadingSpinnerActive) {
                    remoteVideoContainer.removeChild(loadingSpinner);
                }
            }
        } catch (e) {
            console.error(e);
        }
    });

    const createView = async () => {
        // Create a renderer view for the remote video stream.
        view = await renderer.createView();
        // Attach the renderer view to the UI.
        remoteVideoContainer.appendChild(view.target);
        remoteVideosGallery.appendChild(remoteVideoContainer);
    }

    // Remote participant has switched video on/off
    remoteVideoStream.on('isAvailableChanged', async () => {
        try {
            if (remoteVideoStream.isAvailable) {
                await createView();
            } else {
                view.dispose();
                remoteVideosGallery.removeChild(remoteVideoContainer);
            }
        } catch (e) {
            console.error(e);
        }
    });

    // Remote participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        try {
            await createView();
        } catch (e) {
            console.error(e);
        }
    }
    
    console.log(`Initial stream size: height: ${remoteVideoStream.size.height}, width: ${remoteVideoStream.size.width}`);
    remoteVideoStream.on('sizeChanged', () => {
        console.log(`Remote video stream size changed: new height: ${remoteVideoStream.size.height}, new width: ${remoteVideoStream.size.width}`);
    });
}

Subscribe to the remote participant's videoStreamsUpdated event to be notified when the remote participant adds new video streams and removes video streams.

remoteParticipant.on('videoStreamsUpdated', e => {
    e.added.forEach(remoteVideoStream => {
        // Subscribe to new remote participant's video streams
    });

    e.removed.forEach(remoteVideoStream => {
        // Unsubscribe from remote participant's video streams
    });
});

Virtual background

Twilio

To use Virtual Background, install Twilio helper library:

npm install @twilio/video-processors

Create and load a new Processor instance:

import { GaussianBlurBackgroundProcessor } from '@twilio/video-processors';

const blurProcessor = new GaussianBlurBackgroundProcessor({ assetsPath: virtualBackgroundAssets });

await blurProcessor.loadModel();

As soon as the model is loaded, you can add the background to the video track using the addProcessor method:

videoTrack.addProcessor(processor, {  inputFrameBufferType: 'video',  outputFrameBufferContextType: 'webgl2' });

Azure Communication Services

Use the npm install command to install the Azure Communication Services Effects SDK for JavaScript.

npm install @azure/communication-calling-effects --save

Note

To use video effects with the Azure Communication Calling SDK, once you've created a LocalVideoStream, you need to get the VideoEffects feature API of the LocalVideoStream to start/stop video effects:

import * as AzureCommunicationCallingSDK from '@azure/communication-calling'; 

import { BackgroundBlurEffect, BackgroundReplacementEffect } from '@azure/communication-calling-effects'; 

// Get the video effects feature API on the LocalVideoStream 
// (here, localVideoStream is the LocalVideoStream object you created while setting up video calling)
const videoEffectsFeatureApi = localVideoStream.feature(AzureCommunicationCallingSDK.Features.VideoEffects); 

// Subscribe to useful events 
videoEffectsFeatureApi.on(‘effectsStarted’, () => { 
    // Effects started
});

videoEffectsFeatureApi.on(‘effectsStopped’, () => { 
    // Effects stopped
}); 

videoEffectsFeatureApi.on(‘effectsError’, (error) => { 
    // Effects error
});

To blur the background:

// Create the effect instance 
const backgroundBlurEffect = new BackgroundBlurEffect(); 

// Recommended: Check support 
const backgroundBlurSupported = await backgroundBlurEffect.isSupported(); 

if (backgroundBlurSupported) { 
    // Use the video effects feature API we created to start effects
    await videoEffectsFeatureApi.startEffects(backgroundBlurEffect); 
}

To use a custom background replacement with an image you need to provide the URL of the image you want as the background to this effect. Supported image formats are: PNG, JPG, JPEG, TIFF, and BMP. The supported aspect ratio is 16:9.

const backgroundImage = 'https://linkToImageFile'; 

// Create the effect instance 
const backgroundReplacementEffect = new BackgroundReplacementEffect({ 
    backgroundImageUrl: backgroundImage
}); 

// Recommended: Check support
const backgroundReplacementSupported = await backgroundReplacementEffect.isSupported(); 

if (backgroundReplacementSupported) { 
    // Use the video effects feature API as before to start/stop effects 
    await videoEffectsFeatureApi.startEffects(backgroundReplacementEffect); 
}

Change the image for this effect by passing it via the configured method:

const newBackgroundImage = 'https://linkToNewImageFile'; 

await backgroundReplacementEffect.configure({ 
    backgroundImageUrl: newBackgroundImage
});

To switch effects, use the same method on the video effects feature API:

// Switch to background blur 
await videoEffectsFeatureApi.startEffects(backgroundBlurEffect); 

// Switch to background replacement 
await videoEffectsFeatureApi.startEffects(backgroundReplacementEffect);

At any time, if you want to check which effects are active, use the activeEffects property. The activeEffects property returns an array with the names of the currently active effects and returns an empty array if there are no effects active.

// Using the video effects feature api
const currentActiveEffects = videoEffectsFeatureApi.activeEffects;

To stop effects:

await videoEffectsFeatureApi.stopEffects();

Audio

Starting and stopping audio

Twilio

const audioTrack = await twilioVideo.createLocalAudioTrack({ constraints });
const audioTrackPublication = await localParticipant.publishTrack(audioTrack, { options });

The microphone is enabled by default. You can disable and enable it back as needed:

audioTrack.disable();

Or

audioTrack.enable();

Any created Audio Track should be attached by Local Participant the same way as Video Track:

const audioElement = audioTrack.attach();
const localAudioContainer = document.getElementById(localAudioContainerId);
localAudioContainer.appendChild(audioElement);

And by Remote Participant:

twilioRoom.on('participantConneted', (participant) => {
 participant.on('trackSubscribed', (track) => {
   const remoteAudioElement = track.attach();
   const remoteAudioContainer = document.getElementById(remoteAudioContainerId + participant.identity);
   remoteAudioContainer.appendChild(remoteAudioElement);
 });
});

Or:

twilioRoom..on('trackSubscribed', (track, publication, participant) => {
   const remoteAudioElement = track.attach();
   const remoteAudioContainer = document.getElementById(remoteAudioContainerId + participant.identity);
   remoteVideoContainer.appendChild(remoteAudioElement);
 });
});

It isn't possible to mute incoming audio in Twilio Video SDK.

Azure Communication Services

await call.startAudio();

To mute or unmute the local endpoint, you can use the mute and unmute asynchronous APIs:

//mute local device (microphone / sent audio)
await call.mute();

//unmute local device (microphone / sent audio)
await call.unmute();

Mute incoming audio sets the call volume to 0. To mute or unmute the incoming audio, use the muteIncomingAudio and unmuteIncomingAudio asynchronous APIs:

//mute local device (speaker)
await call.muteIncomingAudio();

//unmute local device (speaker)
await call.unmuteIncomingAudio();

Detecting dominant speaker

Twilio

To detect the loudest Participant in the Room, use the Dominant Speaker API. You can enable it in the connection options when joining the Group Room with at least 2 participants:

twilioRoom = await twilioVideo.connect('token', { 
name: 'roomName', 
audio: false, 
video: false,
dominantSpeaker: true
}); 

When the loudest speaker in the Room changes, the dominantSpeakerChanged event is emitted:

twilioRoom.on('dominantSpeakerChanged', (participant) => {
    // Highlighting the loudest speaker
});

Azure Communication Services

Dominant speakers for a call are an extended feature of the core Call API. It enables you to obtain a list of the active speakers in the call. The list of dominant speakers is a ranked list, where the first element in the list represents the last active speaker on the call and so on.

In order to obtain the dominant speakers in a call, you first need to obtain the call dominant speakers feature API object:

const callDominantSpeakersApi = call.feature(Features.CallDominantSpeakers);

Next you can obtain the list of the dominant speakers by calling dominantSpeakers. This has a type of DominantSpeakersInfo, which has the following members:

  • speakersList contains the list of the ranked dominant speakers in the call. These are represented by their participant ID.
  • timestamp is the latest update time for the dominant speakers in the call.
let dominantSpeakers: DominantSpeakersInfo = callDominantSpeakersApi.dominantSpeakers;

You can also subscribe to the dominantSpeakersChanged event to know when the dominant speakers list changes.

const dominantSpeakersChangedHandler = () => {
    // Get the most up-to-date list of dominant speakers
    let dominantSpeakers = callDominantSpeakersApi.dominantSpeakers;
};
callDominantSpeakersApi.on('dominantSpeakersChanged', dominantSpeakersChangedHandler);

Enabling screen sharing

Twilio

To share the screen in Twilio Video, obtain the source track via navigator.mediaDevices:

Chromium-based browsers:

const stream = await navigator.mediaDevices.getDisplayMedia({
   audio: false,
   video: true
 });
const track = stream.getTracks()[0];

Firefox and Safari:

const stream = await navigator.mediaDevices.getUserMedia({ mediaSource: 'screen' });
const track = stream.getTracks()[0];

Obtain the screen share track, then you can publish and manage it the same way as the casual Video Track (see the “Video” section).

Azure Communication Services

To start screen sharing while on a call, you can use the asynchronous API startScreenSharing:

await call.startScreenSharing();

After successfully starting to sending screen sharing, a LocalVideoStream instance of type ScreenSharing is created and added to the localVideoStreams collection on the call instance.

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );

To stop screen sharing while on a call, you can use the asynchronous API stopScreenSharing:

await call.stopScreenSharing();

To verify whether screen sharing is on or off, you can use isScreenSharingOn API, which returns true or false:

call.isScreenSharingOn;

To listen for changes to the screen share, subscribe and unsubscribe to the isScreenSharingOnChanged event:

// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
    // Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
    // Callback();
});

Media quality statistics

Twilio

To collect real-time media stats, use the `getStats`` method.

const stats = twilioRoom.getStats();

Azure Communication Services

Media quality statistics is an extended feature of the core Call API. You first need to obtain the mediaStatsFeature API object:

const mediaStatsFeature = call.feature(Features.MediaStats);

To receive the media statistics data, you can subscribe sampleReported event or summmaryReported event:

  • sampleReported event triggers every second. Suitable as a data source for UI display or your own data pipeline.
  • summmaryReported event contains the aggregated values of the data over intervals. Useful when you just need a summary.

If you want control over the interval of the summmaryReported event, you need to define mediaStatsCollectorOptions of type MediaStatsCollectorOptions. Otherwise, the SDK uses default values.

const mediaStatsCollectorOptions: SDK.MediaStatsCollectorOptions = {
    aggregationInterval: 10,
    dataPointsPerAggregation: 6
};

const mediaStatsCollector = mediaStatsFeature.createCollector(mediaStatsSubscriptionOptions);

mediaStatsCollector.on('sampleReported', (sample) => {
    console.log('media stats sample', sample);
});

mediaStatsCollector.on('summaryReported', (summary) => {
    console.log('media stats summary', summary);
});

If you don't need to use the media statistics collector, you can call the dispose method of mediaStatsCollector.

mediaStatsCollector.dispose();

You don't need to call the dispose method of mediaStatsCollector every time a call ends. The collectors are reclaimed internally when the call ends.

For more information, see Media quality statistics.

Diagnostics

Twilio

To test connectivity, Twilio offers Preflight API. This is a test call performed to identify signaling and media connectivity issues.

An access token is required to launch the test:

const preflightTest = twilioVideo.runPreflight(token);

// Emits when particular call step completes
preflightTest.on('progress', (progress) => {
  console.log(`Preflight progress: ${progress}`);
});

// Emits if the test has failed and returns error and partial test results
preflightTest.on('failed', (error, report) => {
  console.error(`Preflight error: ${error}`);
  console.log(`Partial preflight test report: ${report}`);
});

// Emits when the test has been completed successfully and returns the report
preflightTest.on('completed', (report) => {
  console.log(`Preflight test report: ${report}`);
});

Another way to identify network issues during the call is by using the Network Quality API, which monitors a Participant's network and provides quality metrics. You can enable it in the connection options when a participant joins the Group Room:

twilioRoom = await twilioVideo.connect('token', { 
    name: 'roomName', 
    audio: false, 
    video: false,
    networkQuality: {
        local: 3, // Local Participant's Network Quality verbosity
        remote: 1 // Remote Participants' Network Quality verbosity
    }
});

When the network quality for Participant changes, it generates a networkQualityLevelChanged event:

participant.on(networkQualityLevelChanged, (networkQualityLevel, networkQualityStats)  => {
    // Processing Network Quality stats
});

Azure Communication Services

Azure Communication Services provides a feature called "User Facing Diagnostics" (UFD) that you can use to examine various properties of a call to identify the issue. User Facing Diagnostics events could be caused by some underlying issue (poor network, the user has their microphone muted) that could cause a user to have a poor call experience.

User-facing diagnostics is an extended feature of the core Call API and enables you to diagnose an active call.

const userFacingDiagnostics = call.feature(Features.UserFacingDiagnostics);

Subscribe to the `diagnosticChanged`` event to monitor when any user-facing diagnostic changes:

/**
 *  Each diagnostic has the following data:
 * - diagnostic is the type of diagnostic, e.g. NetworkSendQuality, DeviceSpeakWhileMuted
 * - value is DiagnosticQuality or DiagnosticFlag:
 *     - DiagnosticQuality = enum { Good = 1, Poor = 2, Bad = 3 }.
 *     - DiagnosticFlag = true | false.
 * - valueType = 'DiagnosticQuality' | 'DiagnosticFlag'
 */
const diagnosticChangedListener = (diagnosticInfo: NetworkDiagnosticChangedEventArgs | MediaDiagnosticChangedEventArgs) => {
    console.log(`Diagnostic changed: ` +
        `Diagnostic: ${diagnosticInfo.diagnostic}` +
        `Value: ${diagnosticInfo.value}` +
        `Value type: ${diagnosticInfo.valueType}`);

    if (diagnosticInfo.valueType === 'DiagnosticQuality') {
        if (diagnosticInfo.value === DiagnosticQuality.Bad) {
            console.error(`${diagnosticInfo.diagnostic} is bad quality`);

        } else if (diagnosticInfo.value === DiagnosticQuality.Poor) {
            console.error(`${diagnosticInfo.diagnostic} is poor quality`);
        }

    } else if (diagnosticInfo.valueType === 'DiagnosticFlag') {
        if (diagnosticInfo.value === true) {
            console.error(`${diagnosticInfo.diagnostic}`);
        }
    }
};

userFacingDiagnostics.network.on('diagnosticChanged', diagnosticChangedListener);
userFacingDiagnostics.media.on('diagnosticChanged', diagnosticChangedListener);

To learn more about User Facing Diagnostics and the different diagnostic values available, see User Facing Diagnostics.

Azure Communication Services also provides a precall diagnostics API. To Access the Pre-Call API, you need to initialize a callClient, and provision an Azure Communication Services access token. Then you can access the PreCallDiagnostics feature and the startTest method.

import { CallClient, Features} from "@azure/communication-calling";
import { AzureCommunicationTokenCredential } from '@azure/communication-common';

const callClient = new CallClient(); 
const tokenCredential = new AzureCommunicationTokenCredential("INSERT ACCESS TOKEN");
const preCallDiagnosticsResult = await callClient.feature(Features.PreCallDiagnostics).startTest(tokenCredential);

The Pre-Call API returns a full diagnostic of the device including details like device permissions, availability and compatibility, call quality stats and in-call diagnostics. The results are returned as a PreCallDiagnosticsResult object.

export declare type PreCallDiagnosticsResult  = {
    deviceAccess: Promise<DeviceAccess>;
    deviceEnumeration: Promise<DeviceEnumeration>;
    inCallDiagnostics: Promise<InCallDiagnostics>;
    browserSupport?: Promise<DeviceCompatibility>;
    id: string;
    callMediaStatistics?: Promise<MediaStatsCallFeature>;
};

You can learn more about ensuring precall readiness in Pre-Call diagnostics.

Event listeners

Twilio

twilioRoom.on('participantConneted', (participant) => { 
// Participant connected 
}); 

twilioRoom.on('participantDisconneted', (participant) => { 
// Participant Disconnected 
});

Azure Communication Services

Each object in the JavaScript Calling SDK has properties and collections. Their values change throughout the lifetime of the object. Use the on() method to subscribe to objects' events, and use the off() method to unsubscribe from objects' events.

Properties

  • You must inspect their initial values, and subscribe to the '\<property\>Changed' event for future value updates.

Collections

  • You must inspect their initial values, and subscribe to the '\<collection\>Updated' event for future value updates.
  • The '\<collection\>Updated' event's payload, has an added array that contains values that were added to the collection.
  • The '\<collection\>Updated' event's payload also has a removed array that contains values that were removed from the collection.

Leaving and ending sessions

Twilio

twilioVideo.disconnect();

Azure Communication Services

call.hangUp();

// Set the 'forEveryone' property to true to end call for all participants
call.hangUp({ forEveryone: true });

Cleaning Up

If you want to clean up and remove a Communication Services subscription, you can delete the resource or resource group.

Prerequisites

  1. Azure Account: Make sure that your Azure account is active. New users can create a free account at Microsoft Azure.
  2. Communication Services Resource: Set up a Communication Services Resource via your Azure portal and note your connection string.
  3. Azure CLI: Follow the instructions to Install Azure CLI on Windows.
  4. User Access Token: Generate a user access token to instantiate the call client. You can create one using the Azure CLI as follows:
az communication identity token issue --scope voip --connection-string "yourConnectionString"

For more information, see Use Azure CLI to Create and Manage Access Tokens.

For Video Calling as a Teams user:

UI Library

The Azure Communication Services UI library, simplifies the process of creating modern communication user interfaces using Azure Communication Services Calling. It offers a collection of ready-to-use UI components that you can easily integrate into your application.

This open source prebuilt set of controls enables you to create aesthetically pleasing designs using Fluent UI SDK components and develop high quality audio/video communication experiences. For more information, check out the Azure Communications Services UI Library overview. The overview includes comprehensive information about both web and mobile platforms.

Installation

To start the migration from Twilio Video, the first step is to install the Azure Communication Services Calling SDK for iOS to your project. You can configure these parameters usingCocoapods.

  1. To create a Podfile for your application, open the terminal and navigate to the project folder and run:

pod init

  1. Add the following code to the Podfile and save (make sure that "target" matches the name of your project):
platform :ios, '13.0' 
use_frameworks! 
  
target 'AzureCommunicationCallingSample' do 
  pod 'AzureCommunicationCalling', '~> 2.6.0' 
end 
  1. Set up the .xcworkspace project
pod install
  1. Open the .xcworkspace that was created by the pod install with Xcode.

Authenticating to the SDK

To be able to use the Azure Communication Services Calling SDK, you need to authenticate using an access token.

Twilio

The following code snippets presume the availability of a valid access token for Twilio Services.

From within the Twilio Video, the access token is used to connect to a room. By passing the token to ConnectOptions, you can create the option to create or connect a room.

let connectOptions = ConnectOptions(token: accessToken) { 
 // Twilio Connect options goes here 
} 
 
let room =   TwilioVideoSDK.connect(
    options: connectOptions, 
    delegate: // The Room Delegate
)

Azure Communication Services

The following code snippets require a valid access token to initiate a CallClient.

You need a valid token. For more information, see Create and Manage Access Tokens.

// Create an instance of CallClient 
let callClient = CallClient() 
 
// A reference to the call agent, it will be initialized later 
var callAgent: CallAgent? 
 
// Embed the token in a CommunicationTokenCredential object 
let userCredential = try? CommunicationTokenCredential(token: "<USER_TOKEN>") 
 
// Create a CallAgent that will be used later to initiate or receive calls 
callClient.createCallAgent(userCredential: userCredential) { callAgent, error in 
 if error != nil { 
        // Raise the error to the user and return 
 } 
 self.callAgent = callAgent         
} 

Class reference

Class Name Description
CallClient The main class representing the entry point for the Calling SDK.
CommunicationTokenCredential The Azure Communication Services User token credential
CallAgent The class responsible of managing calls on behalf of the authenticated user

Initiating an outgoing call

Twilio

Twilio Video has a concept of Room, where if user Bob wants to have a call with client Alice, Bob can create a room and Alice has to connect to it by implementing a feature like push notification.

Connect to a room

When user Bob or user Alice wants to create or connect to a room, and they have a valid access token. They can pass the room name they want to create or connect to as a parameter of ConnectOptions.

let connectOptions = ConnectOptions(token: accessToken) { builder in 
 builder.roomName = "the-room"  
} 
 
room = TwilioVideoSDK.connect(options: connectOptions, delegate: self)

Azure Communication Services

Connect to a call

The CommunicationUserIdentifier represents a user identity that was created using the Identity SDK or REST API. It's the only identifier used if your application doesn't use Microsoft Teams interoperability or Telephony features.

Initiating a call with the Azure Communication Service Calling SDK consists of the following steps:

  1. Creating a StartCallOptions object
  2. Creating an Array of CommunicationUserIdentifier
  3. Calling startCall method on the previously created CallAgent
let startCallOptions = StartCallOptions() // 1 
let callees = [CommunicationUserIdentifier(“<USER_ID>”)] // 2 
 
callAgent?.startCall(participants: callees, options: startCallOptions) { call, error in 
    // Check for error if no Error and the call object isn't nil then the call is being established       
}

Connect to a Team's call

With External Identity

Connecting to a team call is almost the same as connecting to a call. Instead of using StartCallOptions, the client application needs to use JoinCallOptions together with a TeamsMeeting locator.

The Teams meeting link can be retrieved using Graph APIs. You can read more about Graph APIs in the Graph documentation.

let joinCallOptions = JoinCallOptions()
let teamsMeetingLinkLocator = TeamsMeetingLinkLocator(meetingLink: meetingLink)
    callAgent?.join(with: teamsMeetingLinkLocator, joinCallOptions: joinCallOptions) { call, error in
    // Handle error or set a CallDelegate delegate on the call if no error
}

Accepting and joining a call

Twilio

Twilio Video uses the concept of a Room. Different clients can establish communication by joining the same room. So accept and join a call is not straight alternative.

Azure Communication Services

Receiving incoming call

To accept calls, the application must first be configured to receive incoming calls.

Register for push notifications and handling incoming push notification

A calling client can select to receive push notifications to receive incoming calls. This guide describes how to set up APNS for the Azure Communication Services Calling.

Setting up the CallAgentDelegate

The Azure Communication Services Calling SDK has a CallAgentDelegate that has a method that is called during an incoming call.

class ApplicationCallAgentDelegate: NSObject, CallAgentDelegate { 
 
    func callAgent(_ callAgent: CallAgent, didUpdateCalls args: CallsUpdatedEventArgs) {} 
 
    func callAgent(_ callAgent: CallAgent, didRecieveIncomingCall incomingCall: IncomingCall) { 
        // This is called when the application receives an incoming call 
        // An application could use this callback to  display an incoming call banner 
        // or report an incoming call to CallKit 
    } 
} 

In order to receive incoming calls, the application needs to add a CallAgentDelegate to its CallAgent.

let callAgentDelegate = ApplicationCallAgentDelegate() 
callClient.createCallAgent(userCredential: userCredential) { callAgent, error in 
    if error != nil { 
        // Raise the error to the user and return 
    } 
    self.callAgent = callAgent 
    self.callAgent?.delegate = callAgentDelegate 
} 

With the CallAgentDelegate in place, and associated with a CallAgent instance, the application should be able to receive incoming calls.

Accept an incoming call

func acceptCall(incomingCall: IncomingCall) { 
    let options = AcceptCallOptions() 
    incomingCall.accept(options: options) {(call, error) in 
        if error != nil { 
            // Raise the error to the user and return 
        } 
      // The call is established clients can speak/view each other 
    }
} 

Class reference

Class Name Description
CallAgentDelegate Defines a set of methods that are called by ACSCallAgent in response to important events.
IncomingCall Describes an incoming call

Video Stream

Starting and stopping video

Twilio

Accessing the camera

With Twilio Video, adding video to a call consists of two steps:

  1. Accessing the camera
  2. Adding the video track to the list of LocalVideoTrack
let options = CameraSourceOptions { (builder) in
    // Set the CameraSource options here
}
camera = CameraSource(options: options, delegate: aCameraDelegate)
Creating the LocalVideoTrack

Once the CameraSource is created, it can be associated to a LocalVideoTrack.

localVideoTrack = LocalVideoTrack(source: camera!, enabled: true, name: "Camera")
Adding the LocalVideoTrack to the call

At connect time . When a call connects, you can add the local video track to the call can be achieved by passing the local video track to the localVideo track list that can be set with ConnectOptions``.

let connectOptions = ConnectOptions(token: accessToken) { builder in
     builder.videoTracks =  [localVideoTrack!]
}

In an existing room. The local participant can publish a local video track via the publishVideoTrack(_ : LocalVideoTrack) method.

room.localParticipant.publishVideoTrack(localVideoTrack)

Azure Communication Services

Accessing the camera

Accessing the camera is done through the DeviceManager. Grabbing an instance of the DeviceManager can be done with the following code.

self.callClient.getDeviceManager { (deviceManager, error) in
    if (error != nil) {
       // Display an error message to the user and exit the closure 
       return
    }
    self.deviceManager = deviceManager                 
}
Creating the LocalVideoStream

The deviceManager provides access to cameras that can be used to create a LocalVideoStream instance.

LocalVideoStream(camera: deviceManager.cameras.first!)
Adding the LocalVideoStream

At connect time

The localVideoStream can be added to the streams via the OutgoingVideoOptions of the StartCallOptions.

var callOptions = StartCallOptions()
let outgoingVideoOptions = OutgoingVideoOptions()
outgoingVideoOptions.streams = [localVideoStream]

In a call

A video stream can be started by calling the startVideo method that takes a LocalVideoStream as a parameter.

call.startVideo(stream: localVideoStream) { error in
    if error != ni {
     // Report the error to the user and return 
    }
}

Class reference

Class Name Description
DeviceManager Facilitates the interaction with the device
LocalVideoStream Local video stream information
VideoDeviceInfo Information about a video device
Call Describes a call

Rendering video

Twilio

To render video using Twilio Video, an object conforming to the VideoRenderer protocol can be added to VideoTrack. The SDK provides a ready to use VideoRenderer called VideoView, which is a subclass of UIView.

let videoView = VideoView(frame: CGRect.zero, delegate: nil)

Once an instance of VideoView is created, a VideoTrack (local or remote) has a method addVideoRenderer that can be used to add the videoView created as a renderer.

localVideoTrack = LocalVideoTrack(source: camera, enabled: true, name: "Camera")
// Add renderer to video track for local preview
localVideoTrack!.addRenderer(videoView)

Azure Communication Services

To render video with Azure Communication Services Calling, create a VideoStreamRenderer and pass a LocalVideoStream or a RemoteVideoStream as parameter.

 let previewRenderer = try VideoStreamRenderer(localVideoStream: localVideoStream)

VideoStreamRenderer created can be used to create a VideoStreamRendererView, which renders the video stream passed to the VideoStreamRenderer.

let scalingMode = ScalingMode.fit
let options = CreateViewOptions(scalingMode:scalingMode)
let previewView = try previewRenderer.createView(withOptions:options)

Class reference

Class Name Description
ScalingMode Enum for local and remote video scaling mode
CreateViewOptions Options to be passed when rendering a Video
VideoStreamRenderer Renderer for video rendering
OutgoingVideoOptions Documentation isn't available yet
VideoStreamRendererView View used to render video

Audio Stream

Toggling the microphone

Twilio

Muting and unmuting of the microphone is done through the LocalAudioTrack associated with the microphone.

Muting the microphone

self.localAudioTrack.isEnabled =  false

Unmuting the microphone

self.localAudioTrack.isEnabled =  true

Azure Communication Services

Muting and unmuting can be done by calling the muteOutgoingAudio and unmuteoutgoingAudio on the Call object.

Muting the microphone

'call': call.muteOutgoingAudio() { error in
    if error == nil {
        isMuted = true
    } else {
       // Display an error to the user
    }
}

Unmuting the microphone

callBase.unmuteOutgoingAudio() { error in
    if error == nil {
        isMuted = true
    } else {
       // Display an error to the user
    }
}

Event Listeners

Twilio Video and Azure Communication Services Calling SDKs propose various delegates to listen to call events.

Room / Call Events

Twilio

The RoomDelegate allows clients to listen to events related to the room. It has methods that can be called for the following events:

  • The client has connected or fails to connect to a room
  • The client is reconnecting to the room or has reconnected
  • A remote participant as connected, disconnected, reconnected to the room
  • The room recording has started or stopped
  • The dominant speaker changes

Azure Communication Services

The Azure Communication Services Calling SDK enable the Call object to incorporate various event listeners, notifying them when a call property changes. Each event type should be subscribed to individually. To learn more about event handling, see the events tutorial.

  • The call state changed. This is where the connection event is reported
  • The list of remote participants has been updated
  • The local video stream has been updated
  • The mute state changed

Local Participant Events

Twilio

Twilio has a LocalParticpant delegate that allows client to receive updates about the following events:

  • The local participant has published or failed to be published a media track (audio, video, data)
  • The network quality level for the local participant changed

Azure Communication Services

Azure Communication Services Calling SDK has a CallAgent delegate that allows clients to listen to the call agent related event. It's notified when:

  • When calls are updated or created, if there's an incoming call or when an existing call is disconnected
  • When an incoming call is received

Remote Participant Events

Both SDKs, propose a remote participant delegate that allows clients to be notified about what is happening with each remote participant.

Twilio

The RemoteParticipantDelegate handles the following events.

  • The remote participant has published or unpublished a media track (video, audio, data)
  • The local participant has subscribed, failed to subscribe or unsubscribed to a remote media track (video, audio, data)
  • The remote participant network quality changed
  • The remote participant changed the priority of a track publication
  • The remote participant has switch on/off its video track

Azure Communication Services

The RemoteParticipantDelegate handles the following events.

  • The remote participant state changed
  • The remote participant is muted or not muted
  • The remote participant is speaking
  • The remote participant display name changed
  • The remote participant added or removed a video stream

Camera Events

Twilio

Twilio proposes a CameraSourceDelegate to notify client about the following events related to the camera:

  • The camera source has been interrupted or has been resumed (when you put the app in background for example)
  • The camera source failed
  • The camera source is reporting system pressure

Azure Communication Services

Azure Communication Services Calling proposes a DeviceManagerDelegate. It consists of a single method that will notify clients when video devices are added or removed on the current DeviceManager.

Class reference

Class Name Description
CallDelegate A set of methods that are called by calling SDK in response to important events.
CallAgentDelegate A set of methods that are called by ACSCallAgent in response to important events.
RemoteParticipantDelegate A set of methods that are called by ACSRemoteParticipant in response to important events.
DeviceManagerDelegate A set of methods that are called by ACSDeviceManager in response to important events.

Ending a Call

Twilio

Ending a call (disconnecting from a room) is done via the room.disconnect() method.

Azure Communication Services

Hanging up a call is done through the hangUp method of the Call object.

call.hangUp(options: HangUpOptions()) { error in
            if error != nil {
                print("ERROR: It was not possible to hang up the call.")
            }
            self.call = nil
 }

Class reference

Class Name Description
Call A set of methods that are called by ACSCall in response to important events.
HangUp Options A Property bag class for hanging up a call

More features from the Azure Communication Services Calling

Dominant speaker

The first step to register for dominant speaker update is to grab an instance of the dominant speaker feature from the Call object. Learn more about the dominant speaker configuration in the tutorial.

let dominantSpeakersFeature = call.feature(Features.dominantSpeakers)

Once an instance of the dominant speakers feature is obtained, a DominantSpeakersCallFeatureDelegate can be attached to it.

dominantSpeakersFeature.delegate = DominantSpeakersDelegate()
public class DominantSpeakersDelegate : DominantSpeakersCallFeatureDelegate {
    public func dominantSpeakersCallFeature(_ dominantSpeakersCallFeature: DominantSpeakersCallFeature, didChangeDominantSpeakers args: PropertyChangedEventArgs) {
        // When the list changes, get the timestamp of the last change and the current list of Dominant Speakers
        let dominantSpeakersInfo = dominantSpeakersCallFeature.dominantSpeakersInfo
        let timestamp = dominantSpeakersInfo.lastUpdatedAt
        let dominantSpeakersList = dominantSpeakersInfo.speakers
    }
}

Media Quality Statistics

To help you understand media quality during the call, Azure Communication Services SDK provides media quality statistics. Use it to examine the low-level audio, video, and screen-sharing quality metrics for incoming and outgoing call metrics.For more information, see the Media quality statistics guide.

User Facing Diagnostics

Azure Communication Services Calling SDK offers a feature known as User Facing Diagnostics (UFD), allowing clients to scrutinize diverse properties of a call to identify potential issues. To learn more about User Facing Diagnostics, see the User Facing Diagnostics.

Important

Some features of the Azure Communication Services Calling SDK described in the list don’t have an equivalent in the Twilio Video SDK.

Raise Hand

Raise Hand feature allows participants of a call raise or lower hands.

Video Background

Adding Video Background Allow users blur the background in the video stream.

Video spotlights

Spotlights Allow users pin and unpin videos.

Prerequisites

  1. Azure Account: Make sure that your Azure account is active. New users can create a free account at Microsoft Azure.
  2. Communication Services Resource: Set up a Communication Services Resource via your Azure portal and note your connection string.
  3. Azure CLI: Follow the instructions to Install Azure CLI on Windows.
  4. User Access Token: Generate a user access token to instantiate the call client. You can create one using the Azure CLI as follows:
az communication identity token issue --scope voip --connection-string "yourConnectionString"

For more information, see Use Azure CLI to Create and Manage Access Tokens.

For Video Calling as a Teams user:

UI Library

The Azure Communication Services UI library simplifies the process of creating modern communication user interfaces using Azure Communication Services Calling. It offers a collection of ready-to-use UI components that you can easily integrate into your application.

This open source prebuilt set of controls enables you to create aesthetically pleasing designs using Fluent UI SDK components and develop high quality audio/video communication experiences. For more information, check out the Azure Communications Services UI Library overview. The overview includes comprehensive information about both web and mobile platforms.

Installation

To start the migration from Twilio Video, the first step is to install the Azure Communication Services Calling SDK for Android to your project. The Azure Communication Services Calling SDK can be integrated as a gradle dependency.

  1. Add the azure-communication-calling package
dependencies {
     ...
    implementation "com.azure.android:azure-communication-calling:<version>"
}
  1. Check permissions in application manifest

Ensure that your application's manifest file contains the necessary permissions, and make the required adjustments.

<manifest >
   <uses-feature android:name="android.hardware.camera" /> 
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

Authenticating to the SDK

To be able to use the Azure Communication Services Calling SDK, you need to authenticate using an access token.

Twilio

The following code snippets presume the availability of a valid access token for Twilio Services.

From within the Twilio Video, the access token is used to connect to a room. By passing the token to ConnectOptions, you can create the option to create or connect a room.

ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken).build();
room = Video.connect(context, connectOptions, roomListener);

Azure Communication Services

The following code snippets require a valid access token to initiate a CallClient.

You need a valid token. For more information, see Create and Manage Access Tokens.

String userToken = "<USER_TOKEN>";
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(accessToken);

CallClient callClient = new CallClient();
callAgent = callClient.createCallAgent(getApplicationContext(), tokenCredential).get();

Class reference

Class Name Description
CallClient The class serving as the entry point for the Calling SDK.
CommunicationTokenCredential The Azure Communication Services User token credential
CallAgent The class responsible for managing calls on behalf of the authenticated user

Initiating an outgoing call

Twilio

Twilio Video has a concept of Room, where if user Bob wants to have a call with client Alice, Bob can create a room and Alice has to connect to it by implementing a feature like push notification.

When user Bob or user Alice wants to create or connect to a room, and they have a valid access token. They can pass the room name they want to create or connect to as a parameter of ConnectOptions.

ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .build()
room = Video.connect(context, connectOptions, roomListener);

Azure Communication Services

Connect to a call

Initiating a call with the Azure Communication Service Calling SDK consists of the following steps:

The CommunicationUserIdentifier represents a user identity that was created using the Identity SDK or REST API. It's the only identifier used if your application doesn't use Microsoft Teams interoperability or Telephony features.

  1. Creating an Array of CommunicationUserIdentifier
  2. Calling startCall method on the previously created CallAgent
ArrayList<CommunicationIdentifier> userCallee = new ArrayList<>();
participants.add(new CommunicationUserIdentifier(“<USER_ID>”));

Call call = callAgent.startCall(context, userCallee);

Connect to a Teams call

With External Identity

Connecting to a Teams call is almost identical to connecting to a call. Instead of using StartCallOptions, the client application uses JoinCallOptions with a TeamsMeetingLocator.

The Teams meeting link can be retrieved using Graph APIs. The retrieval process is detailed in the graph documentation.

JoinCallOptions options = new JoinCallOptions();
TeamsMeetingLinkLocator teamsMeetingLinkLocator = new TeamsMeetingLinkLocator(meetingLink);
Call call = callAgent.join(getApplicationContext(), teamsMeetingLinkLocator, joinCallOptions);

Accepting and joining a call

Twilio

Twilio Video uses the concept of a Room. Different clients can establish communication by joining the same room. So accept and join a call is not straight alternative.

Azure Communication Services

Receiving incoming call

To accept calls, the application must first be configured to receive incoming calls.

Register for push notifications and handle incoming push notification

A calling client can select to receive push notifications to receive incoming calls. This guide describes how to set up APNS for the Azure Communication Services Calling.

Setting up the CallAgentListener

The Azure Communication Services Calling SDK includes an IncomingCallListener. An IncomingCallListener is set on the CallAgent instance. This listener defines an onIncomingCall(IncomingCall incomingCall) method, which is triggered upon the arrival of an incoming call.

callAgent.addOnIncomingCallListener((incomingCall) -> {
     this.incomingCall = incomingCall;

     // Get incoming call ID  
     incomingCall.getId();

     // Get information about caller
     incomingCall.getCallerInfo();

     // CallEndReason is also a property of IncomingCall
      CallEndReason callEndReason = incomingCall.getCallEndReason();
});

Implementing the CallAgentListener and associating it with a CallAgent instance, the application is ready to receive incoming calls.

Accept incoming call

incomingCall.accept(context);

Class reference

Class Name Description
IncomingCallListener Functional interface for incoming calls.
IncomingCall Describes an incoming call

Video Stream

Starting and Stopping Video

Twilio

Accessing the camera

With Twilio Video, adding video to a call consists of two steps:

  1. Accessing the camera
  2. Adding the video track to the list of LocalVideoTrack
 // Access the camera
CameraCapturer cameraCapturer = new Camera2Capturer(context, frontCameraId);

  // Create a video track
LocalVideoTrack videoTrack = LocalVideoTrack.create(context, true, cameraCapturer, LOCAL_VIDEO_TRACK_NAME);

  // The VideoTrack is enabled by default. It can be enabled or disabled if necessary
videoTrack.enable(true|false);

Adding the LocalVideoTrack

At connect time . Adding a local video track is done by passing the LocalVideoTrack to the LocalVideoTrack list that is set via ConnectOptions.

 ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .videoTracks(localVideoTracks)
}

In an existing room, the local participant can publish a local video track via the publishTrack(LocalVideoTrack localVideoTrack) method.

room.localParticipant.publishVideoTrack(localVideoTrack)

Azure Communication Services

Accessing the camera

Accessing the camera is done through the DeviceManager. Obtain an instance of the DeviceManager using the following code snippet.

DeviceManager deviceManager = callClient.getDeviceManager(getApplicationContext()).get();

deviceManager.getCameras();
Creating the LocalVideoStream

The DeviceManager provides access to camera objects that allow the creation of a LocalVideoStream instance.

VideoDeviceInfo camera = deviceManager.getCameras().get(0);
LocalVideoStream videoStream = new LocalVideoStream(camera, context);
Adding the LocalVideoStream

At connect time. The LocalVideoStream is added to the streams via the OutgoingVideoOptions of the StartCallOptions.

    StartCallOptions options = new StartCallOptions();
    LocalVideoStream[] videoStreams = new LocalVideoStream[1];
    videoStreams[0] = videoStream;
    VideoOptions videoOptions = new VideoOptions(videoStreams);
    options.setVideoOptions(videoOptions);

In a call. Initiate a video stream by invoking the startVideo method, which accepts a LocalVideoStream as its parameter.

call.startVideo(context, videoStream).get();

Class reference

Class Name Description
DeviceManager Facilitates the interaction with the device
LocalVideoStream Local video stream information
VideoDeviceInfo Information about a video device
VideoOptions Property bag class for Video Options
Call Describes a call

Rendering video

Twilio

To render video using Twilio Video, an object conforming to the VideoSink can be added to VideoTrack. The SDK provides a prebuilt VideoSink called VideoView, which subclasses android.view.View.

videoTrack.addSink(videoVideoView);

Azure Communication Services

To render video with Azure Communication Services Calling, instantiate a VideoStreamRenderer and pass a LocalVideoStream or a RemoteVideoStream as a parameter to its constructor.

VideoStreamRenderer previewRenderer = new VideoStreamRenderer(remoteStream, context);
VideoStreamRendererView preview = previewRenderer.createView(new CreateViewOptions(ScalingMode.FIT));

Class reference

Class Name Description
ScalingMode Enum for local and remote video scaling mode
CreateViewOptions Options to be passed when rendering a Video
VideoStreamRenderer Renderer for video rendering
VideoStreamRendererView View used to render video

Audio Stream

Toggling the microphone

Twilio

On the Twilio Video SDK, muting and unmuting the microphone is achieved by enabling or disabling the LocalAudioTrack associated with the microphone.

localAudioTrack.enable(true|false);

Azure Communication Services

The Call object proposes methods for muting and unmuting the microphone.

call.muteOutgoingAudio(context).get();
call.unmuteOutgoingAudio(context).get();

// Mute incoming audio sets the call volume to 0. To mute or unmute the incoming audio, use the muteIncomingAudio and unmuteIncomingAudio asynchronous APIs

call.muteIncomingAudio(context).get();
call.unmuteIncomingAudio(context).get();

Event Listeners

Twilio Video and Azure Communication Services Calling SDKs propose various listeners to listen to call events.

Room / Call Events

Twilio

The Room.Listener allows clients to listen to events related to the Room object. The Room.Listener includes methods that are triggered for the following events:

  • The client connected or failed to connect to a room
  • The client is reconnecting to the room or reconnected
  • A remote participant connected, disconnected, reconnected to the room
  • The room recording started or stopped
  • The dominant speaker changed

Azure Communication Services

The Azure Communication Services Calling SDK enable the Call object to incorporate various PropertyChangedListener, notifying them when a call property changes. Each event type should be subscribed to individually. To learn more about event handling, see the events tutorial.

The various PropertyChangedListeners that can be assigned to a call encompass certain events covered by the Twilio Room.Listener, featuring methods for the following events:

  • The call state changed
  • The list of remote participants updated
  • The local video stream updated
  • The mute state changed

Local Participant Events

Twilio

Twilio has a LocalParticipant.Listener that allows clients to receive updates about the following events:

  • The local participant published or failed to publish a media track (audio, video, data).
  • The network quality level for the local participant changed.

Azure Communication Services

The CallAgent receives updates regarding calls through two listeners: CallsUpdatedListener and the IncomingCallListener. These listeners are triggered respectively for the following events:

  • Calls are updated. A new call is created or an existing call is disconnected.
  • An incoming call is received.

Remote Participant Events

Both SDKs offer mechanisms to handle updates from remote participants.

Twilio

The RemoteParticipant.Listener handles the following events.

  • The remote participant published or unpublished a media track (video, audio, data)
  • The local participant subscribed, failed to subscribe, or unsubscribed to a remote media track (video, audio, data)
  • The remote participant network quality changed
  • The remote participant changed the priority of a track publication
  • The remote participant switched on/off its video track

Azure Communication Services

Add a PropertyChangedListener to the RemoteParticipant object to receive updates for the following events:

  • The remote participant state changed
  • The remote participant is muted or not muted
  • The remote participant is speaking
  • The remote participant display name changed
  • The remote participant added or removed a video stream

Camera Events

Twilio

Twilio proposes a CameraCapturer.Listener to notify client about the following events related to the camera:

  • The camera source was switched
  • The camera source failed
  • The first frame has been captured from the camera

Azure Communication Services

Azure Communication Services Calling SDK proposes a VideoDevicesUpdatedListener. It defines a single method to notify clients when video devices are added or removed on the current DeviceManager.

Class reference

Class Name Description
PropertyChangedListener Informs the library that the call state changes
CallsUpdatedListener Informs the library when the calls are updated
IncomingCallListener Informs the library about incoming call
VideoDevicesUpdatedListener Informs the library that new video devices were added or removed to the current library

Ending a Call

Twilio

Ending a call (disconnecting from a room) is done via the room.disconnect() method.

room.disconnect();

Azure Communication Services

Hanging up a call is done through the hangUp method of the Call object.

call.hangUp().get();

// Set the 'forEveryone' property to true to end call for all participants
HangUpOptions options = new HangUpOptions();
options.setForEveryone(true);
call.hangUp(options).get();

Class reference

Class Name Description
HangUp Options Property bag class for hanging up a call

More features from the Azure Communication Services Calling

Dominant speaker

To register for updates about the dominant speaker, instantiate the DominantSpeakersCallFeature from the Call object. Learn more about the dominant speaker configuration in the tutorial.

DominantSpeakersCallFeature dominantSpeakersFeature = call.feature(Features.DOMINANT_SPEAKERS);

// Subscribe to the dominant speaker change event to receive updates about the dominant speaker.

dominantSpeakersFeature.addOnDominantSpeakersChangedListener(event -> {
       dominantSpeakersFeature.getDominantSpeakersInfo();
});

Media Quality Statistics

To help you understand media quality during the call, Azure Communication Services SDK provides media quality statistics. Use it to examine the low-level audio, video, and screen-sharing quality metrics for incoming and outgoing call metrics.For more information, see the Media quality statistics guide.

User Facing Diagnostics

Azure Communication Services Calling SDK offers a feature known as User Facing Diagnostics (UFD), allowing clients to scrutinize diverse properties of a call to identify potential issues. To learn more about User Facing Diagnostics, see the User Facing Diagnostics.

Important

Some features of the Azure Communication Services Calling SDK described in the list don’t have an equivalent in the Twilio Video SDK.

Raise Hand

Raise Hand feature allows participants of a call to raise or lower hands.

Video Background

Adding Video Background Allow users to blur the background in the video stream.

Video spotlights

Spotlights Allow users to pin and unpin videos.