Learn Azure Communication Services Day 8 – Microsoft Teams Meeting Interop
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.
Today, we’re going to look at one of the most exciting announcements that happened at Microsoft Ignite 2021 – Microsoft Teams Meeting Interop in Azure Communication Services. This interop allows an ACS client (such as the one we made in Day 5 or Day 7) to connect to a Microsoft Teams meeting, rather than another ACS user or group.
We’re going to take the Day 7 sample and make a very simple change to it to enable this to happen. This is the real value of how this interop feature has been implemented by Microsoft: if you already have an ACS application then changing it to work with Teams meetings is a very simple step indeed.
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:
index.html
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Learn ASC Day 8 - Microsoft Teams Meeting Interop</title> | |
</head> | |
<body> | |
<h2>Learn ASC Day 8 - Microsoft Teams Meeting Interop</h2> | |
<p>Call state <span style="font-weight: bold" id="call-state">-</span></p> | |
<input id="destination-group-input" type="text" placeholder="Microsoft Teams meeting join URL (https://teams.microsoft.com/l/meetup-join/....)" | |
style="margin-bottom:1em; width: 600px;" /> | |
<div> | |
<button id="connect-button" type="button" disabled="false"> | |
Connect | |
</button> | |
<button id="disconnect-button" type="button" disabled="true"> | |
Disconnect | |
</button> | |
</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 } 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 destinationGroupElement = document.getElementById('destination-group-input'); | |
let call; | |
let callAgent; | |
let callClient; | |
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; | |
} | |
init(); | |
connectButton.addEventListener("click", () => { | |
const destinationToCall = { meetingLink: destinationGroupElement.value}; | |
call = callAgent.join(destinationToCall); | |
call.on('stateChanged', () => { | |
callStateElement.innerText = call.state; | |
}) | |
// toggle button states | |
disconnectButton.disabled = false; | |
connectButton.disabled = true; | |
}); | |
disconnectButton.addEventListener("click", async () => { | |
await call.hangUp(); | |
// toggle button states | |
disconnectButton.disabled = true; | |
connectButton.disabled = false; | |
callStateElement.innerText = '-'; | |
}); | |
Testing it out
First, join an existing Microsoft Teams meeting using the Teams client. If you don’t have one handy you can use the Meet Now button to quickly make one. Make a note of the joining information, in particular, to Join URL by either clicking the “Get a link to share” button or looking in the meeting details once you are in the meeting. It should look like this:
https://teams.microsoft.com/l/meetup-join/XXXXXXX
This is the Web Join Url for the meeting and it’s what we will use to join the meeting from ACS.
As before, use a command-line to run this command then browse to the sample application in your browser (usually http://localhost:8080):
npx webpack-dev-server –entry ./client.js –output bundle.js –debug –devtool inline-source-map
Paste in the Web Join Url into the box and click Connect. Depending on your Teams meeting setting you should see the call state go to either Connected or InLobby.
Now, switch to your Teams client. If you have the lobby enabled for guest users, you’ll see this:
Once you admit them (or they go straight in) then they will show in Teams as a completely regular guest user, just as if they joined using the Teams web client:
What’s the code doing?
If you examine the code differences between today’s sample and Day 7 then you’ll see they are really small. It’s really just Line 28 of client.js which has changed. Rather than destinationToCall being an array containing a groupId entry, it now contains a meetingLink entry. This subtle difference is enough for ACS to know what to do with the call identifier and route it appropriately. As I said in the introduction, none of the rest of the code needs to change for Teams Interop to work!
One more thing: if you want to change the display name that’s used (rather than have it just say “Guest”) you can do that when you create the CallAgent (line 22). Simply pass the displayName as options:Â callAgent = await callClient.createCallAgent(tokenCredential, { displayName: ‘My Name’ });
Today, we took our existing ACS code and used it to join a Microsoft Teams meeting! Tomorrow we’re going to look at muting and un-muting audio. 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!).
Many thanks, Tom.
Does the ACS get any programmatic events back from the Teams meeting i.e when the meeting ends or other participants join or leave?
(The guest user will be able to observe this in the Teams client, but can the ACS app track it also)
“operationFailure”: {
“sender”: null,
“reason”: “unknown”,
“code”: 403,
“subCode”: 5222,
“phrase”: “Acs user is not allowed to create conversation.”,
“resultCategories”: [
“ExpectedError”
],
“acceptedElsewh
Hi I am getting- > sdk.bundle.js:95 POST https://api3.cc.skype.com/conv/ 403 and not connecting, any other configuration needs to be done.
sdk.bundle.js:95 POST https://api3.cc.skype.com/conv/ 403
I am getting below error message. when tried to setup a call. Have you faced this issue?
At this call “/conv”
{
“operationFailure”: {
“sender”: null,
“reason”: “unknown”,
“code”: 403,
“subCode”: 5222,
“phrase”: “Acs user is not allowed to create conversation.”,
“resultCategories”: [
“ExpectedError”
],
“acceptedElsewhereBy”: null
},
“CorrelationId”: “8345bc18-c79e-4354-bf1d-3cffa632de11”
}
Hi Tom,
Can we call teams user from ACS App instead of joining the teams meeting from ACS App?