How to control mid-call media actions with Call Automation

Call Automation uses a REST API interface to receive requests for actions and provide responses to notify whether the request was successfully submitted or not. Due to the asynchronous nature of calling, most actions have corresponding events that are triggered when the action completes successfully or fails. This guide covers the actions available to developers during calls, like Send DTMF and Continuous DTMF Recognition. Actions are accompanied with sample code on how to invoke the said action.

Call Automation supports various other actions to manage calls and recording that aren't included in this guide.

Note

Call Automation currently doesn't interoperate with Microsoft Teams. Actions like making, redirecting a call to a Teams user or playing audio to a Teams user using Call Automation isn't supported.

As a prerequisite, we recommend you to read the below articles to make the most of this guide:

  1. Call Automation concepts guide that describes the action-event programming model and event callbacks.
  2. Learn about user identifiers like CommunicationUserIdentifier and PhoneNumberIdentifier used in this guide.
  3. Learn more about how to control and steer calls with Call Automation, which teaches you about dealing with the basics of dealing with a call.

For all the code samples, client is CallAutomationClient object that can be created as shown and callConnection is the CallConnection object obtained from Answer or CreateCall response. You can also obtain it from callback events received by your application.

var callAutomationClient = new CallAutomationClient("<Azure Communication Services connection string>");

Send DTMF

You can send DTMF tones to an external participant, which may be useful when you’re already on a call and need to invite another participant who has an extension number or an IVR menu to navigate.

Note

This is only supported for external PSTN participants and supports sending a maximum of 18 tones at a time.

SendDtmfAsync Method

Send a list of DTMF tones to an external participant.

var tones = new DtmfTone[] { DtmfTone.One, DtmfTone.Two, DtmfTone.Three, DtmfTone.Pound }; 
var sendDtmfTonesOptions = new SendDtmfTonesOptions(tones, new PhoneNumberIdentifier(calleePhonenumber))
{ 
	OperationContext = "dtmfs-to-ivr" 
}; 

var sendDtmfAsyncResult = await callAutomationClient.GetCallConnection(callConnectionId) 
	.GetCallMedia() 
        .SendDtmfTonesAsync(sendDtmfTonesOptions); 

When your application sends these DTMF tones, you receive event updates. You can use the SendDtmfTonesCompleted and SendDtmfTonesFailed events to create business logic in your application to determine the next steps.

Example of SendDtmfTonesCompleted event

if (acsEvent is SendDtmfTonesCompleted sendDtmfCompleted) 
{ 
    logger.LogInformation("Send DTMF succeeded, context={context}", sendDtmfCompleted.OperationContext); 
} 

Example of SendDtmfTonesFailed

if (acsEvent is SendDtmfTonesFailed sendDtmfFailed) 
{ 
    logger.LogInformation("Send dtmf failed: result={result}, context={context}", 
        sendDtmfFailed.ResultInformation?.Message, sendDtmfFailed.OperationContext); 
} 

Continuous DTMF Recognition

You can subscribe to receive continuous DTMF tones throughout the call. Your application receives DTMF tones as the targeted participant presses on a key on their keypad. These tones are sent to your application one by one as the participant is pressing them.

StartContinuousDtmfRecognitionAsync Method

Start detecting DTMF tones sent by a participant.

await callAutomationClient.GetCallConnection(callConnectionId) 
    .GetCallMedia() 
    .StartContinuousDtmfRecognitionAsync(new PhoneNumberIdentifier(c2Target), "dtmf-reco-on-c2"); 

When your application no longer wishes to receive DTMF tones from the participant anymore, you can use the StopContinuousDtmfRecognitionAsync method to let Azure Communication Services know to stop detecting DTMF tones.

StopContinuousDtmfRecognitionAsync

Stop detecting DTMF tones sent by participant.

var continuousDtmfRecognitionOptions = new ContinuousDtmfRecognitionOptions(new PhoneNumberIdentifier(callerPhonenumber)) 
{ 
    OperationContext = "dtmf-reco-on-c2" 
}; 

var startContinuousDtmfRecognitionAsyncResult = await callAutomationClient.GetCallConnection(callConnectionId) 
    .GetCallMedia() 
    .StartContinuousDtmfRecognitionAsync(continuousDtmfRecognitionOptions); 

Your application receives event updates when these actions either succeed or fail. You can use these events to build custom business logic to configure the next step your application needs to take when it receives these event updates.

ContinuousDtmfRecognitionToneReceived Event

Example of how you can handle a DTMF tone successfully detected.

if (acsEvent is ContinuousDtmfRecognitionToneReceived continuousDtmfRecognitionToneReceived) 
{ 
	logger.LogInformation("Tone detected: sequenceId={sequenceId}, tone={tone}", 
	continuousDtmfRecognitionToneReceived.SequenceId, 
        continuousDtmfRecognitionToneReceived.Tone); 
} 

Azure Communication Services provides you with a SequenceId as part of the ContinuousDtmfRecognitionToneReceived event, which your application can use to reconstruct the order in which the participant entered the DTMF tones.

ContinuousDtmfRecognitionFailed Event

Example of how you can handle when DTMF tone detection fails.

if (acsEvent is ContinuousDtmfRecognitionToneFailed continuousDtmfRecognitionToneFailed) 
{ 
    logger.LogInformation("Start continuous DTMF recognition failed, result={result}, context={context}", 
        continuousDtmfRecognitionToneFailed.ResultInformation?.Message, 
        continuousDtmfRecognitionToneFailed.OperationContext); 
} 

ContinuousDtmfRecogntionStopped Event

Example of how to handle when continuous DTMF recognition has stopped, this could be because your application invoked the StopContinuousDtmfRecognitionAsync event or because the call has ended.

if (acsEvent is ContinuousDtmfRecognitionStopped continuousDtmfRecognitionStopped) 
{ 
    logger.LogInformation("Continuous DTMF recognition stopped, context={context}", continuousDtmfRecognitionStopped.OperationContext); 
} 

Hold

The hold action allows developers to temporarily pause a conversation between a participant and a system or agent. This can be useful in scenarios where the participant needs to be transferred to another agent or department or when the agent needs to consult a supervisor in the background before continuing the conversation. During this time you can choose to play audio to the participant that is on hold.

// Option 1: Hold without additional options
await callAutomationClient.GetCallConnection(callConnectionId)
    .GetCallMedia().HoldAsync(c2Target);

/*
// Option 2: Hold with play source
PlaySource playSource = /* initialize playSource */;
await callAutomationClient.GetCallConnection(callConnectionId)
    .GetCallMedia().HoldAsync(c2Target, playSource);

// Option 3: Hold with options
var holdOptions = new HoldOptions(target) 
{ 
    OperationCallbackUri = new Uri(""),
    OperationContext = "holdcontext"
};
await callMedia.HoldAsync(holdOptions);
*/

Unhold

The unhold action allows developers to resume a conversation between a participant and a system or agent that was previously paused. When the participant is taken off hold they will be able to hear the system or agent again.

var unHoldOptions = new UnholdOptions(target) 
{ 
    OperationContext = "UnHoldPstnParticipant" 
}; 

// Option 1
var UnHoldParticipant = await callMedia.UnholdAsync(unHoldOptions);

/* 
// Option 2
var UnHoldParticipant = await callMedia.UnholdAsync(target);
*/