Learn Azure Communication Services Day 10 – Showing and Hiding Video
This blog post is part of a series called “Learn ACS“, all about Microsoft Azure Communication Services. The series covers a high-level overview of capabilities and considerations, then dives into the development detail of using ACS in your application. Find the rest of the posts in the series at learnACS.dev.
In this blog post, we’re going to add video to our Day 5 audio-only sample. You could also use the Day 8 Teams Interop sample instead if you’d rather add video to that. The nice thing about the calling SDK in Azure Communication Services is that the code is the same regardless of where you are connecting to.
I’m going to assume that you have already followed all of the steps in Day 5 to get the sample code up and running. Replace the contents of index.html and client.js with these versions. We’ll talk about how they are different once everything is up and running. Don’t forget: on Line 18, replace the placeholder text with the full URL of your Azure Function created in Day 3, including the code parameter. Also, note there are additional import statements in this code, so if you’re adding to an earlier sample, be sure to include them:
index.html
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Learn ASC Day 10 - Showing and Hiding Video</title> | |
</head> | |
<body> | |
<h2>Learn ASC Day 10 - Showing and Hiding Video</h2> | |
<p>Call state <span style="font-weight: bold" id="call-state">-</span></p> | |
<input id="destination-user-input" type="text" placeholder="ACS User ID to call" value="8:echo123" | |
style="margin-bottom:1em; width: 300px;" /> | |
<div> | |
<button id="connect-button" type="button" disabled="false"> | |
Connect with Video | |
</button> | |
<button id="disconnect-button" type="button" disabled="true"> | |
Disconnect | |
</button> | |
<button id="startvideo-button" type="button" disabled="true"> | |
Start Video | |
</button> | |
<button id="stopvideo-button" type="button" disabled="true"> | |
Stop Video | |
</button> | |
</div> | |
<div id="selfVideo"></div> | |
<hr/> | |
<p style="font-family: sans-serif">Hi, I'm Tom! I hope you found this code sample useful. This sample code comes from a series of blog posts about Azure Communication Services. Links for all blog posts can be found at <a href="https://learnacs.dev" target="_blank" rel="noopener">learnACS.dev</a>. I blog at <a href="https://blog.thoughtstuff.co.uk">thoughtstuff.co.uk</a>. You can also <a href="https://www.youtube.com/c/TomMorganTS?sub_confirmation=1" target="_blank" rel="noopener">subscribe to my YouTube channel</a> for videos about ACS (and much more!). </p> | |
<h4>Disclaimer: This is a sample. It’s not meant for you to take and use without fully understanding what it’s doing. It’s definitely not meant for production use. You should understand the risks of hosting your own ACS instance and associated web-based entry point on the public internet before proceeding. If you end up sharing your access tokens, or there’s a bug in the code and you end up with a huge hosting bill, or find yourself unwittingly hosting other people’s rooms, you’re on your own. This sample code is provided under the <a href="https://opensource.org/licenses/MIT">MIT license</a>, which you should read in full (it’s 21 LOC).</h4> | |
<script src="./bundle.js"></script> | |
</body> | |
</html> |
client.js
import { CallClient, CallAgent, DeviceManager, LocalVideoStream, Renderer } from "@azure/communication-calling"; | |
import { AzureCommunicationTokenCredential } from '@azure/communication-common'; | |
const connectButton = document.getElementById('connect-button'); | |
const disconnectButton = document.getElementById('disconnect-button'); | |
const callStateElement = document.getElementById('call-state'); | |
const destinationUserElement = document.getElementById('destination-user-input'); | |
const startVideoButton = document.getElementById('startvideo-button'); | |
const stopVideoButton = document.getElementById('stopvideo-button'); | |
let call; | |
let callAgent; | |
let callClient; | |
let localVideoStream; | |
let localVideoRender; | |
async function init() { | |
callClient = new CallClient(); | |
//get an access token to use | |
const response = await fetch('YOUR ACS TOKEN ISSUING WEB FUNCTION URL HERE (WITH THE CODE). SEE DAY 3'); | |
const responseJson = await response.json(); | |
const token = responseJson.value.item2.token; | |
const tokenCredential = new AzureCommunicationTokenCredential(token); | |
callAgent = await callClient.createCallAgent(tokenCredential); | |
connectButton.disabled = false; | |
//get all the cameras, then choose the first one | |
const deviceManager = await callClient.getDeviceManager(); | |
const videoDevices = await deviceManager.getCameras(); | |
const videoDeviceInfo = videoDevices[0]; | |
localVideoStream = new LocalVideoStream(videoDeviceInfo); | |
} | |
init(); | |
connectButton.addEventListener("click", () => { | |
const destinationToCall = { communicationUserId: destinationUserElement.value}; | |
const callOptions = {videoOptions: {localVideoStreams:[localVideoStream]}}; | |
call = callAgent.startCall([destinationToCall],callOptions); | |
call.on('stateChanged', () => { | |
callStateElement.innerText = call.state; | |
}) | |
showLocalFeed(); | |
// toggle button states | |
disconnectButton.disabled = false; | |
connectButton.disabled = true; | |
startVideoButton.disabled = false; | |
stopVideoButton.disabled = false; | |
}); | |
disconnectButton.addEventListener("click", async () => { | |
await call.hangUp(); | |
// toggle button states | |
disconnectButton.disabled = true; | |
connectButton.disabled = false; | |
callStateElement.innerText = '-'; | |
}); | |
startVideoButton.addEventListener("click", async () => { | |
await call.startVideo(localVideoStream); | |
showLocalFeed(); | |
}); | |
stopVideoButton.addEventListener("click", async () => { | |
await call.stopVideo(localVideoStream); | |
hideLocalFeed(); | |
}); | |
async function showLocalFeed() { | |
localVideoRender = new Renderer(localVideoStream); | |
const view = await localVideoRender.createView(); | |
document.getElementById("selfVideo").appendChild(view.target); | |
} | |
async function hideLocalFeed() { | |
localVideoRender.dispose(); | |
document.getElementById("selfVideo").innerHTML = ""; | |
} |
Testing it out
Make sure both index.html and client.js files are saved, then use this command-line command to build and run your application.
npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map
You can test out this sample using the EchoBot endpoint that’s the default in the calling text box, because your video is also shown locally. You could also connect to a Teams Meeting (see Day 8) and verify that video is shown in the Teams meeting.
What’s the code doing?
- This sample involves retrieving details of a local camera, which is done in lines 30-33 using the ACS DeviceManager, which is a nice abstraction to the collection of available video cameras. For the sample, I’m just choosing the first one but in a production system, you might provide users with a choice of available cameras. It’s also necessary to create a localVideoStream which is the actual video feed from the camera. This is used to send when making the call and also used in any local representation of the video feed.
- To start a call with video, create and pass an options object, shown in line 40.
- It’s completely possible to start a video call in ACS without ever showing the video locally, but unless there’s a good reason for it, it’s a bit unnerving and odd to not show the local video to the user. Once a localVideoStream has been created you can create a View and then attach it to a div (or any other HTML element that can accept child nodes). Lines 74-78 show this, with lines 80-83 removing it again once the Stop Video button has been clicked.
Today, we took our existing ACS code and added video functionality. Tomorrow we’re going to expand this sample by adding the ability to change devices. Links for all blog posts can be found at learnACS.dev. You can also subscribe to my YouTube channel for videos about ACS (and much more!).