Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added connect call quick start with create room api. #183

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions ConnectCall-CallAutomation/ConnectCall-CallAutomation.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>ConnectCall_CallAutomation</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Communication.CallAutomation" Version="1.4.0-alpha.20250106.1" />
<PackageReference Include="Azure.Communication.Identity" Version="1.3.1" />
<PackageReference Include="Azure.Communication.Rooms" Version="1.1.1" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="4.28.0" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions ConnectCall-CallAutomation/ConnectCall-CallAutomation.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35431.28
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConnectCall-CallAutomation", "ConnectCall-CallAutomation.csproj", "{E9E2AD2E-529A-4772-83C1-5216729D6A00}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E9E2AD2E-529A-4772-83C1-5216729D6A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9E2AD2E-529A-4772-83C1-5216729D6A00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9E2AD2E-529A-4772-83C1-5216729D6A00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9E2AD2E-529A-4772-83C1-5216729D6A00}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A285B16B-A63B-4164-AA65-5AD17C230D32}
EndGlobalSection
EndGlobal
198 changes: 198 additions & 0 deletions ConnectCall-CallAutomation/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using Azure;
using Azure.Communication;
using Azure.Communication.CallAutomation;
using Azure.Communication.Identity;
using Azure.Communication.Rooms;
using Azure.Core;
using Azure.Messaging;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles();

//Get ACS Connection String from appsettings.json
var acsConnectionString = builder.Configuration.GetValue<string>("AcsConnectionString");
ArgumentNullException.ThrowIfNullOrEmpty(acsConnectionString);

//Get acs phone number from appsettings.json
var acsPhoneNumber = builder.Configuration.GetValue<string>("AcsPhoneNumber");
ArgumentNullException.ThrowIfNullOrEmpty(acsPhoneNumber);

//Get participant phone number from appsettings.json
var participantPhoneNumber = builder.Configuration.GetValue<string>("ParticipantPhoneNumber");
ArgumentNullException.ThrowIfNullOrEmpty(participantPhoneNumber);

//Get Dev Tunnel Uri from appsettings.json
var callbackUriHost = builder.Configuration.GetValue<string>("CallbackUriHost");
ArgumentNullException.ThrowIfNullOrEmpty(callbackUriHost);

//Call back URL
var callbackUri = new Uri(new Uri(callbackUriHost), "/api/callbacks");

string callConnectionId = string.Empty;

string roomId = string.Empty;

CallAutomationClient callAutomationClient;
callAutomationClient = new CallAutomationClient(acsConnectionString);

app.MapPost("/createRoom", async (ILogger<Program> logger) =>
{
// create RoomsClient
var roomsClient = new RoomsClient(acsConnectionString);
var roomParticipants = new List<RoomParticipant>();

//create CommunicationIdentityClient
var IdentityClient = new CommunicationIdentityClient(acsConnectionString);
var scopes = new List<string> { "chat", "voip" };
var user1 = IdentityClient.CreateUser();
Response<AccessToken> user1Token = await IdentityClient.GetTokenAsync(user1.Value, scopes: scopes.Select(x => new CommunicationTokenScope(x)));

var user2 = IdentityClient.CreateUser();
Response<AccessToken> user2Token = await IdentityClient.GetTokenAsync(user2.Value, scopes: scopes.Select(x => new CommunicationTokenScope(x)));

string presenter = user1.Value.RawId;
string attendee = user2.Value.RawId;

var participant1 = new RoomParticipant(new CommunicationUserIdentifier(presenter))
{
Role = ParticipantRole.Presenter
};
var participant2 = new RoomParticipant(new CommunicationUserIdentifier(attendee))
{
Role = ParticipantRole.Attendee
};
roomParticipants.Add(participant1);
roomParticipants.Add(participant2);

var options = new CreateRoomOptions()
{
PstnDialOutEnabled = true,
Participants = roomParticipants,
ValidFrom = DateTime.UtcNow,
ValidUntil = DateTime.UtcNow.AddMinutes(30)
};

var response = await roomsClient.CreateRoomAsync(options);
roomId = response.Value.Id;
logger.LogInformation($"ROOM ID: {response.Value.Id}");
return new
{
user1Id = user1.Value.RawId,
User1Token = user1Token.Value.Token,
user2Id = user2.Value.RawId,
User2Token = user2Token.Value.Token,
RoomId = response.Value.Id
};
});

app.MapPost("/connectCall", async (ILogger<Program> logger) =>
{
if (!string.IsNullOrEmpty(roomId))
{
CallLocator callLocator = new RoomCallLocator(roomId);

ConnectCallOptions connectCallOptions = new ConnectCallOptions(callLocator, callbackUri);

ConnectCallResult result = await callAutomationClient.ConnectCallAsync(connectCallOptions);
logger.LogInformation($"CALL CONNECTION ID : {result.CallConnectionProperties.CallConnectionId}");
callConnectionId = result.CallConnectionProperties.CallConnectionId;
}
else
{
throw new ArgumentNullException(nameof(roomId));
}
});

app.MapPost("/api/callbacks", (CloudEvent[] cloudEvents, ILogger<Program> logger) =>
{
foreach (var cloudEvent in cloudEvents)
{
CallAutomationEventBase parsedEvent = CallAutomationEventParser.Parse(cloudEvent);
if (parsedEvent != null)
{
logger.LogInformation(
"Received call event: {type}, callConnectionID: {connId}, serverCallId: {serverId}",
parsedEvent.GetType(),
parsedEvent.CallConnectionId,
parsedEvent.ServerCallId);
}

if (parsedEvent is CallConnected callConnected)
{
logger.LogInformation($"Received call event: {callConnected.GetType()}");
callConnectionId = callConnected.CallConnectionId;
CallConnectionProperties callConnectionProperties = GetCallConnectionProperties();
logger.LogInformation($"CORRELATION ID: {callConnectionProperties.CorrelationId}");
}
else if (parsedEvent is ConnectFailed connectFailed)
{
callConnectionId = connectFailed.CallConnectionId;
logger.LogInformation($"Received call event: {connectFailed.GetType()}, CorrelationId: {connectFailed.CorrelationId}, " +
$"subCode: {connectFailed.ResultInformation?.SubCode}, message: {connectFailed.ResultInformation?.Message}, context: {connectFailed.OperationContext}");
}
else if (parsedEvent is AddParticipantSucceeded addParticipantSucceeded)
{
logger.LogInformation($"Received call event: {addParticipantSucceeded.GetType()}");
callConnectionId = addParticipantSucceeded.CallConnectionId;
}
else if (parsedEvent is AddParticipantFailed addParticipantFailed)
{
callConnectionId = addParticipantFailed.CallConnectionId;
logger.LogInformation($"Received call event: {addParticipantFailed.GetType()}, CorrelationId: {addParticipantFailed.CorrelationId}, " +
$"subCode: {addParticipantFailed.ResultInformation?.SubCode}, message: {addParticipantFailed.ResultInformation?.Message}, context: {addParticipantFailed.OperationContext}");
}
else if (parsedEvent is CallDisconnected callDisconnected)
{
logger.LogInformation($"Received call event: {callDisconnected.GetType()}");
}
}
return Results.Ok();
}).Produces(StatusCodes.Status200OK);

app.MapPost("/addParticipant", async (ILogger<Program> logger) =>
{
CallInvite callInvite;

CallConnection callConnection = GetConnection();

string operationContext = "addPSTNUserContext";
callInvite = new CallInvite(new PhoneNumberIdentifier(participantPhoneNumber),
new PhoneNumberIdentifier(acsPhoneNumber));

var addParticipantOptions = new AddParticipantOptions(callInvite)
{
OperationContext = operationContext,
InvitationTimeoutInSeconds = 30,
OperationCallbackUri = callbackUri
};

await callConnection.AddParticipantAsync(addParticipantOptions);
return Results.Ok();
});

app.MapPost("/hangUp", async (ILogger<Program> logger) =>
{
CallConnection callConnection = GetConnection();
await callConnection.HangUpAsync(true);
return Results.Ok();
});

CallConnection GetConnection()
{
CallConnection callConnection = !string.IsNullOrEmpty(callConnectionId) ?
callAutomationClient.GetCallConnection(callConnectionId)
: throw new ArgumentNullException("Call connection id is empty");
return callConnection;
}

CallConnectionProperties GetCallConnectionProperties()
{
CallConnectionProperties callConnectionProperties = !string.IsNullOrEmpty(callConnectionId) ?
callAutomationClient.GetCallConnection(callConnectionId).GetCallConnectionProperties()
: throw new ArgumentNullException("Call connection id is empty");
return callConnectionProperties;
}

app.Run();
13 changes: 13 additions & 0 deletions ConnectCall-CallAutomation/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"ConnectCall-CallAutomation": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:8080"
}
}
}
8 changes: 8 additions & 0 deletions ConnectCall-CallAutomation/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
13 changes: 13 additions & 0 deletions ConnectCall-CallAutomation/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"CallbackUriHost": "<DEV_TUNNEL_URI>",
"AcsConnectionString": "<ACS_CONNECTION_STRING>",
"AcsPhoneNumber": "<ACS_PHONE_NUMBER>",
"ParticipantPhoneNumber": "<PARTICIPANT_PHONE_NUMBER>"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectCall-CallAutomation/data/connectCall.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectCall-CallAutomation/data/createRoom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectCall-CallAutomation/data/joinRoomCall.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectCall-CallAutomation/data/roomId.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectCall-CallAutomation/data/tokens.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions ConnectCall-CallAutomation/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

# Project Title

A brief description of what this project does and who it's for

|page_type|languages|products
|---|---|---|
|sample|<table><tr><td>csharp</tr></td></table>|<table><tr><td>azure</td><td>azure-communication-services</td></tr></table>|

# Connect Call - Quick Start Sample

In this quickstart, we cover how you can use Call Automation SDK to connect to active room call with connect endpoint. Creating room with users and enabling pstn dialout to add pstn participant.
Creating room call with room id.

## Prerequisites

- Create an Azure account with an active subscription. For details, see [Create an account for free](https://azure.microsoft.com/free/)
- Create an Azure Communication Services resource. For details, see [Create an Azure Communication Resource](https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource). You will need to record your resource **connection string** for this sample.
- Get a phone number for your new Azure Communication Services resource. For details, see [Get a phone number](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number?tabs=windows&pivots=programming-language-csharp)

- To know about rooms see https://learn.microsoft.com/en-us/azure/communication-services/concepts/rooms/room-concept
- To join room call see https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/rooms/join-rooms-call?pivots=platform-web

## Before running the sample for the first time

1. Open an instance of PowerShell, Windows Terminal, Command Prompt or equivalent and navigate to the directory that you would like to clone the sample to.
2. git clone `https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git`.
3. Navigate to `ConnectCall-CallAutomation` folder and open `ConnectCall-CallAutomation.sln` file.

## Before running calling rooms quickstart
1. To initiate rooms call with room id https://github.com/Azure-Samples/communication-services-javascript-quickstarts/tree/main/calling-rooms-quickstart
2. cd into the `calling-rooms-quickstart` folder.
3. From the root of the above folder, and with node installed, run `npm install`
4. to run sample `npx webpack serve --config webpack.config.js`

### Setup and host your Azure DevTunnel

[Azure DevTunnels](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/overview) is an Azure service that enables you to share local web services hosted on the internet. Use the commands below to connect your local development environment to the public internet. This creates a tunnel with a persistent endpoint URL and which allows anonymous access. We will then use this endpoint to notify your application of calling events from the ACS Call Automation service.

```bash
devtunnel create --allow-anonymous
devtunnel port create -p 8080
devtunnel host
```

### Configuring application

Open the appsettings.json file to configure the following settings

1. `CallbackUriHost`: Base url of the app. (For local development replace the above dev tunnel url from the above for the port 8080).
2. `AcsConnectionString`: Azure Communication Service resource's connection string.
3. `AcsPhoneNumber`: Phone number associated with the Azure Communication Service resource. For e.g. "+1425XXXAAAA"
4. `ParticipantPhoneNumber`: Participant phone number. For e.g. "+1425XXXAAAA"

### Run app locally

1. Run the `ConnectCall-CallAutomation` project with `dotnet run`.
2. Browser should pop up with the below page. If not navigate it to `http://localhost:8080/`
3. To connect rooms call, click on the `Connect a call!` button or make a Http get request to https://<CALLBACK_URI>/connectCall

### Creating and connecting to room call.

1. ![create room with user](./data/createRoom.png)
2. Open two tabs for Presenter and attendee ![calling room quickstart](./data/callingRoomQuickstart.png)
3. Copy tokens for presenter and attendee from ![tokens](./data/tokens.png)
4. Initialize call agent with tokens for both presenter and attendee.
5. Take room id ![room id](./data/roomId.png) and initiate rooms call for both users. ![join room call](./data/joinRoomCall.png)
6. Connect room call with call automation connect call endpoint. ![connect room call](./data/connectCall.png)

Loading