Subscribe to SDK events
Azure Communication Services SDKs are dynamic and contain a lot of properties. When these change, as a developer you might want to know when and more importantly what changes. Here's how!
Events on the Azure Communication Calling SDK
This guide describes the various events or properties changes your app can subscribe to. Subscribing to those events allows your app to be informed about state change in the calling SDK and react accordingly.
Tracking events is crucial because it enables your application's state to stay synchronized with the ACSCalling framework's state, all without requiring you to implement a pull mechanism on the SDK objects.
This guide assumes you went through the QuickStart or that you implemented an application that is able to make and receive calls. If you didn't complete the getting starting guide, refer to our Quickstart.
Each object in the JavaScript calling SDK has properties
and collections
. Their values change throughout the lifetime of the object.
Use the on()
method to subscribe to objects' events, and use the off()
method to unsubscribe from objects' events.
Properties
You can subscribe to the '<property>Changed'
event to listen to value changes on the property.
Example of subscription on a property
In this example, we subscribe to changes in the value of the isLocalVideoStarted
property.
call.on('isLocalVideoStartedChanged', () => {
// At that point the value call.isLocalVideoStarted is updated
console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
});
Collections
You can subscribe to the '<collection>Updated' event to receive notifications about changes in an object collection. The '<collection>Updated' event is triggered whenever elements are added to or removed from the collection you're monitoring.
- 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 subscription on a collection
In this example, we subscribe to changes in values of the Call object LocalVideoStream
.
call.on('localVideoStreamsUpdated', updateEvent => {
updateEvent.added.forEach(async (localVideoStream) => {
// Contains an array of LocalVideoStream that were added to the call
// Add a preview and start any processing if needed
handleAddedLocalVideoStream(localVideoStream )
});
updateEvent.removed.forEach(localVideoStream => {
// Contains an array of LocalVideoStream that were removed from the call
// Remove the preview and stop any processing if needed
handleRemovedLocalVideoStream(localVideoStream )
});
});
Events on the CallAgent
object
Event Name: incomingCall
The incomingCall
event fires when the client is receiving an incoming call.
How should your application react to the event?
Your application should notify the user of the incoming call. The notification prompt should propose the user to accept or refuse the call.
Code sample:
callClient.on('incomingCall', (async (incomimgCallEvent) => {
try {
// Store a reference to the call object
incomingCall = incomimgCallEvent.incomingCall;
// Update your UI to allow
acceptCallButton.disabled = false;
callButton.disabled = true;
} catch (error) {
console.error(error);
}
});
Event Name: callsUpdated
The callsUpdated
updated event is fired when a call is removed or added to the call agent. This event happens when the user makes, receives, or terminates a call.
How should your application react to the event? Your application should update its UI based on the number of active calls for the CallAgent instance.
Event Name: connectionStateChanged
The connectionStateChanged
event fired when the signaling state of the CallAgent
is updated.
How should your application react to the event?
Your application should update its UI based on the new state. The possible connection state values are Connected
and Disconnected
Code sample:
callClient.on('connectionStateChanged', (async (connectionStateChangedEvent) => {
if (connectionStateChangedEvent.newState === "Connected") {
enableCallControls() // Enable all UI element that allow user to make a call
}
if (connectionStateChangedEvent.newState === 'Disconnected') {
if (typeof connectionStateChangedEvent.reason !== 'undefined') {
alert(`Disconnected reason: ${connectionStateChangedEvent.reason}`)
}
disableCallControls() // Disable all the UI element that allows the user to make a call
}
});
Events on the Call
object
Event Name: stateChanged
The stateChanged
event is fired when the call state changes. For example, when a call goes from connected
to disconnected
.
How should your application react to the event?
Your application should update its UI accordingly. Disabling or enabling appropriate buttons and other UI elements based on the new call state.
Code Sample:
call.on('stateChanged', (async (connectionStateChangedEvent) => {
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}}`);
}
});
Event: idChanged
The idChanged
event is fired when the ID of a call changes. The ID of a call changes when the call moves from connecting
state to connected
. Once the call is connected, the ID of the call remains identical.
How might your application react to the event?
Your application should save the new call ID but it can also be retrieved from the call object later when needed.
Code Sample:
let callId = "";
call.on('idChanged', (async (callIdChangedEvent) => {
callId = call.id; // You can log it as the call ID is useful for debugging call issues
});
Event: isMutedChanged
The isMutedChanged
event is fired when the local audio is muted or unmuted.
How might your application react to the event?
Your application should update the mute / unmute button to the proper state.
Code Sample:
call.on('isMutedChanged', (async (isMutedChangedEvent) => {
microphoneButton.disabled = call.isMuted;
});
Event: isScreenSharingOnChanged
The isScreenSharingOnChanged
event is fired when screen sharing for the local user is enabled or disabled.
How might your application react to the event?
Your application should show a preview and/or a warning to the user if the screen sharing became on. If the screen sharing went off, then the application should remove the preview and warning.
Code Sample:
call.on('isScreenSharingOnChanged', () => {
if (!this.call.isScreenSharing) {
displayStartScreenSharingButton();
hideScreenSharingWarning()
removeScreenSharingPreview();
} else {
displayScreenSharingWarning()
displayStopScreenSharingButton();
renderScreenSharingPreview();
}
});
Event: isLocalVideoStartedChanged
The isLocalVideoStartedChanged
event is fired when the user enabled our disabled its local video.
How might your application react to the event?
Your application should show a preview of the local video and enable or disable the camera activation button.
Code Sample:
call.on('isLocalVideoStartedChanged', () => {
showdDisableCameraButton(call.isLocalVideoStarted);
});
Event: remoteParticipantsUpdated
Your application should subscribe to event for each added RemoteParticipants
and unsubscribe of events for participants that have left the call.
How might your application react to the event? Your application should show a preview of the local video and enable or disable the camera activation button.
Code Sample:
call.on('remoteParticipantsUpdated', (remoteParticipantsUpdatedEvent) => {
remoteParticipantsUpdatedEvent.added.forEach(participant => {
// handleParticipant should
// - subscribe to the remote participants events
// - update the UI
handleParticipant(participant);
});
remoteParticipantsUpdatedEvent.removed.forEach(participant => {
// removeParticipant should
// - unsubcribe from the remote participants events
// - update the UI
removeParticipant(participant);
});
});
Event: localVideoStreamsUpdated
The localVideoStreamsUpdated
event is fired when the list of local video stream changes. These changes happen when the user starts or remove a video stream.
How might your application react to the event?
Your application should show previews for each of LocalVideoStream
added. Your application should remove the preview and stop the processing for each LocalVideoStream
removed.
Code Sample:
call.on('localVideoStreamsUpdated', (localVideoStreamUpdatedEvent) => {
localVideoStreamUpdatedEvent.added.forEach(addedLocalVideoStream => {
// Add a preview and start any processing if needed
handleAddedLocalVideoStream(addedLocalVideoStream)
});
localVideoStreamUpdatedEvent.removed.forEach(removedLocalVideoStream => {
// Remove the preview and stop any processing if needed
this.handleRemovedLocalVideoStream(removedLocalVideoStream)
});
});
Event: remoteAudioStreamsUpdated
The remoteAudioStreamsUpdated
event is fired when the list of remote audio stream changes. These changes happen when remote participants add or remove audio streams to the call.
How might your application react to the event?
If a stream was being processed and is now removed, the processing should be stopped. On the other hand, if a stream is added then the event reception is a good place to start the processing of the new audio stream.
Event: totalParticipantCountChanged
The totalParticipantCountChanged
fires when the number of totalParticipant changed in a call.
How might your application react to the event?
If your application is displaying a participant counter, your application can update its participant counter when the event is received.
Code Sample:
call.on('totalParticipantCountChanged', () => {
participantCounterElement.innerText = call.totalParticipantCount;
});
Event: roleChanged
The roleChanged
participant fires when the localParticipant roles changes in the call. An example would be when the local participant become presenter ACSCallParticipantRolePresenter
in a call.
How might your application react to the event? Your application should enable or disabled button base on the user new role.
Code Sample:
call.on('roleChanged', () => {
this.roleElement = call.role;
});
Event: mutedByOthers
The mutedByOthers
event happens when other participants in the call are muted by the local participant.
How might your application react to the event? Your application should display a message to the user notifying it was muted.
Code Sample:
call.on('mutedByOthers', () => {
messageBanner.innerText = "You have been muted by other participant in this call";
});
Event: callerInfoChanged
The callerInfoChanged
event happens when caller information was updated.
How might your application react to the event? Application can update caller information.
Code Sample:
call.on('callerInfoChanged', () => {
showCallerInfo(call.callerInfo)
});
Event: transferorInfoChanged
The transferorInfoChanged
event happens when transferor information was updated.
How might your application react to the event? Application can update transferor information.
Code Sample:
call.on('transferorInfoChanged', () => {
showTransferorInfo(call.transferorInfo)
});
Events on the RemoteParticipant
object
Event: roleChanged
The roleChanged
event fires when the RemotePartipant
role changes in the call. An example would be when the RemoteParticipant become presenter ACSCallParticipantRolePresenter
in a call.
How might your application react to the event?
Your application should update its UI based on the RemoteParticipant
new role.
Code Sample:
remoteParticipant.on('roleChanged', () => {
updateRole(remoteParticipant);
});
Event: isMutedChanged
The isMutedChanged
event fires when one of the RemoteParticipant
mutes or unmute its microphone.
How might your application react to the event?
Your application may display an icon near by the view that displays the participant.
Code Sample:
remoteParticipant.on('isMutedChanged', () => {
updateMuteStatus(remoteParticipant); // Update the UI based on the mute state of the participant
});
Event: displayNameChanged
The displayNameChanged
when the name of the RemoteParticipant
is updated.
How might your application react to the event?
Your application should update the name of the participant if it's being displayed in the UI.
Code Sample:
remoteParticipant.on('displayNameChanged', () => {
remoteParticipant.nameLabel.innerText = remoteParticipant.displayName;
});
Event: isSpeakingChanged
The isSpeakingChanged
when the dominant speaker in a call changes.
How might your application react to the event?
Your application UI should give priority to display the RemotePartipant
who became dominant speaker.
Code Sample:
remoteParticipant.on('isSpeakingChanged', () => {
showAsRemoteSpeaker(remoteParticipant) // Display a speaking icon near the participant
});
Event: videoStreamsUpdated
The videoStreamsUpdated
when a remote participant adds or removes a VideoStream to/from the call.
How might your application react to the event?
If your application was processing a stream that is removed. Your application should stop the processing. When a new stream is added, your application may want to render or process it.
Code Sample:
remoteParticipant.on('videoStreamsUpdated', (videoStreamsUpdatedEvent) => {
videoStreamsUpdatedEvent.added.forEach(addedRemoteVideoStream => {
// Remove a renderer and start processing the stream if any processing is needed
handleAddedRemoteVideoStream(addedRemoteVideoStream)
});
videoStreamsUpdatedEvent.removed.forEach(removedRemoteVideoStream => {
// Remove the renderer and stop processing the stream if any processing is ongoing
this.handleRemovedRemoteVideoStream(removedRemoteVideoStream)
});
});
Event on the AudioEffectsFeature
object
Event: effectsStarted
This event occurs when the audio effect selected is applied to the audio stream. For example, when someone turns on Noise Suppression the effectsStarted
will be fired.
How might your application react to the event?
Your application can display or enable a button that allows the user to disable the audio effect.
Code Sample:
audioEffectsFeature.on('effectsStarted', (effects) => {
stopEffectButton.style.visibility = "visible";
});
Event: effectsStopped
This event occurs when the audio effect selected is applied to the audio stream. For example, when someone turns off Noise Suppression the effectsStopped
will be fired.
How might your application react to the event?
Your application can display or enable a button that allows the user to enable the audio effect.
Code Sample:
audioEffectsFeature.on('effectsStopped', (effects) => {
startEffectButton.style.visibility = "visible";
});
Event: effectsError
This event occurs when an error happens while an audio effect is started or applied.
How might your application react to the event?
Your application should display an alert or an error message that the audio effect isn't working as expected.
Code Sample:
audioEffectsFeature.on('effectsError', (error) => {
console.log(`Error with the audio effect ${error}`);
alert(`Error with the audio effect`);
});
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();
With our Android SDK, you can subscribe to most of the properties and collections to be notified when values change.
Properties
To subscribe to property changed
events:
// subscribe
PropertyChangedListener callStateChangeListener = new PropertyChangedListener()
{
@Override
public void onPropertyChanged(PropertyChangedEvent args)
{
Log.d("The call state has changed.");
}
}
call.addOnStateChangedListener(callStateChangeListener);
//unsubscribe
call.removeOnStateChangedListener(callStateChangeListener);
When you use event listeners that are defined within the same class, bind the listener to a variable. Pass the variable in as an argument to add and remove listener methods.
If you try to pass the listener in directly as an argument, you'll lose the reference to that listener. Java is creating new instances of these listeners and not referencing previously created ones. They'll still fire off properly but can’t be removed because you won’t have a reference to them anymore.
Collections
To subscribe to collection updated
events:
LocalVideoStreamsChangedListener localVideoStreamsChangedListener = new LocalVideoStreamsChangedListener()
{
@Override
public void onLocalVideoStreamsUpdated(LocalVideoStreamsEvent localVideoStreamsEventArgs) {
Log.d(localVideoStreamsEventArgs.getAddedStreams().size());
Log.d(localVideoStreamsEventArgs.getRemovedStreams().size());
}
}
call.addOnLocalVideoStreamsChangedListener(localVideoStreamsChangedListener);
// To unsubscribe
call.removeOnLocalVideoStreamsChangedListener(localVideoStreamsChangedListener);
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")
}
})
With our iOS SDK, You can subscribe to most of the properties and collections to be notified when values change.
Properties
To subscribe to property changed
events, use the following code.
call.delegate = self
// Get the property of the call state by getting on the call's state member
public func call(_ call: Call, didChangeState args: PropertyChangedEventArgs) {
{
print("Callback from SDK when the call state changes, current state: " + call.state.rawValue)
}
// to unsubscribe
self.call.delegate = nil
Collections
To subscribe to collection updated
events, use the following code.
call.delegate = self
// Collection contains the streams that were added or removed only
public func call(_ call: Call, didUpdateLocalVideoStreams args: LocalVideoStreamsUpdatedEventArgs) {
{
print(args.addedStreams.count)
print(args.removedStreams.count)
}
// to unsubscribe
self.call.delegate = nil