Neglected Parking Meter

    @azure/communication-calling
    TypeScript icon, indicating that this package has built-in type declarations

    1.5.4 • Public • Published

    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

    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 an added array that contains values that were added to the collection.
    • The '<collection>Updated' event's payload also has a removed array that contains values that were removed from the collection.
    /************************************************
     * 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 and CallAgent.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 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 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 is Incoming 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 a Disconnected state.
    • Disconnected: Final call state. If the network connection is lost, the state changes to Disconnected 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 the CommunicationIdentifier 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 to Disconnected after two minutes.
    • callEndReason: To learn why a participant left the call, check the callEndReason 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 the isMuted property. It returns Boolean.

      const isMuted = remoteParticipant.isMuted;
    • isSpeaking status: To find out if a remote participant is speaking, check the isSpeaking property. It returns Boolean.

      const isSpeaking = remoteParticipant.isSpeaking;
    • videoStreams: To inspect all video streams that a given participant is sending in this call, check the videoStreams collection. It contains RemoteVideoStream objects.

      const videoStreams = remoteParticipant.videoStreams; // [RemoteVideoStream, ...]
    • displayName: To get display name for this remote participant, inspect displayName 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 be Video or ScreenSharing.

      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

    1. 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.
    2. 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.

    Keywords

    none

    Install

    npm i @azure/communication-calling

    DownloadsWeekly Downloads

    4,659

    Version

    1.5.4

    License

    Microsoft Software License Terms for the Azure Communications Services, Azure CPaaS, ACS Software Development Kit SDK, see file EULA.txt

    Unpacked Size

    7.04 MB

    Total Files

    8

    Last publish

    Collaborators

    • azure-sdk