Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article describes how to manage calls using the Azure Communication Services Calling SDK. Topics include how to place calls, manage participants, and manage properties.
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 call client. For more information on how to get aUser Access Token
. - Optional: Complete getting started with adding calling to your application.
Support
The following tables define support for breakout rooms in Azure Communication Services.
Identities and call types
The following table shows support of features for specific call type and identity.
Identities | Teams meeting | Room | 1:1 call | Group call | 1:1 Teams interop call | Group Teams interop call |
---|---|---|---|---|---|---|
Communication Services user | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Microsoft 365 user | ✔️ | ✔️ | ✔️ |
Operations
The following table show support for individual APIs in the Calling SDK related to individual identity types.
Operations | Communication Services user | Microsoft 365 user |
---|---|---|
Start a call to Communication Services user | ✔️ | |
Start a call to Microsoft 365 user | ✔️ | ✔️ |
Start a call to phone number | ✔️ | ✔️ |
Join a room | ✔️ | |
Join a Teams meeting | ✔️ | ✔️ |
Join a call based on groupId | ✔️ | |
Accept or reject incoming call | ✔️ | ✔️ |
Hold and resume call | ✔️ | ✔️ |
Get participants | ✔️ | ✔️ |
Add Communication Services user | ✔️ | |
Remove Communication Services user | ✔️ | ✔️ |
Add or remove Microsoft 365 user | ✔️ | ✔️ |
Add or remove phone number | ✔️ | ✔️ |
Mute or unmute remote participant | ✔️[1] | ✔️[1] |
Hang up | ✔️ | ✔️ |
End the call for everyone | ✔️[2] | ✔️ |
[1] The API is only supported in group calls, rooms and Teams meetings. [2] The API is not supported in rooms.
SDKs
The following tables show support for the features in individual Azure Communication Services SDKs.
Support status | Web | Web UI | iOS | iOS UI | Android | Android UI | Windows |
---|---|---|---|---|---|---|---|
Is Supported | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Install the SDK
Use the npm install
command to install the Azure Communication Services Common and Calling SDK for JavaScript:
npm install @azure/communication-common --save
npm install @azure/communication-calling --save
Initialize required objects
A CallClient
instance is required for most call operations. When you create a new CallClient
instance, you can configure it with custom options like a Logger
instance.
With the CallClient
instance, you can create a CallAgent
instance by calling the createCallAgent
. 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 console, file, buffer, REST API, or whatever location you want
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 Azure Communication Services user name'});
const deviceManager = await callClient.getDeviceManager()
Manage SDK connectivity to Microsoft infrastructure
The Call Agent
instance helps you manage calls (to join or start calls). In order to work your calling SDK needs to connect to Microsoft infrastructure to get notifications of incoming calls and coordinate other call details. Your Call Agent
has two possible states:
Connected - A Call Agent
connectionStatue value of Connected
means the client SDK is connected and capable of receiving notifications from Microsoft infrastructure.
Disconnected - A Call Agent
connectionStatue value of Disconnected
states there's an issue that is preventing the SDK it from properly connecting. Call Agent
should be re-created.
invalidToken
: If a token is expired or is invalidCall Agent
instance disconnects with this error.connectionIssue
: If there's an issue with the client connecting to Microsoft infrastructure, after many retriesCall Agent
exposes theconnectionIssue
error.
You can check if your local Call Agent
is connected to Microsoft infrastructure by inspecting the current value of connectionState
property. During an active call you can listen to the connectionStateChanged
event to determine if Call Agent
changes from Connected to Disconnected state.
const connectionState = callAgentInstance.connectionState;
console.log(connectionState); // it may return either of 'Connected' | 'Disconnected'
const connectionStateCallback = (args) => {
console.log(args); // it will return an object with oldState and newState, each of having a value of either of 'Connected' | 'Disconnected'
// it will also return reason, either of 'invalidToken' | 'connectionIssue'
}
callAgentInstance.on('connectionStateChanged', connectionStateCallback);
Place a call
To create and start a call, use one of the APIs on callAgent
and provide a user that you 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
See details of PSTN calling offering. For preview program access, apply to the early adopter program.
For a 1:1 call to a PSTN number, use the following code:
const pstnCallee = { phoneNumber: '<ACS_USER_ID>' }
const alternateCallerId = {phoneNumber: '<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 = {phoneNumber: '<ALTERNATE_CALLER_ID>'};
const groupCall = callAgent.startCall([userCallee, pstnCallee], { alternateCallerId });
Join a room 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. For more information about rooms, see Rooms API for structured meetings and Join a room call.
Join a group call
Note
The groupId
parameter is system metadata, used by Microsoft for operations that are required to run the system. Don't include personal data in the groupId
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 must 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 incomingCallHandler = 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 Azure Communication Services 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', incomingCallHandler);
The incomingCall
event includes an incomingCall
instance that you can accept or reject.
The Azure Communication Calling SDK raises a cameraStartFailed: true
call diagnostic if the camera isn't available when starting, accepting, or joining a call with video enabled. In this case, the call starts with video off. The camera might not be available because it's being used by another process or is disabled in the operating system.
Hold and resume call
Note
At any given moment, there must be only one (1) active call, in Connected
state, with active media. All other calls must be put on hold by a user, or programmatically by application. This scenario is common in scenarios like contact centers, where a user may need to handle multiple outbound and inbound calls. In this case, all inactive calls should be put on hold, and user should interact with others only in active call
To hold or resume the call, use the hold
and resume
asynchronous APIs:
To hold the call:
await call.hold();
When hold
operation resolves, the call state is set to LocalHold
. In a 1:1 call, the other participant is also put on hold, and state of the call from the perspective of that participant is set to RemoteHold
. Later, the other participant might put its call on hold, which would result in a state change to LocalHold
.
In a group call or meeting - the hold
is a local operation, it doesn't hold the call for other call participants.
To resume the call, all users who initiated hold must resume it.
To resume call from hold:
await call.resume();
When the resume
operation resolves, the call state again sets to Connected
.
Mute and unmute a call
To mute or unmute the local endpoint, 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 and unmute incoming audio
Mute incoming audio sets the call volume to 0. To mute or unmute the incoming audio, use the muteIncomingAudio
and unmuteIncomingAudio
asynchronous operations:
//mute local device (speaker)
await call.muteIncomingAudio();
//unmute local device (speaker)
await call.unmuteIncomingAudio();
When incoming audio is muted, the participant client SDK still receives the call audio (remote participant's audio). The call audio isn't heard in the speaker and the participant isn't able to listen until call.unmuteIncomingAudio()
is called. However, we can apply a filter on call audio and play the filtered audio.
Manage remote participants
All remote participants are included in the RemoteParticipant
object and available through the remoteParticipants
collection on a call instance. The remoteParticipants
object is accessible from 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....]
Add a participant to a call
To add a participant (either a user or a phone number) to a call, use the addParticipant
operation. Provide one of the Identifier
types. It synchronously returns the remoteParticipant
instance. When a participant is successfully added to the call, it raises the remoteParticipantsUpdated
event from Call.
const userIdentifier = { communicationUserId: '<ACS_USER_ID>' };
const pstnIdentifier = { phoneNumber: '<PHONE_NUMBER>' }
const remoteParticipant = call.addParticipant(userIdentifier);
const alternateCallerId = { phoneNumber: '<ALTERNATE_CALLER_ID>' };
const remoteParticipant = call.addParticipant(pstnIdentifier, { alternateCallerId });
Remove a participant from a call
To remove a participant (either a user or a phone number) from a call, you can call removeParticipant
. You have to pass one of the Identifier
types. This method resolves asynchronously after removing the participant 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);
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 Azure Communication Services 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
Note
This property is only set when adding a remote participant via the Call.addParticipant() API, and the remote participant declines for example.
In the scenario, where UserB kicks UserC, from UserA's perspective, UserA doesn't see this flag get set for UserC. In other words, UserA doesn't see UserC's callEndReason property get set at all.
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;
endpointDetails
: Get the details of all the endpoints for this remote participantconst endpointDetails: EndpointDetails[] = remoteParticipant.endpointDetails;
Note
A remote participant could be in the call from many possible endpoints, and each endpoint has its own unique
participantId
.participantId
is different from theRemoteParticipant
identifier's raw ID.
Mute other participants
Note
Use Azure Communication Services Calling Web SDK version 1.26.1 or higher.
To mute all other participants or mute a specific participant who is connected to a call, you can use the asynchronous APIs muteAllRemoteParticipants
on the call and mute
on the remote participant. The mutedByOthers
event from Call is raised when the local participant is muted by others.
Important
This feature of Azure Communication Services is currently in preview. Features in preview are publicly available and can be used by all new and existing Microsoft customers.
Preview APIs and SDKs are provided without a service-level agreement. We recommend that you don't use them for production workloads. Certain features might not be supported or capabilities might be constrained.
For more information, see Supplemental Terms of Use for Microsoft Azure Previews.
Muting a PSTN endpoint using the calling WebJS SDK is currently in public preview and is available in build 1.34.1 1.34.1 and later versions.
Note
Muting others on a 1:1 call isn't supported.
//mute all participants except yourself
await call.muteAllRemoteParticipants();
//mute a specific participant
await call.remoteParticipants[0].mute();
Check call properties
Get the unique ID (string) for a call:
const callId: string = call.id;
Get the local participant ID:
const participantId: string = call.info.participantId;
Note
An Azure Communication Services identity can use the web calling SDK in many endpoints, and each endpoint has its own unique participantId
. participantId
is different from the Azure Communication Services identity raw ID.
Retrieve the thread ID if joining a Teams meeting:
const threadId: string | undefined = call.info.threadId;
Get information about the call:
const callInfo = call.info;
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'sIncoming
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 a local participant the call put the call on hold. No media is flowing between the local endpoint and remote participants.RemoteHold
: Indicates that a remote participant the call put the call on hold. 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 callEndReasonMessage = callEndReason.message // (string) user friendly message
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';
Inspect the active video streams and active screen sharing streams by checking the localVideoStreams
collection. The localVideoStreams
operation returns LocalVideoStream
objects of type Video
, ScreenSharing
, or RawMedia
.
const localVideoStreams = call.localVideoStreams;
Check if the current microphone is muted. It returns Boolean
.
const muted = call.isMuted;
Check if the current incoming audio (speaker) is muted. It returns Boolean
.
const incomingAudioMuted = call.isIncomingAudioMuted;
Check if video is on. It returns Boolean
.
const isLocalVideoStarted = call.isLocalVideoStarted;
Check is screen sharing is on. It returns Boolean
.
const isScreenSharingOn = call.isScreenSharingOn;
Hang up
There are two ways to hang up the call.
- The initial caller can leave the call and the other participants remain in the call.
- When the initial caller leaves, the call terminates for all participants.
To leave the call, use:
call.hangUp();
End the call for all participants by providing HangUpOptions
.
Note
This operation isn't available in rooms.
call.hangUp( forEveryone: true);
Install the SDK
Locate your project-level build.gradle
file and add mavenCentral()
to the list of repositories under buildscript
and allprojects
:
buildscript {
repositories {
...
mavenCentral()
...
}
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
Then, in your module-level build.gradle
file, add the following lines to the dependencies
section:
dependencies {
...
implementation 'com.azure.android:azure-communication-calling:1.0.0'
...
}
Initialize the required objects
To create a CallAgent
instance, you have to call the createCallAgent
method on a CallClient
instance. This call asynchronously returns a CallAgent
instance object.
The createCallAgent
method takes CommunicationUserCredential
as an argument, which encapsulates an access token.
To access DeviceManager
, you must create a callAgent
instance first. Then you can use the CallClient.getDeviceManager
method to get DeviceManager
.
String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential).get();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
To set a display name for the caller, use this alternative method:
String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.setDisplayName("Alice Bob");
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();
Place a call
To create and start a call, you need to call the CallAgent.startCall()
method and provide the Identifier
of the recipient or recipients.
To join a group call, you need to call the CallAgent.join()
method and provide the groupId
. Group IDs must be in GUID or UUID format.
Call creation and start are synchronous. The call instance enables you to subscribe to all events on the call.
Place a 1:1 call to a user
To place a call to another Communication Services user, invoke the call
method on callAgent
and pass an object with communicationUserId
key.
StartCallOptions startCallOptions = new StartCallOptions();
Context appContext = this.getApplicationContext();
CommunicationUserIdentifier acsUserId = new CommunicationUserIdentifier(<USER_ID>);
CommunicationUserIdentifier participants[] = new CommunicationUserIdentifier[]{ acsUserId };
call oneToOneCall = callAgent.startCall(appContext, participants, startCallOptions);
Place a 1:n call with users and PSTN
Note
See details of PSTN calling offering. For preview program access, apply to the early adopter program.
To place a 1:n call to a user and a public switched telephone network (PSTN) number, you need to specify the phone number of the recipient or recipients.
Your Communication Services resource must be configured to enable PSTN calling:
CommunicationUserIdentifier acsUser1 = new CommunicationUserIdentifier(<USER_ID>);
PhoneNumberIdentifier acsUser2 = new PhoneNumberIdentifier("<PHONE_NUMBER>");
CommunicationIdentifier participants[] = new CommunicationIdentifier[]{ acsUser1, acsUser2 };
StartCallOptions startCallOptions = new StartCallOptions();
Context appContext = this.getApplicationContext();
Call groupCall = callAgent.startCall(participants, startCallOptions);
Accept a call
To accept a call, call the accept
method on a call object.
Context appContext = this.getApplicationContext();
IncomingCall incomingCall = retrieveIncomingCall();
Call call = incomingCall.accept(context).get();
To accept a call with video camera on:
Context appContext = this.getApplicationContext();
IncomingCall incomingCall = retrieveIncomingCall();
AcceptCallOptions acceptCallOptions = new AcceptCallOptions();
VideoDeviceInfo desiredCamera = callClient.getDeviceManager().get().getCameraList().get(0);
acceptCallOptions.setVideoOptions(new VideoOptions(new LocalVideoStream(desiredCamera, appContext)));
Call call = incomingCall.accept(context, acceptCallOptions).get();
Obtain the incoming call by subscribing to the onIncomingCall
event on the callAgent
object:
// Assuming "callAgent" is an instance property obtained by calling the 'createCallAgent' method on CallClient instance
public Call retrieveIncomingCall() {
IncomingCall incomingCall;
callAgent.addOnIncomingCallListener(new IncomingCallListener() {
void onIncomingCall(IncomingCall inboundCall) {
// Look for incoming call
incomingCall = inboundCall;
}
});
return incomingCall;
}
Join a room call
Use the CallAgent
and RoomCallLocator
to join a room call by specifying a roomId
. The CallAgent.join
method returns a Call
object:
val roomCallLocator = RoomCallLocator(roomId)
call = callAgent.join(applicationContext, roomCallLocator, joinCallOptions)
A room
offers application developers better control over who can join a call, when they meet and how they collaborate. For more information about rooms, see Rooms API for structured meetings and Join a room call.
Join a group call
To start a new group call or join an ongoing group call, you need to call the join
method and pass an object with a groupId
property. The value has to be a GUID.
Context appContext = this.getApplicationContext();
GroupCallLocator groupCallLocator = new GroupCallLocator("<GUID>");
JoinCallOptions joinCallOptions = new JoinCallOptions();
call = callAgent.join(context, groupCallLocator, joinCallOptions);
Call properties
Get the unique ID for this Call:
String callId = call.getId();
To learn about other participants in the call inspect remoteParticipant
collection on the call
instance:
List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants();
The identity of caller if the call is incoming:
CommunicationIdentifier callerId = call.getCallerInfo().getIdentifier();
Get the state of the Call:
CallState callState = call.getState();
It returns a string representing the current state of a call:
NONE
- initial call stateEARLY_MEDIA
- indicates a state in which an announcement is played before call is connectedCONNECTING
- initial transition state once call is placed or acceptedRINGING
- for an outgoing call - indicates call is ringing for remote participantsCONNECTED
- call connectedLOCAL_HOLD
- call placed on hold by local participant with no media flowing between local endpoint and remote participantsREMOTE_HOLD
- call placed on hold by a remote participant with no media flowing between local endpoint and remote participantsDISCONNECTING
- transition state before call goes toDisconnected
stateDISCONNECTED
- final call stateIN_LOBBY
- in lobby for a Teams meeting interoperability
To learn why a call ended, inspect callEndReason
property. It contains code/subcode:
CallEndReason callEndReason = call.getCallEndReason();
int code = callEndReason.getCode();
int subCode = callEndReason.getSubCode();
To see if the current call is an incoming or an outgoing call, inspect callDirection
property:
CallDirection callDirection = call.getCallDirection();
// callDirection == CallDirection.INCOMING for incoming call
// callDirection == CallDirection.OUTGOING for outgoing call
To see if the current microphone is muted, inspect the muted
property:
boolean muted = call.isMuted();
To inspect active video streams, check the localVideoStreams
collection:
List<LocalVideoStream> localVideoStreams = call.getLocalVideoStreams();
Mute and unmute
To mute or unmute the local endpoint, you can use the mute
and unmute
asynchronous APIs:
Context appContext = this.getApplicationContext();
call.mute(appContext).get();
call.unmute(appContext).get();
Change the volume of the call
While participants are in a call, the hardware volume keys on the phone should enable the user to change the call volume.
Use the method setVolumeControlStream
with the stream type AudioManager.STREAM_VOICE_CALL
on the Activity where the call is happening.
This method enables the hardware volume keys to change the volume of the call, denoted by a phone icon or something similar on the volume slider. It also prevents changes to volume by other sound profiles, like alarms, media, or system wide volume. For more information, see Handling changes in audio output | Android Developers.
@Override
protected void onCreate(Bundle savedInstanceState) {
...
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}
Remote participants management
All remote participants have the RemoteParticipant
type and are available through the remoteParticipants
collection on a call instance.
List participants in a call
The remoteParticipants
collection returns a list of remote participants in given call:
List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants(); // [remoteParticipant, remoteParticipant....]
Add a participant to a call
To add a participant to a call (either a user or a phone number), you can call the addParticipant
operation.
This operation synchronously returns the remote participant instance.
const acsUser = new CommunicationUserIdentifier("<acs user id>");
const acsPhone = new PhoneNumberIdentifier("<phone number>");
RemoteParticipant remoteParticipant1 = call.addParticipant(acsUser);
AddPhoneNumberOptions addPhoneNumberOptions = new AddPhoneNumberOptions(new PhoneNumberIdentifier("<alternate phone number>"));
RemoteParticipant remoteParticipant2 = call.addParticipant(acsPhone, addPhoneNumberOptions);
Remove participant from a call
To remove a participant from a call (either a user or a phone number), you can call the removeParticipant
operation.
This operation resolves asynchronously once the participant is removed from the call.
The participant is also removed from remoteParticipants
collection.
RemoteParticipant acsUserRemoteParticipant = call.getParticipants().get(0);
RemoteParticipant acsPhoneRemoteParticipant = call.getParticipants().get(1);
call.removeParticipant(acsUserRemoteParticipant).get();
call.removeParticipant(acsPhoneRemoteParticipant).get();
Remote participant properties
Any given remote participant has a set of associated properties and collections:
- Get the identifier for this remote participant.
Identity is one of the Identifier
types:
CommunicationIdentifier participantIdentifier = remoteParticipant.getIdentifier();
Get the state of this remote participant.
ParticipantState state = remoteParticipant.getState();
State can be one of:
IDLE
- initial stateEARLY_MEDIA
- announcement is played before participant is connected to the callRINGING
- participant call is ringingCONNECTING
- transition state while participant is connecting to the callCONNECTED
- participant is connected to the callHOLD
- participant is on holdIN_LOBBY
- participant is waiting in the lobby to be admitted. Currently only used in Teams interop scenarioDISCONNECTED
- final state - participant is disconnected from the callTo learn why a participant left the call, inspect
callEndReason
property:CallEndReason callEndReason = remoteParticipant.getCallEndReason();
To check whether this remote participant is muted or not, inspect the
isMuted
property:boolean isParticipantMuted = remoteParticipant.isMuted();
To check whether this remote participant is speaking or not, inspect the
isSpeaking
property:boolean isParticipantSpeaking = remoteParticipant.isSpeaking();
To inspect all video streams that a given participant is sending in this call, check the
videoStreams
collection:List<RemoteVideoStream> videoStreams = remoteParticipant.getVideoStreams(); // [RemoteVideoStream, RemoteVideoStream, ...]
Mute other participants
Note
Use the Azure Communication Services Calling Android SDK version 2.11.0 or higher.
When a PSTN participant is muted, they receive an announcement that they're muted and that they can press a key combination (such as *6) to unmute themselves. When they press *6, they are unmuted.
To mute all other participants in a call, use the muteAllRemoteParticipants
API operation.
call.muteAllRemoteParticipants();
To mute a specific remote participant, use the mute
API operation on a given remote participant.
remoteParticipant.mute();
To notify the local participant that they're muted by others, subscribe to the onMutedByOthers
event.
Using Foreground Services
If you want to run a user-visible task even when your application is in background, you can use Foreground Services.
Use Foreground Services, for example, to provide users with a visible notification when your application has an active call. This way, even if the user goes to the homescreen or removes the application from the recents screen, the call continues to be active.
If you don't use a Foreground Service while in a call, navigating to the homescreen can keep the call active, but removing the application from the recent's screen can stop the call if the Android OS kills your application's process.
You should start the Foreground Service when a user starts or joins a call, for example:
call = callAgent.startCall(context, participants, options);
startService(yourForegroundServiceIntent);
You should also stop the Foreground Service when you hang up the call or the call's status is Disconnected, for example:
call.hangUp(new HangUpOptions()).get();
stopService(yourForegroundServiceIntent);
Foreground Service details
Keep in mind that scenarios like stopping an already running Foreground Service when the app is removed from the recent's list removes the user-visible notification. In this case the Android OS can keep your application process alive for some extra period of time, meaning that the call can remain active during this period.
If your application is stopping the Foreground Service on the service onTaskRemoved
method, for example, your application can start or stop audio and video according to your Activity Lifecycle. Such as stopping audio and video when your activity is destroyed with the onDestroy
method override.
Set up your system
Follow these steps to set up your system.
Create the Xcode project
In Xcode, create a new iOS project and select the Single View App template. This article uses the SwiftUI framework, so you should set Language to Swift and set Interface to SwiftUI.
You're not going to create tests in this article. Feel free to clear the Include Tests checkbox.
Install the package and dependencies by using CocoaPods
Create a Podfile for your application, like this example:
platform :ios, '13.0' use_frameworks! target 'AzureCommunicationCallingSample' do pod 'AzureCommunicationCalling', '~> 1.0.0' end
Run
pod install
.Open
.xcworkspace
by using Xcode.
Request access to the microphone
To access the device's microphone, you need to update your app's information property list by using NSMicrophoneUsageDescription
. Set the associated value to a string that's included in the dialog that the system uses to request access from the user.
Right-click the Info.plist entry of the project tree, and then select Open As > Source Code. Add the following lines in the top-level <dict>
section, and then save the file.
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>
Set up the app framework
Open your project's ContentView.swift
file. Add an import
declaration to the top of the file to import the AzureCommunicationCalling
library. In addition, import AVFoundation
. You need it for audio permission requests in the code.
import AzureCommunicationCalling
import AVFoundation
Initialize CallAgent
To create a CallAgent
instance from CallClient
, you have to use a callClient.createCallAgent
method that asynchronously returns a CallAgent
object after it's initialized.
To create a call client, pass a CommunicationTokenCredential
object:
import AzureCommunication
let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
do {
let options = CommunicationTokenRefreshOptions(initialToken: token, refreshProactively: true, tokenRefresher: self.fetchTokenSync)
userCredential = try CommunicationTokenCredential(withOptions: options)
} catch {
updates("Couldn't created Credential object", false)
initializationDispatchGroup!.leave()
return
}
// tokenProvider needs to be implemented by Contoso, which fetches a new token
public func fetchTokenSync(then onCompletion: TokenRefreshOnCompletion) {
let newToken = self.tokenProvider!.fetchNewToken()
onCompletion(newToken, nil)
}
Pass the CommunicationTokenCredential
object that you created to CallClient
, and set the display name:
self.callClient = CallClient()
let callAgentOptions = CallAgentOptions()
options.displayName = " iOS Azure Communication Services User"
self.callClient!.createCallAgent(userCredential: userCredential!,
options: callAgentOptions) { (callAgent, error) in
if error == nil {
print("Create agent succeeded")
self.callAgent = callAgent
} else {
print("Create agent failed")
}
})
Note
When the application implements event delegates, it must hold a strong reference to the objects that require event subscriptions. For example, when you call the call.addParticipant
method and it returns a RemoteParticipant
object. Then the application sets the delegate to listen on RemoteParticipantDelegate
and the application must hold a strong reference to the RemoteParticipant
object. Otherwise, if this object is collected, the delegate throws a fatal exception when the Calling SDK tries to invoke the object.
Place an outgoing call
To create and start a call, you need to call one of the APIs on CallAgent
and provide the Communication Services identity of a user that you've provisioned by using the Communication Services Management SDK.
Call creation and start are synchronous. You receive a call instance that enables you to subscribe to all events on the call.
Place a 1:1 call to a user or a 1:n call with users and PSTN
let callees = [CommunicationUser(identifier: 'UserId')]
self.callAgent?.startCall(participants: callees, options: StartCallOptions()) { (call, error) in
if error == nil {
print("Successfully started outgoing call")
self.call = call
} else {
print("Failed to start outgoing call")
}
}
Place a 1:n call with users and PSTN
Note
See details of PSTN calling offering. For preview program access, apply to the early adopter program.
To place a 1:n call to a user and a public switched telephone network (PSTN), you need to specify a phone number acquired with Communication Services.
let pstnCallee = PhoneNumberIdentifier(phoneNumber: '+1999999999')
let callee = CommunicationUserIdentifier('UserId')
self.callAgent?.startCall(participants: [pstnCallee, callee], options: StartCallOptions()) { (groupCall, error) in
if error == nil {
print("Successfully started outgoing call to multiple participants")
self.call = groupCall
} else {
print("Failed to start outgoing call to multiple participants")
}
}
Join a room call
To join a room
call, specify the roomId
property as the room
identifier. To join the call, use the join
method and pass the roomCallLocator
.
func joinRoomCall() {
if self.callAgent == nil {
print("CallAgent not initialized")
return
}
if (self.roomId.isEmpty) {
print("Room ID not set")
return
}
// Join a call with a Room ID
let options = JoinCallOptions()
let audioOptions = AudioOptions()
audioOptions.muted = self.muted
options.audioOptions = audioOptions
let roomCallLocator = RoomCallLocator(roomId: roomId)
self.callAgent!.join(with: roomCallLocator, joinCallOptions: options) { (call, error) in
self.setCallAndObserver(call: call, error: error)
}
}
A room
offers application developers better control over who can join a call, when they meet and how they collaborate. For more information about rooms, see Rooms API for structured meetings and Join a room call.
Join a group call
To join a call, you need to call one of the APIs on CallAgent
.
let groupCallLocator = GroupCallLocator(groupId: UUID(uuidString: "uuid_string")!)
self.callAgent?.join(with: groupCallLocator, joinCallOptions: JoinCallOptions()) { (call, error) in
if error == nil {
print("Successfully joined group call")
self.call = call
} else {
print("Failed to join group call")
}
}
Subscribe to an incoming call
Subscribe to an incoming call event.
final class IncomingCallHandler: NSObject, CallAgentDelegate, IncomingCallDelegate
{
// Event raised when there is an incoming call
public func callAgent(_ callAgent: CallAgent, didReceiveIncomingCall incomingcall: IncomingCall) {
self.incomingCall = incomingcall
// Subscribe to get OnCallEnded event
self.incomingCall?.delegate = self
}
// Event raised when incoming call was not answered
public func incomingCall(_ incomingCall: IncomingCall, didEnd args: PropertyChangedEventArgs) {
print("Incoming call was not answered")
self.incomingCall = nil
}
}
Accept an incoming call
To accept a call, call the accept
method on a IncomingCall
object.
self.incomingCall!.accept(options: AcceptCallOptions()) { (call, error) in
if (error == nil) {
print("Successfully accepted incoming call")
self.call = call
} else {
print("Failed to accept incoming call")
}
}
let firstCamera: VideoDeviceInfo? = self.deviceManager!.cameras.first
localVideoStreams = [LocalVideoStream]()
localVideoStreams!.append(LocalVideoStream(camera: firstCamera!))
let acceptCallOptions = AcceptCallOptions()
acceptCallOptions.videoOptions = VideoOptions(localVideoStreams: localVideoStreams!)
if let incomingCall = self.incomingCall {
incomingCall.accept(options: acceptCallOptions) { (call, error) in
if error == nil {
print("Incoming call accepted")
} else {
print("Failed to accept incoming call")
}
}
} else {
print("No incoming call found to accept")
}
Perform mid-call operations
You can perform operations during a call to manage settings related to video and audio.
Mute and unmute
To mute or unmute the local endpoint, you can use the mute
and unmute
asynchronous APIs.
call!.mute { (error) in
if error == nil {
print("Successfully muted")
} else {
print("Failed to mute")
}
}
Use the following code to unmute the local endpoint asynchronously.
call!.unmute { (error) in
if error == nil {
print("Successfully un-muted")
} else {
print("Failed to unmute")
}
}
Manage remote participants
The RemoteParticipant
type represents all remote participants. They're available through the remoteParticipants
collection on a call instance.
List participants in a call
call.remoteParticipants
Add a participant to a call
To add a participant to a call as either a user or a phone number, call the addParticipant
operation. This operation synchronously returns a remote participant instance.
let remoteParticipantAdded: RemoteParticipant = call.add(participant: CommunicationUserIdentifier(identifier: "userId"))
Remove a participant from a call
To remove a participant from a call as either a user or a phone number, call the removeParticipant
operation. This operation resolves asynchronously.
call!.remove(participant: remoteParticipantAdded) { (error) in
if (error == nil) {
print("Successfully removed participant")
} else {
print("Failed to remove participant")
}
}
Get remote participant properties
// [RemoteParticipantDelegate] delegate - an object you provide to receive events from this RemoteParticipant instance
var remoteParticipantDelegate = remoteParticipant.delegate
// [CommunicationIdentifier] identity - same as the one used to provision a token for another user
var identity = remoteParticipant.identifier
// ParticipantStateIdle = 0, ParticipantStateEarlyMedia = 1, ParticipantStateConnecting = 2, ParticipantStateConnected = 3, ParticipantStateOnHold = 4, ParticipantStateInLobby = 5, ParticipantStateDisconnected = 6
var state = remoteParticipant.state
// [Error] callEndReason - reason why participant left the call, contains code/subcode/message
var callEndReason = remoteParticipant.callEndReason
// [Bool] isMuted - indicating if participant is muted
var isMuted = remoteParticipant.isMuted
// [Bool] isSpeaking - indicating if participant is currently speaking
var isSpeaking = remoteParticipant.isSpeaking
// RemoteVideoStream[] - collection of video streams this participants has
var videoStreams = remoteParticipant.videoStreams // [RemoteVideoStream, RemoteVideoStream, ...]
Mute other participants
Note
Use the Azure Communication Services Calling iOS SDK version 2.13.0 or higher.
When a PSTN participant is muted, they receive an announcement that they're muted and that they can press a key combination (such as *6) to unmute themselves. When they press *6, they're unmuted.
To mute all other participants in a call, use the muteAllRemoteParticipants
operation on the call.
call!.muteAllRemoteParticipants { (error) in
if error == nil {
print("Successfully muted all remote participants.")
} else {
print("Failed to mute remote participants.")
}
}
To mute a specific remote participant, use the mute
operation on a given remote participant.
remoteParticipant.mute { (error) in
if error == nil {
print("Successfully muted participant.")
} else {
print("Failed to mute participant.")
}
}
To notify the local participant that they're muted by others, subscribe to the onMutedByOthers
event.
Set up your system
Follow these steps to set up your system.
Create the Visual Studio project
For a Universal Windows Platform app, in Visual Studio 2022, create a new Blank App (Universal Windows) project. After you enter the project name, feel free to choose any Windows SDK later than 10.0.17763.0.
For a WinUI 3 app, create a new project with the Blank App, Packaged (WinUI 3 in Desktop) template to set up a single-page WinUI 3 app. Windows App SDK version 1.3 or later is required.
Install the package and dependencies by using NuGet Package Manager
The Calling SDK APIs and libraries are publicly available via a NuGet package.
To find, download, and install the Calling SDK NuGet package:
- Open NuGet Package Manager by selecting Tools > NuGet Package Manager > Manage NuGet Packages for Solution.
- Select Browse, and then enter Azure.Communication.Calling.WindowsClient in the search box.
- Make sure that the Include prerelease checkbox is selected.
- Select the Azure.Communication.Calling.WindowsClient package, and then select Azure.Communication.Calling.WindowsClient 1.4.0-beta.1 or a newer version.
- Select the checkbox that corresponds to the Azure Communication Services project on the right pane.
- Select Install.
Implement the sample app in Visual Studio
This section describes how to develop the app to manage calls working in Visual Studio.
Request access to the microphone
The app requires access to the microphone. In Universal Windows Platform (UWP) apps, the microphone capability must be declared in the app manifest file.
To access the microphone:
- In the
Solution Explorer
panel, double click on the file with.appxmanifest
extension. - Click on the
Capabilities
tab. - Select the
Microphone
check box from the capabilities list.
Create UI buttons to place and hang up the call
This sample app contains two buttons. One for placing the call and another to hang up a placed call.
Add two buttons to the app.
- In the
Solution Explorer
panel, double click on the file namedMainPage.xaml
for UWP, orMainWindows.xaml
for WinUI 3. - In the central panel, look for the XAML code under the UI preview.
- Modify the XAML code by the following excerpt:
<TextBox x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" />
<StackPanel>
<Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" />
<Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" />
</StackPanel>
Set up the app with Calling SDK APIs
The Calling SDK APIs are in two different namespaces.
The following steps inform the C# compiler about these namespaces, enabling Visual Studio's Intellisense to help with code development.
- In the
Solution Explorer
panel, click on the arrow on the left side of the file namedMainPage.xaml
for UWP, orMainWindows.xaml
for WinUI 3. - Double click on file named
MainPage.xaml.cs
orMainWindows.xaml.cs
. - Add the following commands at the bottom of the current
using
statements.
using Azure.Communication.Calling.WindowsClient;
Keep MainPage.xaml.cs
or MainWindows.xaml.cs
open. The next steps add more code.
Enable app interactions
The UI buttons previously added need to operate on top of a placed CommunicationCall
. It means that a CommunicationCall
data member should be added to the MainPage
or MainWindow
class.
Additionally, to allow the asynchronous operation creating CallAgent
to succeed, a CallAgent
data member should also be added to the same class.
Add the following data members to the MainPage
pr MainWindow
class:
CallAgent callAgent;
CommunicationCall call;
Create button handlers
Previously, two UI buttons were added to the XAML code. The following code adds the handlers to be executed when a user selects the button. The following code should be added after the data members from the previous section.
private async void CallButton_Click(object sender, RoutedEventArgs e)
{
// Start call
}
private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
// End the current call
}
Object model
The following classes and interfaces handle some of the major features of the Azure Communication Services Calling client library for UWP.
Name | Description |
---|---|
CallClient |
The CallClient is the main entry point to the Calling client library. |
CallAgent |
The CallAgent is used to start and join calls. |
CommunicationCall |
The CommunicationCall is used to manage placed or joined calls. |
CommunicationTokenCredential |
The CommunicationTokenCredential is used as the token credential to instantiate the CallAgent . |
CallAgentOptions |
The CallAgentOptions contains information to identify the caller. |
HangupOptions |
The HangupOptions informs if a call should be terminated to all its participants. |
Initialize the CallAgent
To create a CallAgent
instance from CallClient
, you must use CallClient.CreateCallAgentAsync
method that asynchronously returns a CallAgent
object once it's initialized.
To create CallAgent
, you must pass a CallTokenCredential
object and a CallAgentOptions
object. Keep in mind that CallTokenCredential
throws if a malformed token is passed.
To call during app initialization, add the following code inside and helper function.
var callClient = new CallClient();
this.deviceManager = await callClient.GetDeviceManagerAsync();
var tokenCredential = new CallTokenCredential("<AUTHENTICATION_TOKEN>");
var callAgentOptions = new CallAgentOptions()
{
DisplayName = "<DISPLAY_NAME>"
};
this.callAgent = await callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
Change the <AUTHENTICATION_TOKEN>
with a valid credential token for your resource. If you need to source a credential token, see user access token.
Create CallAgent and place a call
The objects you need to create a CallAgent
are now ready. It's time to asynchronously create CallAgent
and place a call.
Add the following code after handling the exception from the previous step.
var startCallOptions = new StartCallOptions();
var callees = new [] { new UserCallIdentifier(CalleeTextBox.Text.Trim()) };
this.call = await this.callAgent.StartCallAsync(callees, startCallOptions);
this.call.OnStateChanged += Call_OnStateChangedAsync;
Use 8:echo123
to talk to the Azure Communication Services echo bot.
Mute and unmute
To mute or unmute the outgoing audio, use the MuteOutgoingAudioAsync
and UnmuteOutgoingAudioAsync
asynchronous operations:
// mute outgoing audio
await this.call.MuteOutgoingAudioAsync();
// unmute outgoing audio
await this.call.UnmuteOutgoingAudioAsync();
Mute other participants
Note
Use the Azure Communication Services Calling Windows SDK version 1.9.0 or higher.
When a PSTN participant is muted, they must receive an announcement that they have been muted and that they can press a key combination (such as *6) to unmute themselves. When they press *6, they must be unmuted.
To mute all other participants or mute a specific participant, use the asynchronous operations MuteAllRemoteParticipantsAsync
on the call and MuteAsync
on the remote participant:
// mute all participants except yourself
await this.call.MuteAllRemoteParticipantsAsync();
// mute specific participant in the call
await this.call.RemoteParticipants.FirstOrDefault().MuteAsync();
To notify the local participant that they're muted by others, subscribe to the MutedByOthers
event.
End a call
Once a call is placed, use the HangupAsync
method of the CommunicationCall
object to hang up the call.
Use an instance of HangupOptions
to inform all participants if the call must be terminated.
Add the following code inside HangupButton_Click
:
this.call.OnStateChanged -= Call_OnStateChangedAsync;
await this.call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
Run the code
- Make sure Visual Studio builds the app for
x64
,x86
orARM64
. - Press F5 to start running the app.
- Once the app is running, click the Call button to place a call to the defined recipient.
The first time the app runs, the system prompts the user to grant access to the microphone.