6/16/2021 Admin

Blazor Azure Communication Services


NOTE: The sample code is out of date and contains components that have been deprecated

image

You can make a Microsoft Blazor application that leverages Azure Communications Services.

Azure Communication Services allows you to build custom communication applications using the same platform used by Microsoft Teams. You can implement solutions using video, audio, SMS (Texting) and web base text chat. It also allows Microsoft Teams interoperability.

The Application

(Note: At the time of this article, this only works in Google Chrome or Microsoft Edge web browsers).

After creating an Azure Communication Service (see: Create a Communication Services resource), and obtaining a connection string (see: this link), download he sample application from the Downloads page on this site.

image

On a computer, that has a web camera, unzip the files and open it in Visual Studio.

Paste the connection string in the CommunicationsConnectionString property in the appsettings.json file.

Note: You will also need to install Node.js.

Hit F5 to run the application in Visual Studio.

image

When the application displays, click the Get Token button.

image

Copy the Identity ID that is created that displays.

Keep the web browser open and the site running.

image

Open another instance of the application, on another computer , that has a camera, and follow the same process.

This time, paste the Identity ID that was displayed on the first computer, in the “Who would you like to call?” box, and click the start call button.

image

The two computers will connect to each other and you will be able to talk and see video.

Set Up Azure Communications Services

image

To run the sample code, first obtain a Microsoft Azure account with an active subscription (see: Create an account for free).

Next, create a Communication Services resource (see: Create a Communication Services resource).

image

Finally, copy the Connection string from the Keys section (see: this link).

Set Up Node JS

image

Install Node.js Active LTS and Maintenance LTS versions (8.11.1 and 10.14.1).

Creating The Blazor Application

image

In Visual Studio, we create a normal Server Side Blazor application.

We install the Azure.Communication.Identity NuGet package.

image

(Note: much of this was taken from How to Use NPM Packages in Blazor - Brian Lagunas)

Then add a folder called NpmJS.

image

We right-click on that folder and select Open Folder in File Explorer.

image

We can highlight the path…

image

Type CMD and press enter…

image

This will open up the command line.

Enter:

npm init –y

and press Enter.

image

This will create the package.json file.

Install NPM Packages

image

Next, type:

npm install webpack@4.42.0 webpack-cli@3.3.11 webpack-dev-server@3.10.3 --save-dev

								and press Enter.
							

This will create a node_modules folder (you will need to switch to “Show All files” in Visual Studio to see it), and install the required components.

image

Next, install the Azure Communication Services Calling SDK for JavaScript by entering:

npm install @azure/communication-common --save
and press Enter.

image

Type:

npm install @azure/communication-calling --save

and press Enter.

Add JavaScript Script

image

Create a src directory under the NpmJS folder, and add a index.js file using the following code:

								
									import
									{ CallClient, CallAgent, VideoStreamRenderer, LocalVideoStream } from "@azure/communication-calling";
									import
									{ AzureCommunicationTokenCredential } from '@azure/communication-common';

								
let call;
let callAgent;
									const
									calleeInput =
									document.getElementById("callee-id-input");
									const
									callButton =
									document.getElementById("call-button");
									const
									hangUpButton =
									document.getElementById("hang-up-button");
									const
									stopVideoButton =
									document.getElementById("stop-Video");
									const
									startVideoButton =
									document.getElementById("start-Video");

								
let placeCallOptions;
let deviceManager;
let localVideoStream;
let rendererLocal;
let rendererRemote;

								
									window.showAlert = async
									function
									(message) {
									await
									alert(message);
}

								
									window.init = async
									function
									(USER_ACCESS_TOKEN) {
									const
									callClient =
									new
									CallClient();
									const
									tokenCredential =
									new
									AzureCommunicationTokenCredential(USER_ACCESS_TOKEN);
									callAgent = await callClient.createCallAgent(tokenCredential, { displayName: 'optional ACS user
									name' });

								
									// Receive an incoming call
								
									// To handle incoming calls you need to listen to the `incomingCall` event of `callAgent`.
									
								
									// Once there is an incoming call, you need to enumerate local cameras and construct
								
									// a `LocalVideoStream` instance to send a video stream to the other participant.
									
								
									// You also need to subscribe to`remoteParticipants` to handle remote video streams.You can
								
									// accept or reject the call through the `incomingCall` instance.
									
								
									callAgent.on('incomingCall', async e => {
									const
									videoDevices = await deviceManager.getCameras();
									const
									videoDeviceInfo = videoDevices[0];
									localVideoStream =
									new
									LocalVideoStream(videoDeviceInfo);
									localVideoView();

								
									stopVideoButton.disabled =
									false;
									callButton.disabled =
									true;
									hangUpButton.disabled =
									false;

								
									const
									addedCall = await e.incomingCall.accept({ videoOptions: { localVideoStreams: [localVideoStream] } });
									call = addedCall;

								
									subscribeToRemoteParticipantInCall(addedCall);
									});

								
									// Subscribe to call updates
								
									// You need to subscribe to the event when the remote participant ends the call to
									
								
									// dispose of video renderers and toggle button states.
								
									callAgent.on('callsUpdated', e => {
									e.removed.forEach(removedCall => {
									// dispose of video renders
								
									rendererLocal.dispose();
									rendererRemote.dispose();
									// toggle button states
								
									hangUpButton.disabled =
									true;
									callButton.disabled =
									false;
									stopVideoButton.disabled =
									true;
									})
									})

								
									deviceManager = await callClient.getDeviceManager();
									callButton.disabled =
									false;
									calleeInput.disabled =
									false;

								
									callButton.addEventListener("click", async () => {
									const
									videoDevices = await deviceManager.getCameras();
									const
									videoDeviceInfo = videoDevices[0];
									localVideoStream =
									new
									LocalVideoStream(videoDeviceInfo);
									placeCallOptions = { videoOptions: { localVideoStreams: [localVideoStream] } };

								
									localVideoView();
									stopVideoButton.disabled =
									false;
									startVideoButton.disabled =
									true;

								
									const
									userToCall = calleeInput.value;
									call = callAgent.startCall(
									[{ communicationUserId: userToCall }],
									placeCallOptions
									);

								
									subscribeToRemoteParticipantInCall(call);

								
									hangUpButton.disabled =
									false;
									callButton.disabled =
									true;
									calleeInput.disabled =
									true;
									});

								
									hangUpButton.addEventListener("click", async () => {
									// dispose of the renderers
								
									rendererLocal.dispose();
									rendererRemote.dispose();
									// end the current call
								
									await call.hangUp();
									// toggle button states
								
									hangUpButton.disabled =
									true;
									callButton.disabled =
									false;
									stopVideoButton.disabled =
									true;
									calleeInput.disabled =
									false;
									});

								
									stopVideoButton.addEventListener("click", async () => {
									await call.stopVideo(localVideoStream);
									rendererLocal.dispose();
									startVideoButton.disabled =
									false;
									calleeInput.disabled =
									false;
									stopVideoButton.disabled =
									true;
									});

								
									startVideoButton.addEventListener("click", async () => {
									await call.startVideo(localVideoStream);
									localVideoView();
									stopVideoButton.disabled =
									false;
									startVideoButton.disabled =
									true;
									calleeInput.disabled =
									true;
									});
}

								
									function
									handleVideoStream(remoteVideoStream) {
									remoteVideoStream.on('isAvailableChanged', async () => {
									if
									(remoteVideoStream.isAvailable) {
									remoteVideoView(remoteVideoStream);
									}
									else
									{
									rendererRemote.dispose();
									}
									});
									if
									(remoteVideoStream.isAvailable) {
									remoteVideoView(remoteVideoStream);
									}
}

								
									function
									subscribeToParticipantVideoStreams(remoteParticipant) {
									remoteParticipant.on('videoStreamsUpdated', e => {
									e.added.forEach(v => {
									handleVideoStream(v);
									})
									});
									remoteParticipant.videoStreams.forEach(v => {
									handleVideoStream(v);
									});
}

								
									function
									subscribeToRemoteParticipantInCall(callInstance) {
									callInstance.on('remoteParticipantsUpdated', e => {
									e.added.forEach(p => {
									subscribeToParticipantVideoStreams(p);
									})
									});
									callInstance.remoteParticipants.forEach(p => {
									subscribeToParticipantVideoStreams(p);
									})
}

								
async
									function
									localVideoView() {
									rendererLocal =
									new
									VideoStreamRenderer(localVideoStream);
									const
									view = await rendererLocal.createView();
									document.getElementById("myVideo").appendChild(view.target);
}

								
async
									function
									remoteVideoView(remoteVideoStream) {
									rendererRemote =
									new
									VideoStreamRenderer(remoteVideoStream);
									const
									view = await rendererRemote.createView();
									document.getElementById("remoteVideo").appendChild(view.target);
}

This is the JavaScript, that leverages the Azure Communications Services SDK, required by the application.

It will be called by C# code added later.

Update package.json File

Open the package.json file and alter it so the contents resemble the following:

								
{
									"name": "NpmJS",
									"version": "1.0.0",
									"description": "",
									"main": "index.js",
									"scripts": {
									"build": "webpack ./src/index.js --output-path ../wwwroot/js --output-filename index.bundle.js"
									},
									"keywords": [],
									"author": "",
									"license": "ISC",
									"devDependencies": {
									"webpack": "^4.42.0",
									"webpack-cli": "^3.3.11",
									"webpack-dev-server": "^3.10.3"
									},
									"dependencies": {
									"@azure/communication-calling": "^1.0.0",
									"@azure/communication-common": "^1.0.0"
									}
}

This basically adds a build step that will webpack JavaScript from the NpmJS/src/index.js file and compile it to wwwroot\js\index.bundle.js.

Reference JavaScript In _Host.cshtml

image

To allow the application to call the JavaScript in the resulting index.bundle.js file, open the _Host.cshtml file and add the following line before the closing </body> tag:

								
									<
									script
									src="js/index.bundle.js"
									>
									</
									script
									>
								

Set NPM Build In Project Settings

image

Right-click on the project node and select Edit Project File.

Add the following build step before the closing </Project> tag:

								
									<
									Target
									Name="PreBuild"
									BeforeTargets="PreBuildEvent"
									>
								
									<
									Exec
									Command="npm install"
									WorkingDirectory="NpmJS"
									/>
								
									<
									Exec
									Command="npm run build"
									WorkingDirectory="NpmJS"
									/>
								
									</
									Target
									>
								

Build the project.

image

The index.bundle.js file will be created (Note: The build will take a lot longer than usual because of the NPM build steps).

Add the .Razor Code

image

Finally, open the Index.razor page, and replace all the code with the following code:

								
@page "/"
								
@using
									Microsoft.Extensions.Configuration
								
@using
									Azure.Communication;
								
@using
									Azure.Communication.Identity
								
@inject IJSRuntime JSRuntime;
								
@inject IConfiguration _configuration

								
									<
									h4
									>Azure Communication Services</
									h4
									>
								
@if (CommunicationUser == null)
								
{
								
									<
									button
									class="btn btn-primary"
									@onclick="GetToken"
									>Get Token</
									button
									>
								
									<
									br
									/>
								
									<
									br
									/>
								
}
								
@if (CommunicationUser != null)
								
{
								
									<
									p
									>
									<
									b
									>Created an identity with ID:</
									b
									>
									@CommunicationUser.Id</
									p
									>
								
									<
									p
									>
								
									<
									b
									>Expires On:</
									b
									>
								
									@ExpiresOn.DateTime.ToShortDateString()
								
									@ExpiresOn.DateTime.ToShortTimeString()
								
									</
									p
									>
								
}
								
									<
									input
									id="callee-id-input"
								
									type="text"
								
									placeholder="Who would you like to call?"
								
									style="margin-bottom:1em; width: 200px;"
									disabled="true"
									/>
								

								
									<
									div
									>
								
									<
									button
									id="call-button"
									type="button"
									disabled="true"
									>
								
									start call
								
									</
									button
									>
								
									&nbsp;
								
									<
									button
									id="hang-up-button"
									type="button"
									disabled="true"
									>
								
									hang up
								
									</
									button
									>
								
									&nbsp;
								
									<
									button
									id="start-Video"
									type="button"
									disabled="true"
									>
								
									start video
								
									</
									button
									>
								
									&nbsp;
								
									<
									button
									id="stop-Video"
									type="button"
									disabled="true"
									>
								
									stop video
								
									</
									button
									>
								
									</
									div
									>
								
									<
									div
									>Local Video</
									div
									>
								
									<
									div
									style="height:200px; width:300px; background-color:black; position:relative;"
									>
								
									<
									div
									id="myVideo"
									@ref=myVideo
								
									style="background-color: black; position:absolute; top:50%; transform: translateY(-50%);"
									>
								
									</
									div
									>
								
									</
									div
									>
								
									<
									div
									>Remote Video</
									div
									>
								
									<
									div
									style="height:200px; width:300px; background-color:black; position:relative;"
									>
								
									<
									div
									id="remoteVideo"
									@ref=remoteVideo
								
									style="background-color: black; position:absolute; top:50%; transform: translateY(-50%);"
									>
								
									</
									div
									>
								
									</
									div
									>
								

								
@code {
								
									private
									ElementReference myVideo;
								
									private
									ElementReference remoteVideo;
								
									private
									CommunicationUserIdentifier CommunicationUser =
									null;
								
									private
									string
									CommunicationsToken;
								
									private
									DateTimeOffset ExpiresOn;
								

								
									private
									async
									void
									GetToken()
								
									{
								
									string
									CommunicationsConnectionString =
								
									_configuration.GetValue<string>("CommunicationsConnectionString");
								

								
									var client =
									new
									CommunicationIdentityClient(CommunicationsConnectionString);
								

								
									// Issue an identity and an access token with the "voip" scope for the new identity
								
									var identityAndTokenResponse = await client.CreateUserAndTokenAsync(
								
									scopes:
									new[] { CommunicationTokenScope.VoIP });
								

								
									CommunicationUser = identityAndTokenResponse.Value.User;
								
									CommunicationsToken = identityAndTokenResponse.Value.AccessToken.Token;
								
									ExpiresOn = identityAndTokenResponse.Value.AccessToken.ExpiresOn;
								

								
									StateHasChanged();
								

								
									await JSRuntime.InvokeVoidAsync("init", CommunicationsToken);
								

								
									await JSRuntime.InvokeVoidAsync("showAlert", CommunicationUser.Id);
								
									}
								
}

Links

Azure Communication Services | Microsoft Docs

Quickstart - Add video calling to your app (JavaScript) - An Azure Communication Services quickstart | Microsoft Docs

Tutorial - Prepare a web app for Azure Communication Services (Node.js) - An Azure Communication Services tutorial | Microsoft Docs

Teams meeting interoperability - An Azure Communication Services concept document | Microsoft Docs

How to Use NPM Packages in Blazor - Brian Lagunas

An error has occurred. This application may no longer respond until reloaded. Reload 🗙