diff --git a/Call_Automation_GCCH/README.md b/Call_Automation_GCCH/README.md
new file mode 100644
index 0000000..564fa6f
--- /dev/null
+++ b/Call_Automation_GCCH/README.md
@@ -0,0 +1,61 @@
+|page_type| languages |products
+|---|---------------------------------------|---|
+|sample|
|| azure | azure-communication-services |
|
+
+# Call Automation - Quick Start Sample
+
+In this quickstart, we cover how you can use Call Automation SDK to make an outbound call to a phone number and use the newly announced integration with Azure AI services to play dynamic prompts to participants using Text-to-Speech and recognize user voice input through Speech-to-Text to drive business logic in your application.
+
+# Design
+
+
+
+## Prerequisites
+
+- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F).
+- A deployed Communication Services resource. [Create a Communication Services resource](https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource).
+- A [phone number](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/telephony/get-phone-number) in your Azure Communication Services resource that can make outbound calls. NB: phone numbers are not available in free subscriptions.
+- Create Azure AI Multi Service resource. For details, see [Create an Azure AI Multi service](https://learn.microsoft.com/en-us/azure/cognitive-services/cognitive-services-apis-create-account).
+- [Java Development Kit (JDK) Microsoft.OpenJDK.17](https://learn.microsoft.com/en-us/java/openjdk/download)
+- [Apache Maven](https://maven.apache.org/download.cgi)
+- Create and host a Azure Dev Tunnel. Instructions [here](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started)
+- (Optional) A Microsoft Teams user with a phone license that is `voice` enabled. Teams phone license is required to add Teams users to the call. Learn more about Teams licenses [here](https://www.microsoft.com/microsoft-teams/compare-microsoft-teams-bundle-options). Learn about enabling phone system with `voice` [here](https://learn.microsoft.com/microsoftteams/setting-up-your-phone-system). You also need to complete the prerequisite step [Authorization for your Azure Communication Services Resource](https://learn.microsoft.com/azure/communication-services/how-tos/call-automation/teams-interop-call-automation?pivots=programming-language-javascript#step-1-authorization-for-your-azure-communication-services-resource-to-enable-calling-to-microsoft-teams-users) to enable calling to Microsoft Teams users.
+
+## Before running the sample for the first time
+
+- Open the application.yml file in the resources folder to configure the following settings
+
+ - `connectionstring`: Azure Communication Service resource's connection string.
+ - `callerphonenumber`: Phone number associated with the Azure Communication Service resource.
+ - `targetphonenumber`: Target Phone number.
+
+ Format: "OutboundTarget(Phone Number)".
+
+ For e.g. "+1425XXXAAAA"
+ - `basecallbackuri`: Base url of the app. For local development use dev tunnel url.
+ - `cognitiveServiceEndpoint`: Cognitive Service Endpoint.
+ - `targetTeamsUserId`: (Optional) update field with the Microsoft Teams user Id you would like to add to the call. See [Use Graph API to get Teams user Id](../../../how-tos/call-automation/teams-interop-call-automation.md#step-2-use-the-graph-api-to-get-microsoft-entra-object-id-for-teams-users-and-optionally-check-their-presence). Uncomment the below snippet in ProgramSample.java to enable Teams Interop scenario.
+ ```
+ client.getCallConnection(callConnectionId).addParticipant(
+ new CallInvite(new MicrosoftTeamsUserIdentifier(appConfig.getTargetTeamsUserId()))
+ .setSourceDisplayName("Jack (Contoso Tech Support)"));
+ ```
+
+### 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
+```
+
+### Run the application
+
+- Navigate to the directory containing the pom.xml file and use the following mvn commands:
+ - Compile the application: mvn compile
+ - Build the package: mvn package
+ - Execute the app: mvn exec:java
+- Access the Swagger UI at http://localhost:8080/swagger-ui.html
+ - Try the GET /outboundCall to run the Sample Application
diff --git a/Call_Automation_GCCH/pom.xml b/Call_Automation_GCCH/pom.xml
new file mode 100644
index 0000000..2ab1c33
--- /dev/null
+++ b/Call_Automation_GCCH/pom.xml
@@ -0,0 +1,200 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.6
+
+
+
+ com.communication.callautomation
+ Call_Automation_GCCH
+ 1.0-SNAPSHOT
+
+ Call_Automation_GCCH
+ Call_Automation_GCCH Sample application for instructional usage
+
+
+ 17
+ 17
+ UTF-8
+ 1.18.26
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.vaadin.external.google
+ android-json
+
+
+
+
+ com.microsoft.azure
+ applicationinsights-spring-boot-starter
+ 2.6.4
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ com.azure
+ azure-core
+ 1.42.0
+
+
+ com.azure
+ azure-identity
+ 1.10.4
+
+
+ com.azure
+ azure-communication-identity
+ 1.5.0
+
+
+ com.azure
+ azure-communication-callautomation
+ 1.5.0
+
+
+ com.azure
+ azure-messaging-eventgrid
+ 4.16.0
+
+
+ com.azure
+ azure-communication-common
+ 1.4.0
+
+
+ org.projectlombok
+ lombok
+ provided
+ ${lombok.version}
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure-processor
+ true
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.0.0
+
+
+ org.json
+ json
+ 20210307
+
+
+
+
+ azure-sdk-for-java
+ https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.2.0
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ maven-compiler-plugin
+ 3.11.0
+
+
+ -parameters
+
+
+
+
+ maven-surefire-plugin
+ 3.1.0
+
+
+ maven-jar-plugin
+ 3.3.0
+
+
+ maven-deploy-plugin
+ 3.1.1
+
+
+ maven-site-plugin
+ 3.12.1
+
+
+ maven-project-info-reports-plugin
+ 3.4.3
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+
+ java
+
+
+
+
+ com.communication.callautomation.Main
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.2.5
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Call_Automation_GCCH/src/main/java/com/communication/CallResponse.java b/Call_Automation_GCCH/src/main/java/com/communication/CallResponse.java
new file mode 100644
index 0000000..a562dce
--- /dev/null
+++ b/Call_Automation_GCCH/src/main/java/com/communication/CallResponse.java
@@ -0,0 +1,57 @@
+package com.communication;
+
+public class CallResponse {
+
+ private String callConnectionId;
+ private String correlationId;
+ private String message;
+ private String recordingId;
+
+ public CallResponse() {
+ }
+
+ public CallResponse(String callConnectionId, String correlationId, String message) {
+ this.callConnectionId = callConnectionId;
+ this.correlationId = correlationId;
+ this.message = message;
+ }
+
+ public CallResponse(String callConnectionId, String correlationId, String message, String recordingId) {
+ this.callConnectionId = callConnectionId;
+ this.correlationId = correlationId;
+ this.message = message;
+ this.recordingId = recordingId;
+ }
+
+ public String getCallConnectionId() {
+ return callConnectionId;
+ }
+
+ public void setCallConnectionId(String callConnectionId) {
+ this.callConnectionId = callConnectionId;
+ }
+
+ public String getCorrelationId() {
+ return correlationId;
+ }
+
+ public void setCorrelationId(String correlationId) {
+ this.correlationId = correlationId;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getRecordingId() {
+ return recordingId;
+ }
+
+ public void setRecordingId(String recordingId) {
+ this.recordingId = recordingId;
+ }
+}
diff --git a/Call_Automation_GCCH/src/main/java/com/communication/ParticipantListResponse.java b/Call_Automation_GCCH/src/main/java/com/communication/ParticipantListResponse.java
new file mode 100644
index 0000000..6b45f2d
--- /dev/null
+++ b/Call_Automation_GCCH/src/main/java/com/communication/ParticipantListResponse.java
@@ -0,0 +1,105 @@
+package com.communication;
+
+import java.util.List;
+
+public class ParticipantListResponse {
+
+ private String callConnectionId;
+ private String correlationId;
+ private String message;
+ private List participants;
+ private int participantCount;
+
+ public ParticipantListResponse() {
+ }
+
+ public ParticipantListResponse(String callConnectionId, String correlationId, String message,
+ List participants) {
+ this.callConnectionId = callConnectionId;
+ this.correlationId = correlationId;
+ this.message = message;
+ this.participants = participants;
+ this.participantCount = participants != null ? participants.size() : 0;
+ }
+
+ public String getCallConnectionId() {
+ return callConnectionId;
+ }
+
+ public void setCallConnectionId(String callConnectionId) {
+ this.callConnectionId = callConnectionId;
+ }
+
+ public String getCorrelationId() {
+ return correlationId;
+ }
+
+ public void setCorrelationId(String correlationId) {
+ this.correlationId = correlationId;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public List getParticipants() {
+ return participants;
+ }
+
+ public void setParticipants(List participants) {
+ this.participants = participants;
+ this.participantCount = participants != null ? participants.size() : 0;
+ }
+
+ public int getParticipantCount() {
+ return participantCount;
+ }
+
+ public void setParticipantCount(int participantCount) {
+ this.participantCount = participantCount;
+ }
+
+ // Inner class for individual participant information
+ public static class ParticipantInfo {
+ private String participantId;
+ private boolean isOnHold;
+ private boolean isMuted;
+
+ public ParticipantInfo() {
+ }
+
+ public ParticipantInfo(String participantId, boolean isOnHold, boolean isMuted) {
+ this.participantId = participantId;
+ this.isOnHold = isOnHold;
+ this.isMuted = isMuted;
+ }
+
+ public String getParticipantId() {
+ return participantId;
+ }
+
+ public void setParticipantId(String participantId) {
+ this.participantId = participantId;
+ }
+
+ public boolean isOnHold() {
+ return isOnHold;
+ }
+
+ public void setOnHold(boolean onHold) {
+ isOnHold = onHold;
+ }
+
+ public boolean isMuted() {
+ return isMuted;
+ }
+
+ public void setMuted(boolean muted) {
+ isMuted = muted;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Call_Automation_GCCH/src/main/java/com/communication/ParticipantResponse.java b/Call_Automation_GCCH/src/main/java/com/communication/ParticipantResponse.java
new file mode 100644
index 0000000..b8a72db
--- /dev/null
+++ b/Call_Automation_GCCH/src/main/java/com/communication/ParticipantResponse.java
@@ -0,0 +1,72 @@
+package com.communication;
+
+public class ParticipantResponse {
+
+ private String callConnectionId;
+ private String correlationId;
+ private String message;
+ private String participantId;
+ private boolean isOnHold;
+ private boolean isMuted;
+
+ public ParticipantResponse() {
+ }
+
+ public ParticipantResponse(String callConnectionId, String correlationId, String message,
+ String participantId, boolean isOnHold, boolean isMuted) {
+ this.callConnectionId = callConnectionId;
+ this.correlationId = correlationId;
+ this.message = message;
+ this.participantId = participantId;
+ this.isOnHold = isOnHold;
+ this.isMuted = isMuted;
+ }
+
+ public String getCallConnectionId() {
+ return callConnectionId;
+ }
+
+ public void setCallConnectionId(String callConnectionId) {
+ this.callConnectionId = callConnectionId;
+ }
+
+ public String getCorrelationId() {
+ return correlationId;
+ }
+
+ public void setCorrelationId(String correlationId) {
+ this.correlationId = correlationId;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getParticipantId() {
+ return participantId;
+ }
+
+ public void setParticipantId(String participantId) {
+ this.participantId = participantId;
+ }
+
+ public boolean isOnHold() {
+ return isOnHold;
+ }
+
+ public void setOnHold(boolean onHold) {
+ isOnHold = onHold;
+ }
+
+ public boolean isMuted() {
+ return isMuted;
+ }
+
+ public void setMuted(boolean muted) {
+ isMuted = muted;
+ }
+}
\ No newline at end of file
diff --git a/Call_Automation_GCCH/src/main/java/com/communication/callautomation/AppConfig.java b/Call_Automation_GCCH/src/main/java/com/communication/callautomation/AppConfig.java
new file mode 100644
index 0000000..3ae8cb6
--- /dev/null
+++ b/Call_Automation_GCCH/src/main/java/com/communication/callautomation/AppConfig.java
@@ -0,0 +1,64 @@
+package com.communication.callautomation;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.bind.ConstructorBinding;
+
+import lombok.Getter;
+
+@ConfigurationProperties(prefix = "acs")
+@Getter
+public class AppConfig {
+ private final String connectionString;
+ private final String callbackUriHost;
+ private final String acsPhoneNumber;
+ private final String targetPhoneNumber;
+ private final String cognitiveServiceEndpoint;
+ private final String targetTeamsUserId;
+
+ @ConstructorBinding
+ AppConfig(final String connectionString,
+ final String callbackUriHost,
+ final String acsPhoneNumber,
+ final String targetPhoneNumber,
+ final String cognitiveServiceEndpoint,
+ final String targetTeamsUserId) {
+ this.connectionString = connectionString;
+ this.callbackUriHost = callbackUriHost;
+ this.acsPhoneNumber = acsPhoneNumber;
+ this.targetPhoneNumber = targetPhoneNumber;
+ this.cognitiveServiceEndpoint = cognitiveServiceEndpoint;
+ this.targetTeamsUserId = targetTeamsUserId;
+ }
+
+ public String getCallBackUri() {
+ return callbackUriHost + "/api/callback";
+ }
+
+ public String getCallBackUriForRecordingApis() {
+ return callbackUriHost + "/api/recordingcallback";
+ }
+
+ public String getCallbackUriHost() {
+ return this.callbackUriHost;
+ }
+
+ public String getAcsPhoneNumber() {
+ return this.acsPhoneNumber;
+ }
+
+ public String getTargetPhoneNumber() {
+ return this.targetPhoneNumber;
+ }
+
+ public String getCognitiveServiceEndpoint() {
+ return this.cognitiveServiceEndpoint;
+ }
+
+ public String getConnectionString() {
+ return this.connectionString;
+ }
+
+ public String getTargetTeamsUserId() {
+ return this.targetTeamsUserId;
+ }
+}
diff --git a/Call_Automation_GCCH/src/main/java/com/communication/callautomation/Main.java b/Call_Automation_GCCH/src/main/java/com/communication/callautomation/Main.java
new file mode 100644
index 0000000..d1ad1c1
--- /dev/null
+++ b/Call_Automation_GCCH/src/main/java/com/communication/callautomation/Main.java
@@ -0,0 +1,13 @@
+package com.communication.callautomation;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@SpringBootApplication
+@EnableConfigurationProperties(value = AppConfig.class)
+public class Main {
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/Call_Automation_GCCH/src/main/java/com/communication/callautomation/ProgramSample.java b/Call_Automation_GCCH/src/main/java/com/communication/callautomation/ProgramSample.java
new file mode 100644
index 0000000..d1ae29f
--- /dev/null
+++ b/Call_Automation_GCCH/src/main/java/com/communication/callautomation/ProgramSample.java
@@ -0,0 +1,1799 @@
+package com.communication.callautomation;
+
+import com.azure.communication.callautomation.CallAutomationClient;
+import com.azure.communication.callautomation.CallAutomationClientBuilder;
+import com.azure.communication.callautomation.CallAutomationEventParser;
+import com.azure.communication.callautomation.CallConnection;
+import com.azure.communication.callautomation.CallMedia;
+import com.azure.communication.callautomation.models.*;
+import com.azure.communication.callautomation.models.events.*;
+import com.azure.communication.common.CommunicationIdentifier;
+import com.azure.communication.common.CommunicationUserIdentifier;
+import com.azure.communication.common.PhoneNumberIdentifier;
+import com.azure.core.http.rest.PagedIterable;
+import com.azure.core.http.rest.Response;
+import com.azure.core.util.Context;
+import com.communication.CallResponse;
+import com.communication.ParticipantResponse;
+import com.communication.ParticipantListResponse;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.web.bind.annotation.*;
+import org.springframework.http.*;
+
+import java.io.*;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.FileSystemResource;
+import org.json.JSONObject;
+import com.azure.messaging.eventgrid.EventGridEvent;
+import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse;
+import com.azure.messaging.eventgrid.systemevents.AcsRecordingFileStatusUpdatedEventData;
+import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData;
+import com.azure.core.util.BinaryData;
+import com.azure.messaging.eventgrid.SystemEventNames;
+
+@RestController
+public class ProgramSample {
+
+ private static final Logger log = LoggerFactory.getLogger(ProgramSample.class);
+ private CallAutomationClient client;
+ // private final CallAutomationAsyncClient asyncClient;
+
+ // Configuration state variables
+ private AppConfig appConfig;
+ private String acsConnectionString = "";
+ private String cognitiveServicesEndpoint = "";
+ private String acsPhoneNumber = "";
+ private String targetPhoneNumber = "";
+ private String targetAcsUserId = "";
+ private String callbackUriHost = "";
+ private String websocketUriHost = "";
+
+ private String callConnectionId = "";
+ private String recordingId = "";
+ private String recordingLocation = "";
+ private String recordingFileFormat = "";
+
+ private String confirmLabel = "Confirm";
+ private String cancelLabel = "Cancel";
+
+ // Event logging configuration
+ private static final String EVENTS_LOG_FILE = "call_automation_events.txt";
+ private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ public ProgramSample(final AppConfig appConfig) {
+ this.appConfig = appConfig;
+
+ // Initialize configuration values from AppConfig
+ this.acsConnectionString = appConfig.getConnectionString();
+ this.callbackUriHost = appConfig.getCallbackUriHost();
+ this.acsPhoneNumber = appConfig.getAcsPhoneNumber();
+ this.targetPhoneNumber = appConfig.getTargetPhoneNumber();
+ this.cognitiveServicesEndpoint = appConfig.getCognitiveServiceEndpoint();
+ this.targetAcsUserId = appConfig.getTargetTeamsUserId();
+
+ // Set websocketUriHost to same as callbackUriHost for now
+ this.websocketUriHost = appConfig.getCallbackUriHost();
+
+ client = initClient();
+ }
+
+ /**
+ * Writes event information to a text file
+ */
+ private void writeEventToFile(CallAutomationEventBase event, String additionalInfo) {
+ try {
+ Path filePath = Paths.get(EVENTS_LOG_FILE);
+ String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMATTER);
+
+ StringBuilder eventInfo = new StringBuilder();
+ eventInfo.append("==========================================\n");
+ eventInfo.append("Timestamp: ").append(timestamp).append("\n");
+ eventInfo.append("Event Type: ").append(event.getClass().getSimpleName()).append("\n");
+ eventInfo.append("Call Connection ID: ").append(event.getCallConnectionId()).append("\n");
+ eventInfo.append("Server Call ID: ").append(event.getServerCallId()).append("\n");
+ eventInfo.append("Correlation ID: ").append(event.getCorrelationId()).append("\n");
+
+ if (additionalInfo != null && !additionalInfo.isEmpty()) {
+ eventInfo.append("Additional Info: ").append(additionalInfo).append("\n");
+ }
+
+ eventInfo.append("==========================================\n\n");
+
+ // Write to file (append mode)
+ Files.write(filePath, eventInfo.toString().getBytes(),
+ StandardOpenOption.CREATE, StandardOpenOption.APPEND);
+
+ } catch (IOException e) {
+ log.error("Failed to write event to file: {}", e.getMessage());
+ }
+ }
+ @Tag(name = "02. Call Automation Events", description = "CallAutomation Events")
+ @PostMapping(path = "/api/callbacks")
+ public ResponseEntity callbackEvents(@RequestBody final String reqBody) {
+ try {
+ List events = CallAutomationEventParser.parseEvents(reqBody);
+ for (CallAutomationEventBase event : events) {
+ callConnectionId = event.getCallConnectionId();
+ log.info(
+ "Received call event callConnectionID: {}, serverCallId: {}, CorrelationId: {}, eventType: {}",
+ callConnectionId,
+ event.getServerCallId(),
+ event.getCorrelationId(),
+ event.getClass().getSimpleName());
+
+ // Write basic event info to file for all events
+ String basicEventInfo = String.format("Event: %s", event.getClass().getSimpleName());
+ writeEventToFile(event, basicEventInfo);
+
+ if (event instanceof CallConnected) {
+ log.info("****************************************");
+ log.info("CORRELATION ID: {}", event.getCorrelationId());
+ log.info("****************************************");
+ log.info("CALL CONNECTION ID: {}", event.getCallConnectionId());
+ log.info("****************************************");
+ var mediaStreamingSubscription = client.getCallConnection(callConnectionId).getCallProperties()
+ .getMediaStreamingSubscription();
+ var transcriptionSubscription = client.getCallConnection(callConnectionId).getCallProperties()
+ .getTranscriptionSubscription();
+ log.info("MediaStreaming State: {}", mediaStreamingSubscription.getState());
+ log.info("Transcription State: {}", transcriptionSubscription.getState());
+
+ // Write to file
+ String additionalInfo = String.format("MediaStreaming State: %s, Transcription State: %s",
+ mediaStreamingSubscription.getState(),
+ transcriptionSubscription.getState());
+ writeEventToFile(event, additionalInfo);
+ } else if (event instanceof MediaStreamingStarted) {
+ MediaStreamingStarted acsEvent = (MediaStreamingStarted) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("MediaSteaming Status: {}",
+ acsEvent.getMediaStreamingUpdateResult().getMediaStreamingStatus());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, MediaStreaming Status: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getMediaStreamingUpdateResult().getMediaStreamingStatus());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof MediaStreamingStopped) {
+ MediaStreamingStopped acsEvent = (MediaStreamingStopped) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("MediaSteaming Status: {}",
+ acsEvent.getMediaStreamingUpdateResult().getMediaStreamingStatus());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, MediaStreaming Status: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getMediaStreamingUpdateResult().getMediaStreamingStatus());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof MediaStreamingFailed) {
+ MediaStreamingFailed acsEvent = (MediaStreamingFailed) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("MediaSteaming Status: {}",
+ acsEvent.getMediaStreamingUpdateResult().getMediaStreamingStatus());
+ log.error("Received failed event: {}", acsEvent
+ .getResultInformation().getMessage());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, MediaStreaming Status: %s, Error: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getMediaStreamingUpdateResult().getMediaStreamingStatus(),
+ acsEvent.getResultInformation().getMessage());
+ writeEventToFile(event, additionalInfo);
+ } else if (event instanceof TranscriptionStarted) {
+ TranscriptionStarted acsEvent = (TranscriptionStarted) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("Transcription Status: {}",
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, Transcription Status: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof TranscriptionUpdated) {
+ TranscriptionUpdated acsEvent = (TranscriptionUpdated) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("Transcription Status: {}",
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, Transcription Status: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof TranscriptionStopped) {
+ TranscriptionStopped acsEvent = (TranscriptionStopped) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("Transcription Status: {}",
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, Transcription Status: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof TranscriptionFailed) {
+ TranscriptionFailed acsEvent = (TranscriptionFailed) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("Transcription Status: {}",
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus());
+ log.error("Received failed event: {}", acsEvent
+ .getResultInformation().getMessage());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, Transcription Status: %s, Error: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getTranscriptionUpdateResult().getTranscriptionStatus(),
+ acsEvent.getResultInformation().getMessage());
+ writeEventToFile(event, additionalInfo);
+ } else {
+ log.debug("Received unhandled event: {}", event.getClass().getSimpleName());
+ // Write unhandled events to file too
+ writeEventToFile(event, "Unhandled event type");
+ }
+
+ if (event instanceof RecognizeCompleted) {
+ RecognizeCompleted acsEvent = (RecognizeCompleted) event;
+ RecognizeResult recognizeResult = acsEvent.getRecognizeResult().get();
+ String recognitionInfo;
+ if (recognizeResult instanceof DtmfResult) {
+ // Take action on collect tones
+ DtmfResult dtmfResult = (DtmfResult) recognizeResult;
+ List tones = dtmfResult.getTones();
+ log.info("Recognition completed, tones=" + tones + ", context="
+ + acsEvent.getOperationContext());
+ recognitionInfo = "DTMF Tones: " + tones;
+ } else if (recognizeResult instanceof ChoiceResult) {
+ ChoiceResult collectChoiceResult = (ChoiceResult) recognizeResult;
+ String labelDetected = collectChoiceResult.getLabel();
+ String phraseDetected = collectChoiceResult.getRecognizedPhrase();
+ recognitionInfo = String.format("Choice - Label: %s, Phrase: %s", labelDetected, phraseDetected);
+ } else if (recognizeResult instanceof SpeechResult) {
+ SpeechResult speechResult = (SpeechResult) recognizeResult;
+ String text = speechResult.getSpeech();
+ log.info("Recognition completed, text=" + text + ", context=" + acsEvent.getOperationContext());
+ recognitionInfo = "Speech: " + text;
+ } else {
+ log.info("Recognition completed, result=" + recognizeResult + ", context="
+ + acsEvent.getOperationContext());
+ recognitionInfo = "Result: " + recognizeResult;
+ }
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, Recognition Info: %s",
+ acsEvent.getOperationContext(), recognitionInfo);
+ writeEventToFile(event, additionalInfo);
+ } else if (event instanceof RecognizeFailed) {
+
+ RecognizeFailed acsEvent = (RecognizeFailed) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("FailedPlaySourceIndex: {}",
+ acsEvent.getFailedPlaySourceIndex());
+ log.error("Received failed event: {}", acsEvent
+ .getResultInformation().getMessage());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, FailedPlaySourceIndex: %s, Error: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getFailedPlaySourceIndex(),
+ acsEvent.getResultInformation().getMessage());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof PlayCompleted) {
+ PlayCompleted acsEvent = (PlayCompleted) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s", acsEvent.getOperationContext());
+ writeEventToFile(event, additionalInfo);
+ } else if (event instanceof PlayFailed) {
+
+ PlayFailed acsEvent = (PlayFailed) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.info("FailedPlaySourceIndex: {}",
+ acsEvent.getFailedPlaySourceIndex());
+ log.error("Received failed event: {}", acsEvent
+ .getResultInformation().getMessage());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, FailedPlaySourceIndex: %s, Error: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getFailedPlaySourceIndex(),
+ acsEvent.getResultInformation().getMessage());
+ writeEventToFile(event, additionalInfo);
+
+ } else if (event instanceof RecordingStateChanged) {
+ RecordingStateChanged acsEvent = (RecordingStateChanged) event;
+ log.info("Recording State Changed event received: {}",
+ event.getCallConnectionId());
+ log.info("Recording State: {}", acsEvent.getRecordingState());
+
+ // Write to file
+ String additionalInfo = String.format("Recording State: %s", acsEvent.getRecordingState());
+ writeEventToFile(event, additionalInfo);
+ } else if (event instanceof CallTransferAccepted) {
+ CallTransferAccepted acsEvent = (CallTransferAccepted) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s", acsEvent.getOperationContext());
+ writeEventToFile(event, additionalInfo);
+ } else if (event instanceof CallTransferFailed) {
+
+ CallTransferFailed acsEvent = (CallTransferFailed) event;
+ log.info("Operation Context: {}", acsEvent.getOperationContext());
+ log.error("Received failed event: {}", acsEvent
+ .getResultInformation().getMessage());
+
+ // Write to file
+ String additionalInfo = String.format("Operation Context: %s, Error: %s",
+ acsEvent.getOperationContext(),
+ acsEvent.getResultInformation().getMessage());
+ writeEventToFile(event, additionalInfo);
+ }
+ }
+ return ResponseEntity.ok().body("");
+ } catch (Exception e) {
+ log.error("Error processing callback events: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to process callback events.");
+ }
+ }
+
+ private void handleIncomingCall(final BinaryData eventData) {
+ JSONObject data = new JSONObject(eventData.toString());
+ String callbackUri;
+ AnswerCallOptions options;
+ String cognitiveServicesUrl;
+ String websocketUrl;
+
+ try {
+ callbackUri = callbackUriHost + "/api/callbacks";
+ // Replace "https://" with "wss://" for WebSocket protocol
+ websocketUrl = websocketUriHost;
+ System.out.println("WebSocket URL: " + websocketUrl);
+ MediaStreamingOptions mediaStreamingOptions = new MediaStreamingOptions(MediaStreamingAudioChannel.UNMIXED);
+ mediaStreamingOptions.setTransportUrl(websocketUrl);
+ mediaStreamingOptions.setStartMediaStreaming(false);
+ mediaStreamingOptions.setEnableDtmfTones(false);
+ mediaStreamingOptions.setEnableBidirectional(false);
+ mediaStreamingOptions.setAudioFormat(AudioFormat.PCM_16K_MONO);
+
+ TranscriptionOptions transcriptionOptions = new TranscriptionOptions("en-ES");
+ transcriptionOptions.setTransportUrl(websocketUriHost);
+ transcriptionOptions.setStartTranscription(false);
+
+ options = new AnswerCallOptions(data.getString("incomingCallContext"),
+ callbackUri);
+ options.setMediaStreamingOptions(mediaStreamingOptions);
+ options.setTranscriptionOptions(transcriptionOptions);
+
+ Response answerCallResponse = client.answerCallWithResponse(options, Context.NONE);
+
+ log.info("Incoming call answered. Cognitive Services Url: {}\nCallbackUri: {}\nCallConnectionId: {}",
+ cognitiveServicesEndpoint,
+ callbackUri,
+ answerCallResponse.getValue().getCallConnectionProperties().getCallConnectionId());
+ } catch (Exception e) {
+ log.error("Error getting recording location info {} {}",
+ e.getMessage(),
+ e.getCause());
+ }
+ }
+
+ private ResponseEntity handleSubscriptionValidation(final BinaryData eventData) {
+ try {
+ log.info("Received Subscription Validation Event from Incoming Call API endpoint");
+ SubscriptionValidationEventData subscriptioneventData = eventData
+ .toObject(SubscriptionValidationEventData.class);
+ SubscriptionValidationResponse responseData = new SubscriptionValidationResponse();
+ responseData.setValidationResponse(subscriptioneventData.getValidationCode());
+ return ResponseEntity.ok().body(responseData);
+ } catch (Exception e) {
+ log.error("Error at subscription validation event {} {}",
+ e.getMessage(),
+ e.getCause());
+ return ResponseEntity.internalServerError().body(null);
+ }
+ }
+
+ @Tag(name = "02. Call Automation Events", description = "CallAutomation Events")
+ @PostMapping(path = "/api/incomingCall")
+ public ResponseEntity recordinApiEventGridEvents(
+ @RequestBody final String reqBody) {
+ List events = EventGridEvent.fromString(reqBody);
+ for (EventGridEvent eventGridEvent : events) {
+ if (eventGridEvent.getEventType().equals(SystemEventNames.EVENT_GRID_SUBSCRIPTION_VALIDATION)) {
+ return handleSubscriptionValidation(eventGridEvent.getData());
+ } else if (eventGridEvent.getEventType().equals(SystemEventNames.COMMUNICATION_INCOMING_CALL)) {
+ handleIncomingCall(eventGridEvent.getData());
+ }
+ }
+ return ResponseEntity.ok().body(null);
+ }
+
+ @Tag(name = "02. Call Automation Events", description = "CallAutomation Events")
+ @PostMapping("/api/recordingFileStatus")
+ public ResponseEntity handleRecordingFileStatus(@RequestBody String reqBody) {
+ List events = EventGridEvent.fromString(reqBody);
+ log.info("RECORDING FILE STATUS UPDATED EVENT GRID EVENT RECEIVED.");
+ for (EventGridEvent eventGridEvent : events) {
+ if (eventGridEvent.getEventType().equals(SystemEventNames.EVENT_GRID_SUBSCRIPTION_VALIDATION)) {
+ return handleSubscriptionValidation(eventGridEvent.getData());
+ } else if (eventGridEvent.getEventType()
+ .equals(SystemEventNames.COMMUNICATION_RECORDING_FILE_STATUS_UPDATED)) {
+ log.info("The event received for recording file status update");
+ AcsRecordingFileStatusUpdatedEventData recordingFileStatusUpdatedEventData = eventGridEvent.getData()
+ .toObject(AcsRecordingFileStatusUpdatedEventData.class);
+ recordingLocation = recordingFileStatusUpdatedEventData.getRecordingStorageInfo().getRecordingChunks()
+ .get(0).getContentLocation();
+ String recordingMetadataLocation = recordingFileStatusUpdatedEventData.getRecordingStorageInfo()
+ .getRecordingChunks()
+ .get(0).getMetadataLocation();
+ String recordingDeleteLocation = recordingFileStatusUpdatedEventData.getRecordingStorageInfo()
+ .getRecordingChunks()
+ .get(0).getDeleteLocation();
+ log.info("The recording location is : {}", recordingLocation);
+ log.info("The recording metadata location is : {}", recordingMetadataLocation);
+ log.info("The recording delete location is : {}", recordingDeleteLocation);
+
+ } else {
+ log.debug("Unhandled event.");
+ }
+ }
+
+ return ResponseEntity.ok().build();
+ }
+
+ // POST: /outboundCallAsync
+ @Tag(name = "03. Outbound Call APIs", description = "Outbound Call APIs")
+ @PostMapping("/outboundCallAsync")
+ public ResponseEntity outboundCallAsync(@RequestParam String target,
+ @RequestParam boolean isPSTN) {
+
+ try {
+ String callbackUri = callbackUriHost + "/api/callbacks";
+ log.info("Creating async call with callbackUri: {}", callbackUri);
+ if (isPSTN) {
+ PhoneNumberIdentifier targetParticipant = new PhoneNumberIdentifier(target);
+ PhoneNumberIdentifier caller = new PhoneNumberIdentifier(acsPhoneNumber);
+ CallInvite callInvite = new CallInvite(targetParticipant, caller);
+ CreateCallOptions createCallOptions = new CreateCallOptions(callInvite, callbackUri);
+ CallIntelligenceOptions callIntelligenceOptions = new CallIntelligenceOptions();
+ createCallOptions.setCallIntelligenceOptions(callIntelligenceOptions);
+
+ log.info("Creating async call to PSTN with target: {}, caller: {}, callbackUri: {}",
+ targetParticipant.getRawId(), caller.getRawId(), callbackUri);
+ // Make async call and block to get the result
+ Response response = client.createCallWithResponse(createCallOptions, Context.NONE);
+
+ if (response != null && response.getValue() != null) {
+ callConnectionId = response.getValue().getCallConnectionProperties().getCallConnectionId();
+ String correlationId = response.getValue().getCallConnectionProperties().getCorrelationId();
+ log.info("Created async pstn call with connection id: " + callConnectionId);
+ CallResponse callResponse = new CallResponse(callConnectionId, correlationId, "Created async PSTN call successfully");
+ return ResponseEntity.ok(callResponse);
+ } else {
+ log.error("Failed to create call. Response or value was null.");
+ CallResponse errorResponse = new CallResponse(null, null, "Failed to create call. Response or value was null.");
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ } else {
+ CommunicationUserIdentifier targetParticipant = new CommunicationUserIdentifier(target);
+ CallInvite callInvite = new CallInvite(targetParticipant);
+
+ CreateCallOptions createCallOptions = new CreateCallOptions(callInvite, callbackUri.toString());
+ CallIntelligenceOptions callIntelligenceOptions = new CallIntelligenceOptions();
+ createCallOptions.setCallIntelligenceOptions(callIntelligenceOptions);
+
+ Response result = client.createCallWithResponse(createCallOptions, Context.NONE);
+ callConnectionId = result.getValue().getCallConnectionProperties().getCallConnectionId();
+ String correlationId = result.getValue().getCallConnectionProperties().getCorrelationId();
+ log.info("Created async call with connection id: " + callConnectionId);
+ CallResponse callResponse = new CallResponse(callConnectionId, correlationId, "Created async call successfully");
+ return ResponseEntity.ok(callResponse);
+ }
+ } catch (Exception e) {
+ log.error("Error creating call : {}", e.getMessage());
+ CallResponse errorResponse = new CallResponse(null, null, "Failed to create call: " + e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "03. Outbound Call APIs", description = "Outbound Call APIs")
+ @PostMapping("/outboundCall")
+ public ResponseEntity outboundCallToPstn(@RequestParam String target,
+ @RequestParam boolean isPSTN) {
+ try {
+ if (isPSTN) {
+ PhoneNumberIdentifier targetParticipant = new PhoneNumberIdentifier(target);
+ PhoneNumberIdentifier caller = new PhoneNumberIdentifier(acsPhoneNumber);
+
+ URI callbackUri = URI.create(callbackUriHost + "/api/callbacks");
+ CallInvite callInvite = new CallInvite(targetParticipant, caller);
+
+ // ✅ Convert URI to String
+ CreateCallResult result = client.createCall(callInvite, callbackUri.toString());
+ callConnectionId = result.getCallConnectionProperties().getCallConnectionId();
+ String correlationId = result.getCallConnectionProperties().getCorrelationId();
+ log.info("Created call with connection id: " + callConnectionId);
+ CallResponse callResponse = new CallResponse(callConnectionId, correlationId, "Created PSTN call successfully");
+ return ResponseEntity.ok(callResponse);
+ } else {
+ CommunicationUserIdentifier targetParticipant = new CommunicationUserIdentifier(target);
+ CallInvite callInvite = new CallInvite(targetParticipant);
+ URI callbackUri = URI.create(callbackUriHost + "/api/callbacks");
+
+ CreateCallResult result = client.createCall(callInvite, callbackUri.toString());
+ callConnectionId = result.getCallConnectionProperties().getCallConnectionId();
+ String correlationId = result.getCallConnectionProperties().getCorrelationId();
+ log.info("Created call with connection id: " + callConnectionId);
+ CallResponse callResponse = new CallResponse(callConnectionId, correlationId, "Created call successfully");
+ return ResponseEntity.ok(callResponse);
+ }
+ } catch (Exception e) {
+ log.error("Error creating call : {}", e.getMessage());
+ CallResponse errorResponse = new CallResponse(null, null, "Failed to create call: " + e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "04. Disconnect Call APIs", description = "Disconnect call APIs")
+ @PostMapping("/hangupAsync")
+ public ResponseEntity hangupAsync(@RequestParam String callConnectionId, @RequestParam boolean isForEveryOne) {
+ try {
+ CallConnection callConnection = getConnection(callConnectionId);
+ CallConnectionProperties properties = callConnection.getCallProperties();
+ String correlationId = properties.getCorrelationId();
+
+ callConnection.hangUpWithResponse(isForEveryOne, Context.NONE);
+ log.info("Call hangup requested (async) forEveryone={}", isForEveryOne);
+
+ CallResponse callResponse = new CallResponse(callConnectionId, correlationId, "Call hangup requested (async)");
+ return ResponseEntity.ok(callResponse);
+ } catch (Exception e) {
+ log.error("Error hanging up call: {}", e.getMessage());
+ CallResponse errorResponse = new CallResponse(callConnectionId, null, "Failed to hang up call: " + e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "04. Disconnect Call APIs", description = "Disconnect call APIs")
+ @PostMapping("/hangup")
+ public ResponseEntity hangup(@RequestParam String callConnectionId, @RequestParam boolean isForEveryOne) {
+ try {
+ CallConnection callConnection = getConnection(callConnectionId);
+ CallConnectionProperties properties = callConnection.getCallProperties();
+ String correlationId = properties.getCorrelationId();
+
+ callConnection.hangUp(isForEveryOne);
+ log.info("Call hangup requested (sync) forEveryone={}", isForEveryOne);
+
+ CallResponse callResponse = new CallResponse(callConnectionId, correlationId, "Call hangup requested");
+ return ResponseEntity.ok(callResponse);
+ } catch (Exception e) {
+ log.error("Error hanging up call: {}", e.getMessage());
+ CallResponse errorResponse = new CallResponse(callConnectionId, null, "Failed to hang up call: " + e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "05. Hold Participant APIs", description = "Hold Participant APIs")
+ @PostMapping("/holdParticipantAsync")
+ public ResponseEntity holdParticipantAsync(@RequestParam String callConnectionId, @RequestParam String targetParticipant,
+ @RequestParam boolean isPSTN, @RequestParam boolean isPlaySource) {
+ try {
+ CommunicationIdentifier target;
+ if (isPSTN) {
+ target = new PhoneNumberIdentifier(targetParticipant);
+ } else {
+ target = new CommunicationUserIdentifier(targetParticipant);
+ }
+ HoldOptions holdOptions = new HoldOptions(target).setOperationContext("holdUserContext");
+ CallMedia callMediaService = getCallMedia(callConnectionId);
+
+ if (isPlaySource) {
+ TextSource textSource = new TextSource()
+ .setText("You are on hold. Please wait...")
+ .setVoiceName("en-US-NancyNeural")
+ .setSourceLocale("en-US")
+ .setVoiceKind(VoiceKind.MALE);
+ holdOptions.setPlaySource(textSource);
+ }
+
+ callMediaService.holdWithResponse(holdOptions, Context.NONE);
+ log.info("Held participant asynchronously with playSource = {}", isPlaySource);
+ return ResponseEntity.ok("Participant held (async).");
+ } catch (Exception e) {
+ log.error("Error holding participant asynchronously: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to hold participant asynchronously.");
+ }
+ }
+
+ @Tag(name = "05. Hold Participant APIs", description = "Hold Participant APIs")
+ @PostMapping("/holdParticipant")
+ public ResponseEntity holdParticipant(@RequestParam String callConnectionId, @RequestParam String targetParticipant,
+ @RequestParam boolean isPSTN, @RequestParam boolean isPlaySource) {
+ try {
+ CommunicationIdentifier target;
+ if (isPSTN) {
+ target = new PhoneNumberIdentifier(targetParticipant);
+ } else {
+ target = new CommunicationUserIdentifier(targetParticipant);
+ }
+ TextSource textSource = null;
+ CallMedia callMediaService = getCallMedia(callConnectionId);
+
+ if (isPlaySource) {
+ textSource = new TextSource()
+ .setText("You are on hold. Please wait...")
+ .setVoiceName("en-US-NancyNeural")
+ .setSourceLocale("en-US")
+ .setVoiceKind(VoiceKind.MALE);
+ }
+
+ callMediaService.hold(target, textSource);
+ log.info("Held participant synchronously with playSource = {}", isPlaySource);
+ return ResponseEntity.ok("Participant held.");
+ } catch (Exception e) {
+ log.error("Error holding participant: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to hold participant.");
+ }
+ }
+
+ @Tag(name = "05. Hold Participant APIs", description = "Hold Participant APIs")
+ @PostMapping("/unholdParticipantAsync")
+ public ResponseEntity unholdParticipantAsync(@RequestParam String callConnectionId, @RequestParam String targetParticipant,
+ @RequestParam boolean isPSTN) {
+ try {
+ CommunicationIdentifier target;
+ if (isPSTN) {
+ target = new PhoneNumberIdentifier(targetParticipant);
+ } else {
+ target = new CommunicationUserIdentifier(targetParticipant);
+ }
+ UnholdOptions unholdOptions = new UnholdOptions(target).setOperationContext("unholdUserContext");
+ CallMedia callMediaService = getCallMedia(callConnectionId);
+
+ log.info("Unhold participant asynchronously {}", targetParticipant);
+ return ResponseEntity.ok("Participant unheld (async).");
+ } catch (Exception e) {
+ log.error("Error unholding participant asynchronously: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to unhold participant asynchronously.");
+ }
+ }
+
+ @Tag(name = "05. Hold Participant APIs", description = "Hold Participant APIs")
+ @PostMapping("/unholdParticipant")
+ public ResponseEntity unholdParticipant(@RequestParam String callConnectionId, @RequestParam String targetParticipant,
+ @RequestParam boolean isPSTN) {
+ try {
+ CommunicationIdentifier target;
+ if (isPSTN) {
+ target = new PhoneNumberIdentifier(targetParticipant);
+ } else {
+ target = new CommunicationUserIdentifier(targetParticipant);
+ }
+ CallMedia callMediaService = getCallMedia(callConnectionId);
+
+ callMediaService.unhold(target);
+ log.info("Unhold participant synchronously {}", targetParticipant);
+ return ResponseEntity.ok("Participant unheld.");
+ } catch (Exception e) {
+ log.error("Error unholding participant: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to unhold participant.");
+ }
+ }
+
+ @Tag(name = "06. Get Participant APIs", description = "Get Participant APIs")
+ @PostMapping("/getParticipantAsync")
+ public ResponseEntity getParticipantAsync(@RequestParam String callConnectionId, @RequestParam String targetParticipant,
+ @RequestParam boolean isPSTN) {
+ try {
+ CallConnection callConnection = getConnection(callConnectionId);
+ CallConnectionProperties properties = callConnection.getCallProperties();
+ String correlationId = properties.getCorrelationId();
+
+ CommunicationIdentifier target;
+ if (isPSTN) {
+ target = new PhoneNumberIdentifier(targetParticipant);
+ } else {
+ target = new CommunicationUserIdentifier(targetParticipant);
+ }
+ Response response = callConnection.getParticipantWithResponse(
+ target,
+ Context.NONE
+ );
+
+ CallParticipant participant = response.getValue();
+
+ if (participant != null) {
+ String participantId = participant.getIdentifier().getRawId();
+ boolean isOnHold = participant.isOnHold();
+ boolean isMuted = participant.isMuted();
+
+ log.info("Participant: --> {}", participantId);
+ log.info("Is Participant on hold: --> {}", isOnHold);
+ log.info("Is Participant muted: --> {}", isMuted);
+
+ ParticipantResponse participantResponse = new ParticipantResponse(
+ callConnectionId, correlationId, "Participant found successfully",
+ participantId, isOnHold, isMuted
+ );
+ return ResponseEntity.ok(participantResponse);
+ } else {
+ log.warn("No participant found for identifier: {}", targetParticipant);
+ ParticipantResponse errorResponse = new ParticipantResponse(
+ callConnectionId, correlationId, "No participant found for identifier: " + targetParticipant,
+ targetParticipant, false, false
+ );
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
+ }
+ } catch (Exception e) {
+ log.error("Error getting participant asynchronously: {}", e.getMessage());
+ ParticipantResponse errorResponse = new ParticipantResponse(
+ callConnectionId, null, "Failed to get participant asynchronously: " + e.getMessage(),
+ targetParticipant, false, false
+ );
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "06. Get Participant APIs", description = "Get Participant APIs")
+ @PostMapping("/getParticipant")
+ public ResponseEntity getParticipant(@RequestParam String callConnectionId, @RequestParam String targetParticipant,
+ @RequestParam boolean isPSTN) {
+ try {
+ CallConnection callConnection = getConnection(callConnectionId);
+ CallConnectionProperties properties = callConnection.getCallProperties();
+ String correlationId = properties.getCorrelationId();
+
+ CommunicationIdentifier target;
+ if (isPSTN) {
+ target = new PhoneNumberIdentifier(targetParticipant);
+ } else {
+ target = new CommunicationUserIdentifier(targetParticipant);
+ }
+ CallParticipant participant = callConnection.getParticipant(target);
+
+ if (participant != null) {
+ String participantId = participant.getIdentifier().getRawId();
+ boolean isOnHold = participant.isOnHold();
+ boolean isMuted = participant.isMuted();
+
+ log.info("Participant: --> {}", participantId);
+ log.info("Is Participant on hold: --> {}", isOnHold);
+ log.info("Is Participant muted: --> {}", isMuted);
+
+ ParticipantResponse participantResponse = new ParticipantResponse(
+ callConnectionId, correlationId, "Participant found successfully",
+ participantId, isOnHold, isMuted
+ );
+ return ResponseEntity.ok(participantResponse);
+ } else {
+ log.warn("No participant found for identifier: {}", targetParticipant);
+ ParticipantResponse errorResponse = new ParticipantResponse(
+ callConnectionId, correlationId, "No participant found for identifier: " + targetParticipant,
+ targetParticipant, false, false
+ );
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
+ }
+ } catch (Exception e) {
+ log.error("Error getting participant: {}", e.getMessage());
+ ParticipantResponse errorResponse = new ParticipantResponse(
+ callConnectionId, null, "Failed to get participant: " + e.getMessage(),
+ targetParticipant, false, false
+ );
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "06. Get Participant APIs", description = "Get Participant APIs")
+ @PostMapping("/getParticipantListAsync")
+ public ResponseEntity getParticipantListAsync(@RequestParam String callConnectionId) {
+ try {
+ CallConnection callConnection = getConnection(callConnectionId);
+ CallConnectionProperties properties = callConnection.getCallProperties();
+ String correlationId = properties.getCorrelationId();
+
+ PagedIterable participants = callConnection.listParticipants(Context.NONE);
+
+ if (participants != null) {
+ List participantList = new ArrayList<>();
+
+ for (CallParticipant participant : participants) {
+ String participantId = participant.getIdentifier().getRawId();
+ boolean isOnHold = participant.isOnHold();
+ boolean isMuted = participant.isMuted();
+
+ log.info("----------------------------------------------------------------------");
+ log.info("Participant: --> {}", participantId);
+ log.info("Is Participant on hold: --> {}", isOnHold);
+ log.info("Is Participant muted: --> {}", isMuted);
+ log.info("----------------------------------------------------------------------");
+
+ participantList.add(new ParticipantListResponse.ParticipantInfo(participantId, isOnHold, isMuted));
+ }
+
+ ParticipantListResponse response = new ParticipantListResponse(
+ callConnectionId, correlationId, "Participant list retrieved successfully", participantList
+ );
+ return ResponseEntity.ok(response);
+ } else {
+ log.warn("No participants returned in the response.");
+ ParticipantListResponse emptyResponse = new ParticipantListResponse(
+ callConnectionId, correlationId, "No participants found", new ArrayList<>()
+ );
+ return ResponseEntity.ok(emptyResponse);
+ }
+ } catch (Exception e) {
+ log.error("Error getting participant list: {}", e.getMessage());
+ ParticipantListResponse errorResponse = new ParticipantListResponse(
+ callConnectionId, null, "Failed to get participant list: " + e.getMessage(), new ArrayList<>()
+ );
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "06. Get Participant APIs", description = "Get Participant APIs")
+ @PostMapping("/getParticipantList")
+ public ResponseEntity getParticipantList(@RequestParam String callConnectionId) {
+ try {
+ CallConnection callConnection = getConnection(callConnectionId);
+ CallConnectionProperties properties = callConnection.getCallProperties();
+ String correlationId = properties.getCorrelationId();
+
+ PagedIterable participants = callConnection.listParticipants();
+
+ if (participants != null) {
+ List participantList = new ArrayList<>();
+
+ for (CallParticipant participant : participants) {
+ String participantId = participant.getIdentifier().getRawId();
+ boolean isOnHold = participant.isOnHold();
+ boolean isMuted = participant.isMuted();
+
+ log.info("----------------------------------------------------------------------");
+ log.info("Participant: --> {}", participantId);
+ log.info("Is Participant on hold: --> {}", isOnHold);
+ log.info("Is Participant muted: --> {}", isMuted);
+ log.info("----------------------------------------------------------------------");
+
+ participantList.add(new ParticipantListResponse.ParticipantInfo(participantId, isOnHold, isMuted));
+ }
+
+ ParticipantListResponse response = new ParticipantListResponse(
+ callConnectionId, correlationId, "Participant list retrieved successfully", participantList
+ );
+ return ResponseEntity.ok(response);
+ } else {
+ log.warn("No participants returned in the response.");
+ ParticipantListResponse emptyResponse = new ParticipantListResponse(
+ callConnectionId, correlationId, "No participants found", new ArrayList<>()
+ );
+ return ResponseEntity.ok(emptyResponse);
+ }
+ } catch (Exception e) {
+ log.error("Error getting participant list: {}", e.getMessage());
+ ParticipantListResponse errorResponse = new ParticipantListResponse(
+ callConnectionId, null, "Failed to get participant list: " + e.getMessage(), new ArrayList<>()
+ );
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+ }
+ }
+
+ @Tag(name = "07. Mute Participant APIs", description = "Mute Participant APIs")
+ @PostMapping("/muteParticipantAsync")
+ public ResponseEntity muteParticipantAsync(@RequestParam String callConnectionId, @RequestParam String targetAcsUserId) {
+ try {
+ CommunicationIdentifier target = new CommunicationUserIdentifier(targetAcsUserId);
+ CallConnection callConnection = getConnection(callConnectionId);
+
+ MuteParticipantOptions options = new MuteParticipantOptions(target)
+ .setOperationContext("muteContext");
+
+ // Assuming you're calling a method like muteParticipantWithResponse(options, context)
+ callConnection.muteParticipantWithResponse(options, Context.NONE);
+
+ log.info("Muted participant asynchronously: {}", targetAcsUserId);
+ return ResponseEntity.ok("Muted participant (async).");
+ } catch (Exception e) {
+ log.error("Error muting participant asynchronously: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to mute participant asynchronously.");
+ }
+ }
+
+ @Tag(name = "07. Mute Participant APIs", description = "Mute Participant APIs")
+ @PostMapping("/muteParticipant")
+ public ResponseEntity muteParticipant(@RequestParam String callConnectionId, @RequestParam String targetAcsUserId) {
+ try {
+ CommunicationIdentifier target = new CommunicationUserIdentifier(targetAcsUserId);
+ CallConnection callConnection = getConnection(callConnectionId);
+
+ callConnection.muteParticipant(target); // Synchronous mute using options if method is available
+ log.info("Muted participant synchronously: {}", targetAcsUserId);
+ return ResponseEntity.ok("Muted participant.");
+ } catch (Exception e) {
+ log.error("Error muting participant: {}", e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to mute participant.");
+ }
+ }
+
+ @Tag(name = "08. Add/Remove Participant APIs", description = "Add/Remove Participant APIs")
+ @PostMapping("/addParticipantAsync")
+ public ResponseEntity