Proposed Pull Request Change

title description author ms.author ms.date ms.topic ms.service
Quickstart - Make a call to Teams user from a web app In this tutorial, you learn how to make a call to Teams user using the Azure Communication Services Calling SDK for JavaScript ruslanzdor ruslanzdor 07/19/2023 include azure-communication-services
📄 Document Links
GitHub View on GitHub Microsoft Learn View on Microsoft Learn
Raw New Markdown
Generating updated version of doc...
Rendered New Markdown
Generating updated version of doc...
+0 -0
+0 -0
--- title: Quickstart - Make a call to Teams user from a web app description: In this tutorial, you learn how to make a call to Teams user using the Azure Communication Services Calling SDK for JavaScript author: ruslanzdor ms.author: ruslanzdor ms.date: 07/19/2023 ms.topic: include ms.service: azure-communication-services --- ## Sample Code If you'd like to skip ahead to the end, you can download this quickstart as a sample on [GitHub](https://github.com/Azure-Samples/communication-services-javascript-quickstarts/tree/main/add-1-on-1-cte-video-calling). ## Prerequisites - A working [Communication Services calling web app](../../getting-started-with-calling.md). - A [Teams deployment](/deployoffice/teams-install). - Teams users must have a Teams Phone license and be [Enterprise Voice enabled](/microsoftteams/direct-routing-enable-users#use-powershell-1) ## Add the Teams UI controls Replace code in index.html with following snippet. The text box is used to enter the Teams meeting context and the button is used to join the specified meeting: ```html <!-- 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-teams-call-agent" type="button">Initialize Call Agent</button> <br> <br> <input id="teams-user-id" type="text" placeholder="Enter callee's Teams user identity in format: '8:orgid:USER_GUID'" style="margin-bottom:1em; width: 500px; display: block;"/> <button id="start-call-button" type="button" disabled="true">Start Call</button> <button id="hangup-call-button" type="button" disabled="true">Hang up 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> ``` ## Enable the Teams UI controls Replace content of app.js file with following snippet. ```JavaScript // 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 teamsUserId = document.getElementById('teams-user-id'); let initializeCallAgentButton = document.getElementById('initialize-call-agent'); let startCallButton = document.getElementById('start-call-button'); let hangUpCallButton = document.getElementById('hangup-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'); /** * Create an instance of CallClient. Initialize a CallAgent instance with a AzureCommunicationTokenCredential via created CallClient. CallAgent enables us to make outgoing calls and receive incoming calls. * You can then use the CallClient.getDeviceManager() API instance to get the DeviceManager. */ 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); } } /** * Place a 1:1 outgoing video call to a Teams user * Add an event listener to initiate a call when the `startCallButton` is selected. * Enumerate local cameras using the deviceManager `getCameraList` API. * In this quickstart, we're using the first camera in the collection. Once the desired camera is selected, a * LocalVideoStream instance will be constructed and passed within `videoOptions` as an item within the * localVideoStream array to the call method. When the call connects, your application will be sending a video stream to the other participant. */ startCallButton.onclick = async () => { try { const localVideoStream = await createLocalVideoStream(); const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined; call = teamsCallAgent.startCall([{ microsoftTeamsUserId: teamsUserId.value.trim() }], { videoOptions: videoOptions }); // Subscribe to the call's properties and events. subscribeToCall(call); } catch (error) { console.error(error); } } /** * Accepting an incoming call with a video * Add an event listener to accept a call when the `acceptCallButton` is selected. * You can accept incoming calls after subscribing to the `CallAgent.on('incomingCall')` event. * You can pass the local video stream to accept the call with the following code. */ 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 updates. subscribeToCall = (call) => { try { // Inspect the initial call.id value. console.log(`Call Id: ${call.id}`); //Subscribe 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; hangUpCallButton.disabled = false; startVideoButton.disabled = false; stopVideoButton.disabled = false; } else if (call.state === 'Disconnected') { connectedLabel.hidden = true; startCallButton.disabled = false; hangUpCallButton.disabled = true; startVideoButton.disabled = true; stopVideoButton.disabled = true; console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`); } }); call.on('isLocalVideoStartedChanged', () => { console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`); }); console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`); 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 updates. 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 remoteParticipant adds new videoStreams and removes video streams. remoteParticipant.on('videoStreamsUpdated', e => { // Subscribe to newly added remote participant's video streams. e.added.forEach(remoteVideoStream => { subscribeToRemoteVideoStream(remoteVideoStream) }); // Unsubscribe from newly removed remote participants' video streams. 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. * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property * changes to 'true' a remote participant is sending a stream. Whenever the availability of a remote stream changes * you can choose to destroy the whole 'Renderer' a specific 'RendererView' or keep them. Displaying RendererView without a video stream will result in a blank video frame. */ 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); } } /** * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then * create a new VideoStreamRendererView instance using the asynchronous createView() method. * You may then attach view.target to any UI element. */ // 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); } } // End the current call hangUpCallButton.addEventListener("click", async () => { // end the current call await call.hangUp(); }); ``` ## Run the code Run the following command to bundle your application host on a local webserver: ```console npx webpack serve --config webpack.config.js ``` Manual steps to setup the call: 1. Open your browser and navigate to http://localhost:8080/. 2. Enter a valid user access token. Refer to the [user access token documentation](../../../manage-teams-identity.md) if you don't already have access tokens available to use. 3. Click on the "Initialize Call Agent" buttons. 4. Enter the Teams user Object ID, and select the "Start Call" button. Application will start the outgoing call with given object ID.
Success! Branch created successfully. Create Pull Request on GitHub
Error: