Azure Communication Calling client library for JavaScript
Get started with Azure Communication Services by using the Communication Services calling client library to add voice and video calling to your app. Read more about Azure Communication Services here
Prerequisites
- An Azure account with an active subscription. Create an account for free.
- A deployed Communication Services resource. Create a Communication Services resource.
- A user access token to enable the calling client. For more information, see Create and manage access tokens
- Optional: Complete the quickstart to add voice calling to your application
Install the SDK
Use the npm install
command to install the Azure Communication Services calling and common SDKs for JavaScript.
npm install @azure/communication-common --save
npm install @azure/communication-calling --save
The Communication Services Web Calling SDK must be used through https
. For local development, use localhost
or local 'file:'
Documentation support
Models
Object model
The following classes and interfaces handle some of the major 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 callAgent . |
CallAgent |
Used to start and manage calls. |
DeviceManager |
Used to manage media devices. |
Call |
Used for representing a Call |
LocalVideoStream |
Used for creating a local video stream for a camera device on the local system. |
RemoteParticipant |
Used for representing a remote participant in the Call |
RemoteVideoStream |
Used for representing a remote video stream from a Remote Participant. |
[!NOTE] The Calling SDK object instances shouldn't be considered to be a plain JavaScript objects. These are actual instances of various classes and therefore can't be serialized.
Events model
Each object in the calling sdk, has properties and collections values of which, 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 anadded
array that contains values that were added to the collection. - The
'<collection>Updated'
event's payload also has aremoved
array that contains values that were removed from the collection.
/************************************************
* Example quickstart code - client.js *
* Convert this script into a *
* bundle.js that your html index *
* page can use. *
************************************************/
// Make sure to install the necessary dependencies
const { CallClient, VideoStreamRenderer, LocalVideoStream } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential } = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the log level and output
setLogLevel('verbose');
AzureLogger.log = (...args) => {
console.log(...args);
};
// Calling web sdk objects
let callAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let calleeAcsUserId = document.getElementById('callee-acs-user-id');
let initializeCallAgentButton = document.getElementById('initialize-call-agent');
let startCallButton = document.getElementById('start-call-button');
let acceptCallButton = document.getElementById('accept-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideoContainer = document.getElementById('remoteVideoContainer');
let localVideoContainer = document.getElementById('localVideoContainer');
// Instantiate the Call Agent.
initializeCallAgentButton.onclick = async () => {
try {
const callClient = new CallClient();
tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
callAgent = await callClient.createCallAgent(tokenCredential)
// Set up a camera device to use.
deviceManager = await callClient.getDeviceManager();
await deviceManager.askDevicePermission({ video: true });
await deviceManager.askDevicePermission({ audio: true });
// Listen for an incoming call to accept.
callAgent.on('incomingCall', async (args) => {
try {
incomingCall = args.incomingCall;
acceptCallButton.disabled = false;
startCallButton.disabled = true;
} catch (error) {
console.error(error);
}
});
startCallButton.disabled = false;
initializeCallAgentButton.disabled = true;
} catch(error) {
console.error(error);
}
}
// Start an out-going call.
startCallButton.onclick = async () => {
try {
const localVideoStream = await createLocalVideoStream();
const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
call = callAgent.startCall([{ communicationUserId: calleeAcsUserId.value.trim() }], { videoOptions });
// Subscribe to the call's properties and events.
subscribeToCall(call);
} catch (error) {
console.error(error);
}
}
// Accept the incoming call.
acceptCallButton.onclick = async () => {
try {
const localVideoStream = await createLocalVideoStream();
const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
call = await incomingCall.accept({ videoOptions });
// Subscribe to the call's properties and events.
subscribeToCall(call);
} catch (error) {
console.error(error);
}
}
// Subscribe to a call obj.
// Listen for property changes and collection udpates.
subscribeToCall = (call) => {
try {
// Inspect the initial call.id value.
console.log(`Call Id: ${call.id}`);
//Subsribe to call's 'idChanged' event for value changes.
call.on('idChanged', () => {
console.log(`Call Id changed: ${call.id}`);
});
// Inspect the initial call.state value.
console.log(`Call state: ${call.state}`);
// Subscribe to call's 'stateChanged' event for value changes.
call.on('stateChanged', async () => {
console.log(`Call state changed: ${call.state}`);
if(call.state === 'Connected') {
connectedLabel.hidden = false;
acceptCallButton.disabled = true;
startCallButton.disabled = true;
startVideoButton.disabled = false;
stopVideoButton.disabled = false
} else if (call.state === 'Disconnected') {
connectedLabel.hidden = true;
startCallButton.disabled = false;
console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
}
});
call.localVideoStreams.forEach(async (lvs) => {
localVideoStream = lvs;
await displayLocalVideoStream();
});
call.on('localVideoStreamsUpdated', e => {
e.added.forEach(async (lvs) => {
localVideoStream = lvs;
await displayLocalVideoStream();
});
e.removed.forEach(lvs => {
removeLocalVideoStream();
});
});
// Inspect the call's current remote participants and subscribe to them.
call.remoteParticipants.forEach(remoteParticipant => {
subscribeToRemoteParticipant(remoteParticipant);
})
// 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 => {
// Subscribe to new remote participants that are added to the call.
e.added.forEach(remoteParticipant => {
subscribeToRemoteParticipant(remoteParticipant)
});
// Unsubscribe from participants that are removed from the call
e.removed.forEach(remoteParticipant => {
console.log('Remote participant removed from the call.');
})
});
} catch (error) {
console.error(error);
}
}
// Subscribe to a remote participant obj.
// Listen for property changes and collection udpates.
subscribeToRemoteParticipant = (remoteParticipant) => {
try {
// Inspect the initial remoteParticipant.state value.
console.log(`Remote participant state: ${remoteParticipant.state}`);
// Subscribe to remoteParticipant's 'stateChanged' event for value changes.
remoteParticipant.on('stateChanged', () => {
console.log(`Remote participant state changed: ${remoteParticipant.state}`);
});
// Inspect the remoteParticipants's current videoStreams and subscribe to them.
remoteParticipant.videoStreams.forEach(remoteVideoStream => {
subscribeToRemoteVideoStream(remoteVideoStream)
});
// Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
// notified when the remoteParticiapant adds new videoStreams and removes video streams.
remoteParticipant.on('videoStreamsUpdated', e => {
// Subscribe to new remote participant's video streams that were added.
e.added.forEach(remoteVideoStream => {
subscribeToRemoteVideoStream(remoteVideoStream)
});
// Unsubscribe from remote participant's video streams that were removed.
e.removed.forEach(remoteVideoStream => {
console.log('Remote participant video stream was removed.');
})
});
} catch (error) {
console.error(error);
}
}
// Subscribe to a remote participant's remote video stream obj.
// Listen for property changes and collection udpates.
// When their remote video streams become available, display them in the UI.
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
// Create a video stream renderer for the remote video stream.
let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
let view;
const renderVideo = async () => {
try {
// Create a renderer view for the remote video stream.
view = await videoStreamRenderer.createView();
// Attach the renderer view to the UI.
remoteVideoContainer.hidden = false;
remoteVideoContainer.appendChild(view.target);
} catch (e) {
console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
}
}
remoteVideoStream.on('isAvailableChanged', async () => {
// Participant has switched video on.
if (remoteVideoStream.isAvailable) {
await renderVideo();
// Participant has switched video off.
} else {
if (view) {
view.dispose();
view = undefined;
}
}
});
// Participant has video on initially.
if (remoteVideoStream.isAvailable) {
await renderVideo();
}
}
// Start your local video stream.
// This will send your local video stream to remote participants so they can view it.
startVideoButton.onclick = async () => {
try {
const localVideoStream = await createLocalVideoStream();
await call.startVideo(localVideoStream);
} catch (error) {
console.error(error);
}
}
// Stop your local video stream.
// This will stop your local video stream from being sent to remote participants.
stopVideoButton.onclick = async () => {
try {
await call.stopVideo(localVideoStream);
} catch (error) {
console.error(error);
}
}
// Create a local video stream for your camera device
createLocalVideoStream = async () => {
const camera = (await deviceManager.getCameras())[0];
if (camera) {
return new LocalVideoStream(camera);
} else {
console.error(`No camera device found on the system`);
}
}
// Display your local video stream preview in your UI
displayLocalVideoStream = async () => {
try {
localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
const view = await localVideoStreamRenderer.createView();
localVideoContainer.hidden = false;
localVideoContainer.appendChild(view.target);
} catch (error) {
console.error(error);
}
}
// Remove your local video stream preview from your UI
removeLocalVideoStream = async() => {
try {
localVideoStreamRenderer.dispose();
localVideoContainer.hidden = true;
} catch (error) {
console.error(error);
}
}
An HTML example code that can use a bundle generated from the above JavaScript example (client.js
).
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Azure Communication Services - Calling Web SDK</title>
</head>
<body>
<h4>Azure Communication Services - Calling Web SDK</h4>
<input id="user-access-token"
type="text"
placeholder="User access token"
style="margin-bottom:1em; width: 500px;"/>
<button id="initialize-call-agent" type="button">Initialize Call Agent</button>
<br>
<br>
<input id="callee-acs-user-id"
type="text"
placeholder="Enter callee's ACS user identity in format: '8:acs:resourceId_userId'"
style="margin-bottom:1em; width: 500px; display: block;"/>
<button id="start-call-button" type="button" disabled="true">Start Call</button>
<button id="accept-call-button" type="button" disabled="true">Accept Call</button>
<button id="start-video-button" type="button" disabled="true">Start Video</button>
<button id="stop-video-button" type="button" disabled="true">Stop Video</button>
<br>
<br>
<div id="connectedLabel" style="color: #13bb13;" hidden>Call is connected!</div>
<br>
<div id="remoteVideoContainer" style="width: 40%;" hidden>Remote participants' video streams:</div>
<br>
<div id="localVideoContainer" style="width: 30%;" hidden>Local video stream:</div>
<!-- points to the bundle generated from client.js -->
<script src="./bundle.js"></script>
</body>
</html>
Note that after starting call, joining call, or accepting call, you can also use the callAgent's 'callsUpdated' event to be notified of the new Call object and start subscribing to it.
callAgent.on('callsUpdated', e => {
// New Call object is added to callAgent.calls collection
e.added.forEach(call => {
call.remoteParticipants.forEach(remoteParticipant => {
subscribeToRemoteParticipant(remoteParticipant);
})
call.on('remoteParticipantsUpdated', e => {
e.added.forEach(remoteParticipant => {
subscribeToRemoteParticipant(remoteParticipant)
});
e.removed.forEach(remoteParticipant => {
console.log('Remote participant removed from the call.');
})
});
})
})
Initialize a CallClient instance, create a CallAgent instance, and access deviceManager
Create a new CallClient
instance. You can configure it with custom options like a Logger instance.
When you have a CallClient
instance, you can create a CallAgent
instance by calling the createCallAgent
method on the CallClient
instance. This method asynchronously returns a CallAgent
instance object.
The createCallAgent
method uses CommunicationTokenCredential
as an argument. It accepts a user access token.
You can use the getDeviceManager
method on the CallClient
instance to access deviceManager
.
const { CallClient } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential} = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the logger's log level
setLogLevel('verbose');
// Redirect log output to wherever desired. To console, file, buffer, REST API, etc...
AzureLogger.log = (...args) => {
console.log(...args); // Redirect log output to console
};
const userToken = '<USER_TOKEN>';
callClient = new CallClient(options);
const tokenCredential = new AzureCommunicationTokenCredential(userToken);
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional ACS user name'});
/***
* Display name field maximum char length is 256.
***/
const deviceManager = await callClient.getDeviceManager()
Place a call
To create and start a call, use one of the APIs on callAgent
and provide a user that you've created through the Communication Services identity SDK.
Call creation and start are synchronous. The call
instance allows you to subscribe to call events.
Place a 1:n call to a user or PSTN
To call another Communication Services user, use the startCall
method on callAgent
and pass the recipient's CommunicationUserIdentifier
that you created with the Communication Services administration library.
For a 1:1 call to a user, use the following code:
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const oneToOneCall = callAgent.startCall([userCallee]);
To place a call to a public switched telephone network (PSTN), use the startCall
method on callAgent
and pass the recipient's PhoneNumberIdentifier
. Your Communication Services resource must be configured to allow PSTN calling.
When you call a PSTN number, specify your alternate caller ID. An alternate caller ID is a phone number (based on the E.164 standard) that identifies the caller in a PSTN call. It's the phone number the call recipient sees for an incoming call.
[!NOTE] PSTN calling is currently in private preview. For access, apply to the early adopter program.
For a 1:1 call to a PSTN number, use the following code:
const pstnCalee = { phoneNumber: '<ACS_USER_ID>' }
const alternateCallerId = {alternateCallerId: '<ALTERNATE_CALLER_ID>'};
const oneToOneCall = callAgent.startCall([pstnCallee], {alternateCallerId});
For a 1:n call to a user and a PSTN number, use the following code:
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const pstnCallee = { phoneNumber: '<PHONE_NUMBER>'};
const alternateCallerId = {alternateCallerId: '<ALTERNATE_CALLER_ID>'};
const groupCall = callAgent.startCall([userCallee, pstnCallee], {alternateCallerId});
Place a 1:1 call with video camera
[!IMPORTANT] There can currently be no more than one outgoing local video stream.
To place a video call, you have to enumerate local cameras by using the getCameras()
method in deviceManager
.
After you select a camera, use it to construct a LocalVideoStream
instance. Pass it within videoOptions
as an item within the localVideoStream
array to the startCall
method.
const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
localVideoStream = new LocalVideoStream(camera);
const placeCallOptions = {videoOptions: {localVideoStreams:[localVideoStream]}};
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const call = callAgent.startCall([userCallee], placeCallOptions);
- When your call connects, it automatically starts sending a video stream from the selected camera to the other participant. This also applies to the
Call.Accept()
video options andCallAgent.join()
video options.
Join a group call
[!NOTE] The
groupId
parameter is considered system metadata and may be used by Microsoft for operations that are required to run the system. Don't include personal data in thegroupId
value. Microsoft doesn't treat this parameter as personal data and its content may be visible to Microsoft employees or stored long-term.The
groupId
parameter requires data to be in GUID format. We recommend using randomly generated GUIDs that aren't considered personal data in your systems.
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 has to be a GUID.
const context = { groupId: '<GUID>'}
const call = callAgent.join(context);
Receive an incoming call
The callAgent
instance emits an incomingCall
event when the logged-in identity receives an incoming call. To listen to this event, subscribe by using one of these options:
const incomingCallHander = async (args: { incomingCall: IncomingCall }) => {
const incomingCall = args.incomingCall;
// Get incoming call ID
var incomingCallId = incomingCall.id
// Get information about this Call. This API is provided as a preview for developers
// and may change based on feedback that we receive. Do not use this API in a production environment.
// To use this api please use 'beta' release of ACS Calling Web SDK
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', incomingCallHander);
The incomingCall
event includes an incomingCall
instance that you can accept or reject.
When starting/joining/accepting a call with video on, if the specified video camera device is being used by another process or if it is disabled in the system, the call will start with video off, and a cameraStartFailed: true call diagnostic will be raised.
See Call Diagnostics section to see how to handle this call diagnostic.
Manage calls
During a call, you can access call properties and manage video and audio settings.
Check call properties
Get the unique ID (string) for a call:
const callId: string = call.id;
Learn about other participants in the call by inspecting the remoteParticipants
collection on the 'call' instance:
const remoteParticipants = call.remoteParticipants;
Identify the caller of an incoming call:
const callerIdentity = call.callerInfo.identifier;
identifier
is one of the CommunicationIdentifier
types.
Get the state of a call:
const callState = call.state;
This returns a string representing the current state of a call:
-
None
: Initial call state. -
Connecting
: Initial transition state when a call is placed or accepted. -
Ringing
: For an outgoing call, indicates that a call is ringing for remote participants. It isIncoming
on their side. -
EarlyMedia
: Indicates a state in which an announcement is played before the call is connected. -
Connected
: Indicates that the call is connected. -
LocalHold
: Indicates that the call is put on hold by a local participant. No media is flowing between the local endpoint and remote participants. -
RemoteHold
: Indicates that the call was put on hold by remote participant. No media is flowing between the local endpoint and remote participants. -
InLobby
: Indicates that user is in lobby. -
Disconnecting
: Transition state before the call goes to aDisconnected
state. -
Disconnected
: Final call state. If the network connection is lost, the state changes toDisconnected
after two minutes.
Find out why a call ended by inspecting the callEndReason
property:
const callEndReason = call.callEndReason;
const callEndReasonCode = callEndReason.code // (number) code associated with the reason
const callEndReasonSubCode = callEndReason.subCode // (number) subCode associated with the reason
Learn if the current call is incoming or outgoing by inspecting the direction
property. It returns CallDirection
.
const isIncoming = call.direction == 'Incoming';
const isOutgoing = call.direction == 'Outgoing';
Check if the current microphone is muted. It returns Boolean
.
const muted = call.isMuted;
Find out if the screen sharing stream is being sent from a given endpoint by checking the isScreenSharingOn
property. It returns Boolean
.
const isScreenSharingOn = call.isScreenSharingOn;
Inspect active video streams by checking the localVideoStreams
collection. It returns LocalVideoStream
objects.
const localVideoStreams = call.localVideoStreams;
Mute and unmute
To mute or unmute the local endpoint, you can use the mute
and unmute
asynchronous APIs:
//mute local device
await call.mute();
//unmute local device
await call.unmute();
Start and stop sending local video
To start a video, you have to enumerate cameras using the getCameras
method on the deviceManager
object. Then create a new instance of LocalVideoStream
with the desired camera and then pass the LocalVideoStream
object into the startVideo
method:
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 is added to the localVideoStreams
collection on a call instance.
call.localVideoStreams[0] === localVideoStream;
To stop local video, pass the localVideoStream
instance that's available in the localVideoStreams
collection:
await call.stopVideo(localVideoStream);
// or
await call.stopVideo(call.localVideoStreams[0]);
You can switch to a different camera device while a video is sending by invoking switchSource
on a localVideoStream
instance:
const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);
Notes
- If the specified video device is being used by another process, or if it is disabled in the system (See Call Diagnostics section to see how to handle call diagnostics.):
- While in a call, if your video is off and you start video using the call.startVideo() api, this API will throw with a SourceUnavailableError and a cameraStartFiled: true call diagnostic will be raised.
- A call to the localVideoStream.switchSource() api will cause a cameraStartFailed: true call diagnostic to be raised.
- There are 4 apis in which you can pass a localVideoStream instance to start video in a call, callAgent.startCall() api, callAgent.join() api, call.accept() api, and call.startVideo() api. To the call.stopVideo() api, you must pass that same localVideoStream instance that you passed in those 4 apis.
Manage remote participants
All remote participants are represented by RemoteParticipant
type and available through remoteParticipants
collection on a call instance.
List the participants in a call
The remoteParticipants
collection returns a list of remote participants in a call:
call.remoteParticipants; // [remoteParticipant, remoteParticipant....]
Access remote participant properties
Remote participants have a set of associated properties and collections:
-
CommunicationIdentifier
: Get the identifier for a remote participant. Identity is one of theCommunicationIdentifier
types:const identifier = remoteParticipant.identifier;
It can be one of the following
CommunicationIdentifier
types:-
{ communicationUserId: '<ACS_USER_ID'> }
: Object representing the ACS user. -
{ phoneNumber: '<E.164>' }
: Object representing the phone number in E.164 format. -
{ microsoftTeamsUserId: '<TEAMS_USER_ID>', isAnonymous?: boolean; cloud?: "public" | "dod" | "gcch" }
: Object representing the Teams user. -
{ id: string }
: object representing identifier that doesn't fit any of the other identifier types
-
-
state
: Get the state of a remote participant.const state = remoteParticipant.state;
The state can be:
-
Idle
: Initial state. -
Connecting
: Transition state while a participant is connecting to the call. -
Ringing
: Participant is ringing. -
Connected
: Participant is connected to the call. -
Hold
: Participant is on hold. -
EarlyMedia
: Announcement that plays before a participant connects to the call. -
InLobby
: Indicates that remote participant is in lobby. -
Disconnected
: Final state. The participant is disconnected from the call. If the remote participant loses their network connectivity, their state changes toDisconnected
after two minutes.
-
-
callEndReason
: To learn why a participant left the call, check thecallEndReason
property:const callEndReason = remoteParticipant.callEndReason; const callEndReasonCode = callEndReason.code // (number) code associated with the reason const callEndReasonSubCode = callEndReason.subCode // (number) subCode associated with the reason
-
isMuted
status: To find out if a remote participant is muted, check theisMuted
property. It returnsBoolean
.const isMuted = remoteParticipant.isMuted;
-
isSpeaking
status: To find out if a remote participant is speaking, check theisSpeaking
property. It returnsBoolean
.const isSpeaking = remoteParticipant.isSpeaking;
-
videoStreams
: To inspect all video streams that a given participant is sending in this call, check thevideoStreams
collection. It containsRemoteVideoStream
objects.const videoStreams = remoteParticipant.videoStreams; // [RemoteVideoStream, ...]
-
displayName
: To get display name for this remote participant, inspectdisplayName
property it return string.const displayName = remoteParticipant.displayName;
Add a participant to a call
To add a participant (either a user or a phone number) 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: '<ACS_USER_ID>' };
const pstnIdentifier = { phoneNumber: '<PHONE_NUMBER>' }
const remoteParticipant = call.addParticipant(userIdentifier);
const remoteParticipant = call.addParticipant(pstnIdentifier, {alternateCallerId: '<ALTERNATE_CALLER_ID>'});
Remove a participant from a call
To remove a participant (either a user or a phone number) from a call, you can invoke removeParticipant
. You have 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: '<ACS_USER_ID>' };
const pstnIdentifier = { phoneNumber: '<PHONE_NUMBER>' }
await call.removeParticipant(userIdentifier);
await call.removeParticipant(pstnIdentifier);
Render remote participant video streams
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 have to subscribe to it's 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 choose to destroy the whole VideoStreamRenderer
, a specific VideoStreamRendererView
or keep them, but this will result in displaying blank video frame.
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
// Create a video stream renderer for the remote video stream.
let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
let view;
const remoteVideoContainer = document.getElementById('remoteVideoContainer');
const renderVideo = async () => {
try {
// Create a renderer view for the remote video stream.
view = await videoStreamRenderer.createView();
// Attach the renderer view to the UI.
remoteVideoContainer.appendChild(view.target);
} catch (e) {
console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
}
}
remoteVideoStream.on('isAvailableChanged', async () => {
// Participant has switched video on.
if (remoteVideoStream.isAvailable) {
await renderVideo();
// Participant has switched video off.
} else {
if (view) {
view.dispose();
view = undefined;
}
}
});
// Participant has video on initially.
if (remoteVideoStream.isAvailable) {
await renderVideo();
}
}
Remote video stream properties
Remote video streams have the following properties:
-
id
: The ID of a remote video stream.const id: number = remoteVideoStream.id;
-
mediaStreamType
: Can beVideo
orScreenSharing
.const type: MediaStreamType = remoteVideoStream.mediaStreamType;
-
isAvailable
: Whether a remote participant endpoint is actively sending a stream.const type: boolean = remoteVideoStream.isAvailable;
-
size
: The stream size. The higher the stream size, the better the video quality.const type: StreamSize = remoteVideoStream.size;
VideoStreamRenderer methods and properties
Create a VideoStreamRendererView
instance that can be attached in the application UI to render the remote video stream, use asynchronous createView()
method, it resolves when stream is ready to render and returns an object with target
property that represents video
element that can be appended anywhere in the DOM tree
await videoStreamRenderer.createView()
Dispose of videoStreamRenderer
and all associated VideoStreamRendererView
instances:
videoStreamRenderer.dispose()
VideoStreamRendererView methods and properties
When you create a VideoStreamRendererView
, you can specify the scalingMode
and isMirrored
properties. scalingMode
can be Stretch
, Crop
, or Fit
. If isMirrored
is specified, the rendered stream is flipped vertically.
const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });
Every VideoStreamRendererView
instance has a target
property that represents the rendering surface. Attach this property in the application UI:
htmlElement.appendChild(view.target);
You can update scalingMode
by invoking the updateScalingMode
method:
view.updateScalingMode('Crop')
Device management
In deviceManager
, you can enumerate local devices that can transmit your audio and video streams in a call. You can also use it to request permission to access the local device's microphones and cameras.
You can access deviceManager
by calling the callClient.getDeviceManager()
method:
const deviceManager = await callClient.getDeviceManager();
Get local devices
To access local devices, you can use enumeration methods on deviceManager
. Enumeration is an asynchronous action
// Get a list of available video devices for use.
const localCameras = await deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]
// Get a list of available microphone devices for use.
const localMicrophones = await deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]
// Get a list of available speaker devices for use.
const localSpeakers = await deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]
Set the default microphone and speaker
In deviceManager
, you can set a default device that you'll use to start a call. If client defaults aren't set, Communication Services uses operating system defaults.
// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;
// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);
// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;
// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);
Local camera preview
You can use deviceManager
and VideoStreamRenderer
to begin rendering streams from your local camera. This stream won't be sent to other participants; it's a local preview feed.
const cameras = await deviceManager.getCameras();
const camera = cameras[0];
const localCameraStream = new LocalVideoStream(camera);
const videoStreamRenderer = new VideoStreamRenderer(localCameraStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);
Request permission to camera and microphone
Prompt a user to grant camera and/or microphone permissions:
const result = await deviceManager.askDevicePermission({audio: true, video: true});
This resolves with an object that indicates whether audio
and video
permissions were granted:
console.log(result.audio);
console.log(result.video);
Notes
- The 'videoDevicesUpdated' event fires when video devices are plugging-in/unplugged.
- The 'audioDevicesUpdated' event fires when audio devices are plugged
- When the DeviceManager is created, at first it does not know about any devices if permissions have not been granted yet and so initially it's device lists are empty. If we then call the DeviceManager.askPermission() API, the user is prompted for device access and if the user clicks on 'allow' to grant the access, then the device manager will learn about the devices on the system, update it's device lists and emit the 'audioDevicesUpdated' and 'videoDevicesUpdated' events. Lets say we then refresh the page and create device manager, the device manager will be able to learn about devices because user has already previously granted access, and so it will initially it will have it's device lists filled and it will not emit 'audioDevicesUpdated' nor 'videoDevicesUpdated' events.
- Speaker enumeration/selection is not supported on Android Cheome, iOS Safari, nor macOS Safari.
Releasing resources
- How to properly release resources when a call is finished:
- When the call is finished our SDK will terminate the signaling & media sessions leaving you with an instance of the call that holds the last state of it. You can still check callEndReason. If your app won't hold the reference to the Call instance then the JavaScript garbage collector will clean up everything so in terms of memory consumption your app should go back to initial state from before the call.
- Which resource types are long-lived (app lifetime) vs. short-lived (call lifetime):
- The following are considered to be "long-lived" resources - you can create them and keep referenced for a long time, they are very light in terms of resource(memory) consumption so won't impact perf:
- CallClient
- CallAgent
- DeviceManager
- The following are considered to be "short-lived" resources and are the ones that are playing some role in the call itself, emit events to the application, or are interacting with local media devices. These will consume more cpu&memory, but once call ends - SDK will clean up all the state and release resource:
- Call - since it's the one holding the actual state of the call (both signaling and media).
- RemoteParticipants - Represent the remote participants in the call.
- VideoStreamRenderer with it's VideoStreamRendererViews - handling video rendering.
- The following are considered to be "long-lived" resources - you can create them and keep referenced for a long time, they are very light in terms of resource(memory) consumption so won't impact perf: