diff --git a/CallRecording/Controllers/RecordingsController.cs b/CallRecording/Controllers/RecordingsController.cs
new file mode 100644
index 00000000..3fa033d2
--- /dev/null
+++ b/CallRecording/Controllers/RecordingsController.cs
@@ -0,0 +1,241 @@
+using Azure.Communication;
+using Azure.Communication.CallAutomation;
+using Azure.Messaging;
+using Azure.Messaging.EventGrid;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System;
+using System.Threading.Tasks;
+
+namespace RecordingApi.Controllers
+{
+ ///
+ /// Recording APIs
+ ///
+ [ApiController]
+ public class RecordingsController : ControllerBase
+ {
+ private readonly ILogger _logger;
+ private readonly CallAutomationClient _client;
+ private readonly IConfiguration _configuration;
+
+ // for simplicity using static values
+ private static string _serverCallId = "";
+ private static string _callConnectionId = "";
+ private static string _recordingId = "";
+ private static string _contentLocation = "";
+ private static string _deleteLocation = "";
+
+ ///
+ /// Initilize Recording
+ ///
+ ///
+ ///
+ public RecordingsController(IConfiguration configuration, ILogger logger)
+ {
+ _logger = logger;
+ _configuration = configuration;
+ _client = new CallAutomationClient(_configuration["ACSResourceConnectionString"]);
+ }
+
+ #region outbound call - an active call required for recording to start.
+
+ ///
+ /// Start outbound call, Run before start recording
+ ///
+ ///
+ ///
+ [HttpGet("OutboundCall")]
+ public async Task OutboundCall([FromQuery] string targetPhoneNumber)
+ {
+ targetPhoneNumber = targetPhoneNumber.Replace(" ", "+");
+ var callerId = new PhoneNumberIdentifier(_configuration["ACSAcquiredPhoneNumber"]);
+ var target = new PhoneNumberIdentifier(targetPhoneNumber);
+ var callInvite = new CallInvite(target, callerId);
+ var createCallOption = new CreateCallOptions(callInvite, new Uri(_configuration["BaseUri"] + "/api/callbacks"));
+
+ var response = await _client.CreateCallAsync(createCallOption).ConfigureAwait(false);
+ _callConnectionId = response.Value.CallConnection.CallConnectionId;
+
+ return Ok($"CallConnectionId: {_callConnectionId}");
+ }
+
+ #endregion
+
+ ///
+ /// Start Recording
+ ///
+ ///
+ ///
+ [HttpGet("StartRecording")]
+ public async Task StartRecordingAsync([FromQuery] string serverCallId)
+ {
+ try
+ {
+ _serverCallId = serverCallId ?? _client.GetCallConnection(_callConnectionId).GetCallConnectionProperties().Value.ServerCallId;
+ StartRecordingOptions recordingOptions = new StartRecordingOptions(new ServerCallLocator(_serverCallId));
+ var callRecording = _client.GetCallRecording();
+ var response = await callRecording.StartAsync(recordingOptions).ConfigureAwait(false);
+ _recordingId = response.Value.RecordingId;
+ return Ok($"RecordingId: {_recordingId}");
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ }
+
+ ///
+ /// Pause Recording
+ ///
+ ///
+ ///
+ [HttpPost("PauseRecording")]
+ public async Task PauseRecording([FromQuery] string recordingId)
+ {
+ _recordingId = recordingId ?? _recordingId;
+ var response = await _client.GetCallRecording().PauseAsync(_recordingId).ConfigureAwait(false);
+
+ _logger.LogInformation($"Pause Recording response -- > {response}");
+ return Ok();
+ }
+
+ ///
+ /// Resume Recording
+ ///
+ ///
+ ///
+ [HttpPost("ResumeRecording")]
+ public async Task ResumeRecordingAsync([FromQuery] string recordingId)
+ {
+ _recordingId = recordingId ?? _recordingId;
+ var response = await _client.GetCallRecording().ResumeAsync(_recordingId).ConfigureAwait(false);
+
+ _logger.LogInformation($"Resume Recording response -- > {response}");
+ return Ok();
+ }
+
+ ///
+ /// Stop Recording
+ ///
+ ///
+ ///
+ [HttpDelete("StopRecording")]
+ public async Task StopRecordingAsync([FromQuery] string recordingId)
+ {
+ _recordingId = recordingId ?? _recordingId;
+ var response = await _client.GetCallRecording().StopAsync(_recordingId).ConfigureAwait(false);
+
+ _logger.LogInformation($"StopRecordingAsync response -- > {response}");
+ return Ok();
+ }
+
+ ///
+ /// Get recording state
+ ///
+ ///
+ ///
+ [HttpGet("GetRecordingState")]
+ public async Task GetRecordingStateAsync([FromQuery] string recordingId)
+ {
+ _recordingId = recordingId ?? _recordingId;
+ var response = await _client.GetCallRecording().GetStateAsync(_recordingId).ConfigureAwait(false);
+
+ _logger.LogInformation($"GetRecordingStateAsync response -- > {response}");
+ return Ok();
+ }
+
+ ///
+ /// Download Recording
+ ///
+ ///
+ [HttpGet("DownloadRecording")]
+ public IActionResult DownloadRecording()
+ {
+ var callRecording = _client.GetCallRecording();
+ callRecording.DownloadTo(new Uri(_contentLocation), "Recording_File.wav");
+ return Ok();
+ }
+
+ ///
+ /// Delete Recording
+ ///
+ ///
+ [HttpDelete("DeleteRecording")]
+ public IActionResult DeleteRecording()
+ {
+ _client.GetCallRecording().Delete(new Uri(_deleteLocation));
+ return Ok();
+ }
+
+ #region call backs apis
+
+ ///
+ /// Web hook to receive the recording file update status event, [Do not call directly from Swagger]
+ ///
+ ///
+ ///
+ [HttpPost]
+ [Route("recordingFileStatus")]
+ public IActionResult RecordingFileStatus([FromBody] EventGridEvent[] eventGridEvents)
+ {
+ foreach (var eventGridEvent in eventGridEvents)
+ {
+ if (eventGridEvent.TryGetSystemEventData(out object eventData))
+ {
+ // Handle the webhook subscription validation event.
+ if (eventData is Azure.Messaging.EventGrid.SystemEvents.SubscriptionValidationEventData subscriptionValidationEventData)
+ {
+ var responseData = new Azure.Messaging.EventGrid.SystemEvents.SubscriptionValidationResponse
+ {
+ ValidationResponse = subscriptionValidationEventData.ValidationCode
+ };
+ return Ok(responseData);
+ }
+
+ if (eventData is Azure.Messaging.EventGrid.SystemEvents.AcsRecordingFileStatusUpdatedEventData statusUpdated)
+ {
+ _contentLocation = statusUpdated.RecordingStorageInfo.RecordingChunks[0].ContentLocation;
+ _deleteLocation = statusUpdated.RecordingStorageInfo.RecordingChunks[0].DeleteLocation;
+ }
+ }
+ }
+ return Ok($"Recording Download Location : {_contentLocation}, Recording Delete Location: {_deleteLocation}");
+ }
+
+ ///
+ /// Call backs for signalling events, [Do not call directly from swagger]
+ ///
+ ///
+ ///
+ [HttpPost]
+ [Route("/api/callbacks")]
+ public IActionResult Callbacks([FromBody] CloudEvent[] cloudEvents)
+ {
+ try
+ {
+ foreach (var cloudEvent in cloudEvents)
+ {
+ _logger.LogInformation($"Event received: {JsonConvert.SerializeObject(cloudEvent)}");
+ CallAutomationEventBase @event = CallAutomationEventParser.Parse(cloudEvent);
+
+ // for start recording we required server call id, so capture it when call connected.
+ if (@event is CallConnected)
+ {
+ _logger.LogInformation($"Server Call Id: {@event.ServerCallId}");
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(new { Exception = ex });
+ }
+ return Ok();
+ }
+
+ #endregion
+ }
+}
diff --git a/CallRecording/Program.cs b/CallRecording/Program.cs
new file mode 100644
index 00000000..2a91bdde
--- /dev/null
+++ b/CallRecording/Program.cs
@@ -0,0 +1,28 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.IO;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddControllers();
+
+builder.Services.AddSwaggerGen(c =>
+{
+ c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "RecordingApi.xml"));
+});
+
+var app = builder.Build();
+
+app.UseSwagger();
+app.UseSwaggerUI(c =>
+{
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
+});
+
+app.UseHttpsRedirection();
+app.UseRouting();
+app.UseAuthorization();
+app.MapControllers();
+
+app.Run();
diff --git a/ServerRecording/Properties/launchSettings.json b/CallRecording/Properties/launchSettings.json
similarity index 92%
rename from ServerRecording/Properties/launchSettings.json
rename to CallRecording/Properties/launchSettings.json
index 3ae1b7b1..4b02a8ab 100644
--- a/ServerRecording/Properties/launchSettings.json
+++ b/CallRecording/Properties/launchSettings.json
@@ -11,11 +11,12 @@
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
+ "launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
- "QuickStartApi": {
+ "RecordingApi": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
diff --git a/CallRecording/README.MD b/CallRecording/README.MD
new file mode 100644
index 00000000..93454ea1
--- /dev/null
+++ b/CallRecording/README.MD
@@ -0,0 +1,82 @@
+---
+Page Type: Sample
+Languages: C#
+Products: Azure.Communication.CallAutomation
+---
+
+# Recording APIs Sample
+
+This is a sample application to showcase how the Call Automation SDK can be used to add recording features to any application.
+This application, built on the .NET Core framework using C#, is seamlessly integrated with Azure Communication Services. It harnesses the power of Azure Communication Services to establish connections and enable communication features within the application.
+A separate branch with end to end implementation is [available](https://github.com/Azure-Samples/communication-services-web-calling-hero/tree/public-preview). It's a public preview branch and uses beta SDKs that are not meant for production use. Please use the main branch sample for any production scenarios.
+
+## Prerequisites
+- Create an Azure account with an active subscription. For details, see [Create an account for free](https://azure.microsoft.com/free/)
+- [Visual Studio (2022 and above)](https://visualstudio.microsoft.com/vs/)
+- [.NET7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) (Make sure to install version that corresponds with your visual studio instance, 32 vs 64 bit)
+- 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'll need to record your Communication Service resource **Connection string** under Keys section for this sample.
+- Enable Visual studio dev tunneling for local development. For details, see [Enable dev tunnel] (https://learn.microsoft.com/en-us/connectors/custom-connectors/port-tunneling)
+
+ - To enable dev tunneling, Click `Tools` -> `Options` in Visual Studio 2022. In the search bar type tunnel, Click the checkbox under `Environment` -> `Preview Features` called `Enable dev tunnels for Web Application`
+ 
+ - Create `Dev Tunnels` by followingg this link -> [Dev Tunnels.](https://learn.microsoft.com/en-us/aspnet/core/test/dev-tunnels?view=aspnetcore-7.0)
+ **Note:** For accessing the Dev Tunnel Url publicly change the Access to Public.
+
+
+## Clone the code local and update appsettings
+
+1. Open an instance of PowerShell, Windows Terminal, Command Prompt or equivalent and navigate to the directory that you'd like to clone the sample to.
+2. Run `git clone https://github.com/Azure-Samples/Communication-Services-dotnet-quickstarts.git`
+3. Once you get the code on local machine, navigate to **CallRecording/appsettings.json** file found under the CallRecording folder.
+4. Update the values for below.
+
+ | Key | Value | Description |
+ | -------- | -------- | -------- |
+ | `ACSResourceConnectionString` | \ | Input your ACS connection string in the variable |
+ | `ACSAcquiredPhoneNumber` | \ | Phone number associated with the Azure Communication Service resource |
+ | `BaseUri` | \ | Base url of the app, don't add `/` at end. For getting the dev tunnel url, run the app once. |
+
+## Locally Run the sample app
+
+1. Go to CallRecording folder and open `RecordingApi.csproj` solution in Visual Studio.
+2. Enable Visual Studio Dev Tunnels for local development (see pre-requisite section for enabling dev tunnel).
+3. Run the application in debug mode, swagger url should open, if swagger not opened by itself add `/swagger/index.html` at the end.
+
+## Create Webhook for Microsoft.Communication.RecordingFileStatus event
+Call Recording enables you to record multiple calling scenarios available in Azure Communication Services by providing you with a set of APIs to start, stop, pause and resume recording. To learn more about it, see [this guide](https://learn.microsoft.com/en-us/azure/communication-services/concepts/voice-video-calling/call-recording).
+1. Navigate to your Communication Service resource on Azure portal and select `Events` from the left side blade.
+2. Click `+ Event Subscription` to create a new subscription, provide `Name` field value.
+3. Under Topic details, choose a System Topic or create new, no changes required if its already have topic name.
+4. Under `Event Types` Filter for `Recording File Status Updated` event.
+5. Choose `Endpoint Type` as `Web Hook` and provide the public url generated by Dev Tunnels. It would look like `https://21pdf6lm-44348.usw2.devtunnels.ms/recordingFileStatus`.
+6. Click `Create` to complete the event grid subscription. The subscription is ready when the provisioning status is marked as succeeded.
+**Note:** Application should be running to able to create the `Web Hook` successfully.
+
+
+# Step by step guid for testing recording APIs via swagger.
+
+Once App is running local, you will see all list of exposed API on swagger.
+1. Step 1. Start a call invoke OutboundCall under Outbound section.
+ - Try it out `GET OutboundCall`, provide the Target PSTN phone number to get the call.
+ - `Execute`, accept the call on Target PSTN Phone number, Keep call running.
+2. Step 2. Start Recording.
+ - Try it out `GET StartRecording`, provide the serverCallId value, optional if recording the same call started in step1.
+ - `Execute`, recording would be started.
+3. Step 3. (Optional) Execute `POST PauseRecording` and then `POST ResumeRecording`, passing recordingId is optional.
+4. Step 4. Execute `DELETE StopReocording` for stop the recording.
+5. Step 5. Execute `GET DownloadRecording` for downloading the recording from server, only last recorded file will be downloaded.
+6. Step 6. Execute `DELETE DeleteRecording` for delete the recording at server.
+
+
+## Troubleshooting
+
+1. Solution doesn\'t build, it throws errors during build.
+ - Clean/rebuild the C# solution.
+2. Recording files not getting downloaded.
+ - Check for webhook settings if dev tunnel url is correct.
+
+## Additional Reading
+
+- [Azure Communication Calling SDK](https://docs.microsoft.com/azure/communication-services/concepts/voice-video-calling/calling-sdk-features) - To learn more about the calling web SDK.
+- [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-7.0) - Framework for building web applications
+
diff --git a/ServerRecording/RecordingApi.csproj b/CallRecording/RecordingApi.csproj
similarity index 63%
rename from ServerRecording/RecordingApi.csproj
rename to CallRecording/RecordingApi.csproj
index 9f0d929f..f2bfb19d 100644
--- a/ServerRecording/RecordingApi.csproj
+++ b/CallRecording/RecordingApi.csproj
@@ -1,14 +1,14 @@
- net6.0
+ net7.0
false
+ True
-
-
+
-
+
\ No newline at end of file
diff --git a/ServerRecording/RecordingApi.sln b/CallRecording/RecordingApi.sln
similarity index 100%
rename from ServerRecording/RecordingApi.sln
rename to CallRecording/RecordingApi.sln
diff --git a/CallRecording/Recording_File.wav b/CallRecording/Recording_File.wav
new file mode 100644
index 00000000..ec724ab8
Binary files /dev/null and b/CallRecording/Recording_File.wav differ
diff --git a/ServerRecording/appsettings.json b/CallRecording/appsettings.json
similarity index 67%
rename from ServerRecording/appsettings.json
rename to CallRecording/appsettings.json
index c5fcc24c..9bfb4ce8 100644
--- a/ServerRecording/appsettings.json
+++ b/CallRecording/appsettings.json
@@ -8,6 +8,6 @@
},
"AllowedHosts": "*",
"ACSResourceConnectionString": "%ACSResourceConnectionString%",
- "BlobStorageConnectionString": "%BlobStorageConnectionString%",
- "BlobContainerName": "%BlobContainerName%"
+ "ACSAcquiredPhoneNumber": "%ACSAcquiredPhoneNumber%",
+ "BaseUri": "%BaseUri%"
}
\ No newline at end of file
diff --git a/CallRecording/data/EnableDevTunnel.png b/CallRecording/data/EnableDevTunnel.png
new file mode 100644
index 00000000..8fc6fd24
Binary files /dev/null and b/CallRecording/data/EnableDevTunnel.png differ
diff --git a/ServerRecording/BlobStorageHelper.cs b/ServerRecording/BlobStorageHelper.cs
deleted file mode 100644
index 39331da5..00000000
--- a/ServerRecording/BlobStorageHelper.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using Azure.Storage.Blobs;
-using Azure.Storage.Blobs.Models;
-
-namespace QuickStartApi
-{
- public static class BlobStorageHelper
- {
- ///
- /// Method to check if Blob Storage Container exists or not
- ///
- /// Connection String details for Azure Blob Storage
- /// Container Name to upload files
- /// Boolean
- public static async Task IsContainerAvailableAsync(string connectionString, string containerName)
- {
- try
- {
- BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
- BlobContainerClient blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);
- return await blobContainerClient.ExistsAsync();
- }
- catch (Exception ex)
- {
- throw new Exception($"Exception checking the container availability. Exception: {ex.Message}");
- }
- }
- ///
- /// Method to check if Blob exists or not
- ///
- /// Connection String details for Azure Blob Storage
- /// Container Name to upload files
- /// File name
- /// Boolean
- public static async Task IsBlobAvailableAsync(string connectionString, string containerName, string blobName)
- {
- try
- {
- if (!await IsContainerAvailableAsync(connectionString, containerName)) return false;
- BlobClient blobClient = new BlobClient(connectionString, containerName, blobName);
- return await blobClient.ExistsAsync();
- }
- catch (Exception ex)
- {
- throw new Exception($"Exception checking the blob availability. Exception: {ex.Message}");
- }
- }
- ///
- /// Method to upload a file to Blob storage
- ///
- /// Connection String details for Azure Blob Storage
- /// Container Name to upload files
- /// File name
- /// File path of the file to upload
- /// BlobStorageHelperInfo
- public static async Task UploadFileAsync(string connectionString, string containerName, string blobName, string filePath)
- {
- BlobStorageHelperInfo blobStorageHelperInfo = new BlobStorageHelperInfo();
- try
- {
- //checking if container is available
- if (!await IsContainerAvailableAsync(connectionString, containerName))
- {
- blobStorageHelperInfo.Message = $"Container {containerName} is not available";
- blobStorageHelperInfo.Status = false;
- return blobStorageHelperInfo;
- }
-
- //checking if blob is already available
- if (await IsBlobAvailableAsync(connectionString, containerName, blobName))
- {
- blobStorageHelperInfo.Message = $"Blob \"{blobName}\" already exists";
- blobStorageHelperInfo.Status = false;
- return blobStorageHelperInfo;
- }
-
- //Upload blob
- BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
- BlobContainerClient blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);
- BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
-
- FileStream uploadFileStream = File.OpenRead(filePath);
- BlobContentInfo status = await blobClient.UploadAsync(uploadFileStream, true);
- uploadFileStream.Close();
- blobStorageHelperInfo.Message = $"File uploaded successfully. Uri : {blobClient.Uri}";
- blobStorageHelperInfo.Status = true;
- return blobStorageHelperInfo;
- }
- catch (Exception ex)
- {
- throw new Exception($"The file upload was not successful. Exception: {ex.Message}");
- }
- }
- }
-}
diff --git a/ServerRecording/Controller/CallRecordingController.cs b/ServerRecording/Controller/CallRecordingController.cs
deleted file mode 100644
index 0c62b287..00000000
--- a/ServerRecording/Controller/CallRecordingController.cs
+++ /dev/null
@@ -1,420 +0,0 @@
-using Azure.Communication.CallAutomation;
-using Microsoft.AspNetCore.Mvc;
-using Azure.Messaging.EventGrid;
-using Azure.Messaging.EventGrid.SystemEvents;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-
-namespace QuickStartApi.Controllers
-{
- [Route("/recording")]
- public class CallRecordingController : Controller
- {
- private readonly string blobStorageConnectionString;
- private readonly string containerName;
- private readonly CallAutomationClient callAutomationClient;
- private const string CallRecodingActiveErrorCode = "8553";
- private const string CallRecodingActiveError = "Recording is already in progress, one recording can be active at one time.";
- public ILogger Logger { get; }
- static Dictionary recordingData = new Dictionary();
- public static string recFileFormat;
-
- public CallRecordingController(IConfiguration configuration, ILogger logger)
- {
- blobStorageConnectionString = configuration["BlobStorageConnectionString"];
- containerName = configuration["BlobContainerName"];
- callAutomationClient = new CallAutomationClient(configuration["ACSResourceConnectionString"]);
- Logger = logger;
- }
-
- ///
- /// Method to start call recording
- ///
- /// Conversation id of the call
- [HttpGet]
- [Route("startRecording")]
- public async Task StartRecordingAsync(string serverCallId)
- {
- try
- {
- if (!string.IsNullOrEmpty(serverCallId))
- {
- //Passing RecordingContent initiates recording in specific format. audio/audiovideo
- //RecordingChannel is used to pass the channel type. mixed/unmixed
- //RecordingFormat is used to pass the format of the recording. mp4/mp3/wav
- StartRecordingOptions recordingOptions = new StartRecordingOptions(new ServerCallLocator(serverCallId));
- var startRecordingResponse = await callAutomationClient.GetCallRecording()
- .StartRecordingAsync(recordingOptions).ConfigureAwait(false);
-
- Logger.LogInformation($"StartRecordingAsync response -- > {startRecordingResponse.GetRawResponse()}, Recording Id: {startRecordingResponse.Value.RecordingId}");
-
- var recordingId = startRecordingResponse.Value.RecordingId;
- if (!recordingData.ContainsKey(serverCallId))
- {
- recordingData.Add(serverCallId, string.Empty);
- }
- recordingData[serverCallId] = recordingId;
-
- return Json(recordingId);
- }
- else
- {
- return BadRequest(new { Message = "serverCallId is invalid" });
- }
- }
- catch (Exception ex)
- {
- if (ex.Message.Contains(CallRecodingActiveErrorCode))
- {
- return BadRequest(new { Message = CallRecodingActiveError });
- }
- return Json(new { Exception = ex });
- }
- }
-
-
-//********** Replace above API with this if you want to start recording with additional arguments. *************
-
- ///
- /// Method to start call recording using given parameters
- ///
- /// Conversation id of the call
- /// Recording content type. audiovideo/audio
- /// Recording channel type. mixed/unmixed
- /// Recording format type. mp3/mp4/wav
- //[HttpGet]
- //[Route("startRecording")]
- public async Task StartRecordingAsync(string serverCallId, string recordingContent, string recordingChannel, string recordingFormat)
- {
- try
- {
- if (!string.IsNullOrEmpty(serverCallId))
- {
- RecordingContent recContentVal;
- RecordingChannel recChannelVal;
- RecordingFormat recFormatVal;
-
- //Passing RecordingContent initiates recording in specific format. audio/audiovideo
- //RecordingChannel is used to pass the channel type. mixed/unmixed
- //RecordingFormat is used to pass the format of the recording. mp4/mp3/wav
- StartRecordingOptions recordingOptions = new StartRecordingOptions(new ServerCallLocator(serverCallId));
- recordingOptions.RecordingChannel = Mapper.RecordingChannelMap.TryGetValue(recordingChannel, out recChannelVal) ? recChannelVal : RecordingChannel.Mixed;
- recordingOptions.RecordingContent = Mapper.RecordingContentMap.TryGetValue(recordingContent, out recContentVal) ? recContentVal : RecordingContent.AudioVideo;
- recordingOptions.RecordingFormat = Mapper.RecordingFormatMap.TryGetValue(recordingFormat, out recFormatVal) ? recFormatVal : RecordingFormat.Mp4;
-
- var startRecordingResponse = await callAutomationClient.GetCallRecording()
- .StartRecordingAsync(recordingOptions).ConfigureAwait(false);
-
- Logger.LogInformation($"StartRecordingAudioAsync response -- > {startRecordingResponse.GetRawResponse()}, Recording Id: {startRecordingResponse.Value.RecordingId}");
-
- var recordingId = startRecordingResponse.Value.RecordingId;
- if (!recordingData.ContainsKey(serverCallId))
- {
- recordingData.Add(serverCallId, string.Empty);
- }
- recordingData[serverCallId] = recordingId;
-
- return Json(recordingId);
- }
- else
- {
- return BadRequest(new { Message = "serverCallId is invalid" });
- }
- }
- catch (Exception ex)
- {
- if (ex.Message.Contains(CallRecodingActiveErrorCode))
- {
- return BadRequest(new { Message = CallRecodingActiveError });
- }
- return Json(new { Exception = ex });
- }
- }
-
- ///
- /// Method to pause call recording
- ///
- /// Conversation id of the call
- /// Recording id of the call
- [HttpGet]
- [Route("pauseRecording")]
- public async Task PauseRecordingAsync(string serverCallId, string recordingId)
- {
- try
- {
- if (!string.IsNullOrEmpty(serverCallId))
- {
- if (string.IsNullOrEmpty(recordingId))
- {
- recordingId = recordingData[serverCallId];
- }
- else
- {
- if (!recordingData.ContainsKey(serverCallId))
- {
- recordingData[serverCallId] = recordingId;
- }
- }
- var pauseRecording = await callAutomationClient.GetCallRecording().PauseRecordingAsync(recordingId);
- Logger.LogInformation($"PauseRecordingAsync response -- > {pauseRecording}");
-
- return Ok();
- }
- else
- {
- return BadRequest(new { Message = "serverCallId is invalid" });
- }
- }
- catch (Exception ex)
- {
- return Json(new { Exception = ex });
- }
- }
-
- ///
- /// Method to resume call recording
- ///
- /// Conversation id of the call
- /// Recording id of the call
- [HttpGet]
- [Route("resumeRecording")]
- public async Task ResumeRecordingAsync(string serverCallId, string recordingId)
- {
- try
- {
- if (!string.IsNullOrEmpty(serverCallId))
- {
- if (string.IsNullOrEmpty(recordingId))
- {
- recordingId = recordingData[serverCallId];
- }
- else
- {
- if (!recordingData.ContainsKey(serverCallId))
- {
- recordingData[serverCallId] = recordingId;
- }
- }
- var resumeRecording = await callAutomationClient.GetCallRecording().ResumeRecordingAsync(recordingId);
- Logger.LogInformation($"ResumeRecordingAsync response -- > {resumeRecording}");
-
- return Ok();
- }
- else
- {
- return BadRequest(new { Message = "serverCallId is invalid" });
- }
- }
- catch (Exception ex)
- {
- return Json(new { Exception = ex });
- }
- }
-
- ///
- /// Method to stop call recording
- ///
- /// Conversation id of the call
- /// Recording id of the call
- ///
- [HttpGet]
- [Route("stopRecording")]
- public async Task StopRecordingAsync(string serverCallId, string recordingId)
- {
- try
- {
- if (!string.IsNullOrEmpty(serverCallId))
- {
- if (string.IsNullOrEmpty(recordingId))
- {
- recordingId = recordingData[serverCallId];
- }
- else
- {
- if (!recordingData.ContainsKey(serverCallId))
- {
- recordingData[serverCallId] = recordingId;
- }
- }
-
- var stopRecording = await callAutomationClient.GetCallRecording().StopRecordingAsync(recordingId).ConfigureAwait(false);
- Logger.LogInformation($"StopRecordingAsync response -- > {stopRecording}");
-
- if (recordingData.ContainsKey(serverCallId))
- {
- recordingData.Remove(serverCallId);
- }
- return Ok();
- }
- else
- {
- return BadRequest(new { Message = "serverCallId is invalid" });
- }
- }
- catch (Exception ex)
- {
- return Json(new { Exception = ex });
- }
- }
-
- ///
- /// Method to get recording state
- ///
- /// Conversation id of the call
- /// Recording id of the call
- ///
- /// CallRecordingProperties
- /// RecordingState is {active}, in case of active recording
- /// RecordingState is {inactive}, in case if recording is paused
- /// 404:Recording not found, if recording was stopped or recording id is invalid.
- ///
- [HttpGet]
- [Route("getRecordingState")]
- public async Task GetRecordingState(string serverCallId, string recordingId)
- {
- try
- {
- if (!string.IsNullOrEmpty(serverCallId))
- {
- if (string.IsNullOrEmpty(recordingId))
- {
- recordingId = recordingData[serverCallId];
- }
- else
- {
- if (!recordingData.ContainsKey(serverCallId))
- {
- recordingData[serverCallId] = recordingId;
- }
- }
-
- var recordingState = await callAutomationClient.GetCallRecording().GetRecordingStateAsync(recordingId).ConfigureAwait(false);
-
- Logger.LogInformation($"GetRecordingStateAsync response -- > {recordingState}");
-
- return Json(recordingState.Value.RecordingState);
- }
- else
- {
- return BadRequest(new { Message = "serverCallId is invalid" });
- }
- }
- catch (Exception ex)
- {
- return Json(new { Exception = ex });
- }
- }
-
- ///
- /// Web hook to receive the recording file update status event
- ///
- ///
- ///
- [HttpPost]
- [Route("getRecordingFile")]
- public async Task GetRecordingFile([FromBody] object request)
- {
- try
- {
- var httpContent = new BinaryData(request.ToString()).ToStream();
- EventGridEvent cloudEvent = EventGridEvent.ParseMany(BinaryData.FromStream(httpContent)).FirstOrDefault();
-
- if (cloudEvent.EventType == SystemEventNames.EventGridSubscriptionValidation)
- {
- var eventData = cloudEvent.Data.ToObjectFromJson();
-
- Logger.LogInformation("Microsoft.EventGrid.SubscriptionValidationEvent response -- >" + cloudEvent.Data);
-
- var responseData = new SubscriptionValidationResponse
- {
- ValidationResponse = eventData.ValidationCode
- };
-
- if (responseData.ValidationResponse != null)
- {
- return Ok(responseData);
- }
- }
-
- if (cloudEvent.EventType == SystemEventNames.AcsRecordingFileStatusUpdated)
- {
- Logger.LogInformation($"Event type is -- > {cloudEvent.EventType}");
-
- Logger.LogInformation("Microsoft.Communication.RecordingFileStatusUpdated response -- >" + cloudEvent.Data);
-
- var eventData = cloudEvent.Data.ToObjectFromJson();
-
- Logger.LogInformation("Start processing metadata -- >");
-
- await ProcessFile(eventData.RecordingStorageInfo.RecordingChunks[0].MetadataLocation,
- eventData.RecordingStorageInfo.RecordingChunks[0].DocumentId,
- FileFormat.Json,
- FileDownloadType.Metadata);
-
- Logger.LogInformation("Start processing recorded media -- >");
-
- await ProcessFile(eventData.RecordingStorageInfo.RecordingChunks[0].ContentLocation,
- eventData.RecordingStorageInfo.RecordingChunks[0].DocumentId,
- string.IsNullOrWhiteSpace(recFileFormat) ? FileFormat.Mp4 : recFileFormat,
- FileDownloadType.Recording);
- }
-
- return Ok();
- }
- catch (Exception ex)
- {
- return Json(new { Exception = ex });
- }
- }
-
- private async Task ProcessFile(string downloadLocation, string documentId, string fileFormat, string downloadType)
- {
- var recordingDownloadUri = new Uri(downloadLocation);
- var response = await callAutomationClient.GetCallRecording().DownloadStreamingAsync(recordingDownloadUri);
-
- Logger.LogInformation($"Download {downloadType} response -- >" + response.GetRawResponse());
- Logger.LogInformation($"Save downloaded {downloadType} -- >");
-
- string filePath = ".\\" + documentId + "." + fileFormat;
- using (Stream streamToReadFrom = response.Value)
- {
- using (Stream streamToWriteTo = System.IO.File.Open(filePath, FileMode.Create))
- {
- await streamToReadFrom.CopyToAsync(streamToWriteTo);
- await streamToWriteTo.FlushAsync();
- }
- }
-
- if (string.Equals(downloadType, FileDownloadType.Metadata, StringComparison.InvariantCultureIgnoreCase) && System.IO.File.Exists(filePath))
- {
- Root deserializedFilePath = JsonConvert.DeserializeObject(System.IO.File.ReadAllText(filePath));
- recFileFormat = deserializedFilePath.recordingInfo.format;
-
- Logger.LogInformation($"Recording File Format is -- > {recFileFormat}");
- }
-
- Logger.LogInformation($"Starting to upload {downloadType} to BlobStorage into container -- > {containerName}");
-
- var blobStorageHelperInfo = await BlobStorageHelper.UploadFileAsync(blobStorageConnectionString, containerName, filePath, filePath);
- if (blobStorageHelperInfo.Status)
- {
- Logger.LogInformation(blobStorageHelperInfo.Message);
- Logger.LogInformation($"Deleting temporary {downloadType} file being created");
-
- System.IO.File.Delete(filePath);
- }
- else
- {
- Logger.LogError($"{downloadType} file was not uploaded,{blobStorageHelperInfo.Message}");
- }
-
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/ServerRecording/Models.cs b/ServerRecording/Models.cs
deleted file mode 100644
index d36133e3..00000000
--- a/ServerRecording/Models.cs
+++ /dev/null
@@ -1,156 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Azure.Communication.CallAutomation;
-
-namespace QuickStartApi
-{
- public class BlobStorageHelperInfo
- {
- public string Message { set; get; }
- public bool Status { set; get; }
- }
-
- static class FileDownloadType
- {
- const string recordingType = "recording";
- const string metadataType = "metadata";
-
- public static string Recording
- {
- get
- {
- return recordingType;
- }
- }
-
- public static string Metadata
- {
- get
- {
- return metadataType;
- }
- }
- }
-
- static class FileFormat
- {
- const string json = "json";
- const string mp4 = "mp4";
- const string mp3 = "mp3";
- const string wav = "wav";
-
- public static string Json
- {
- get
- {
- return json;
- }
- }
-
- public static string Mp4
- {
- get
- {
- return mp4;
- }
- }
-
- public static string Mp3
- {
- get
- {
- return mp3;
- }
- }
-
- public static string Wav
- {
- get
- {
- return wav;
- }
- }
- }
-
- public class Mapper
- {
- static Dictionary recContentMap
- = new Dictionary()
- {
- { "audiovideo", RecordingContent.AudioVideo },
- { "audio", RecordingContent.Audio }
- };
-
- static Dictionary recChannelMap
- = new Dictionary()
- {
- { "mixed", RecordingChannel.Mixed },
- { "unmixed", RecordingChannel.Unmixed }
- };
-
- static Dictionary recFormatMap
- = new Dictionary()
- {
- { "mp3", RecordingFormat.Mp3 },
- { "mp4", RecordingFormat.Mp4 },
- { "wav", RecordingFormat.Wav },
- };
-
- public static Dictionary RecordingContentMap
- {
- get { return recContentMap; }
- }
-
- public static Dictionary RecordingChannelMap
- {
- get { return recChannelMap; }
- }
-
- public static Dictionary RecordingFormatMap
- {
- get { return recFormatMap; }
- }
- }
-
- public class AudioConfiguration
- {
- public int sampleRate { get; set; }
- public int bitRate { get; set; }
- public int channels { get; set; }
- }
-
- public class VideoConfiguration
- {
- public int longerSideLength { get; set; }
- public int shorterSideLength { get; set; }
- public int framerate { get; set; }
- public int bitRate { get; set; }
- }
-
- public class RecordingInfo
- {
- public string contentType { get; set; }
- public string channelType { get; set; }
- public string format { get; set; }
- public AudioConfiguration audioConfiguration { get; set; }
- public VideoConfiguration videoConfiguration { get; set; }
- }
-
- public class Participant
- {
- public string participantId { get; set; }
- }
-
- public class Root
- {
- public string resourceId { get; set; }
- public string callId { get; set; }
- public string chunkDocumentId { get; set; }
- public int chunkIndex { get; set; }
- public DateTime chunkStartTime { get; set; }
- public double chunkDuration { get; set; }
- public List