通话自动化使用 REST API 接口来接收操作请求,并提供响应以告知请求是否已成功提交。 由于调用的异步性质,大多数操作在成功完成或失败时会触发相应的事件。 本文介绍在调用期间可供开发人员执行的操作,例如 SendDTMF 和 ContinuousDtmfRecognition。 动作附带有说明如何调用特定操作的示例代码。
呼叫自动化支持本文中未包含的其他操作,以管理呼叫和录音。
Note
通话自动化目前不能与 Microsoft Teams 互操作。 不支持通过呼叫自动化向 Teams 用户发出或重定向呼叫或向 Teams 用户播放音频等作。
Prerequisites
- 介绍操作事件编程模型和事件回调的通话自动化概念指南。
- 了解本文中使用的用户标识符。
CommunicationUserIdentifierPhoneNumberIdentifier
- 详细了解如何使用 呼叫自动化来控制和引导呼叫,该调用将指导你处理呼叫的基础知识。
对于所有代码示例,client是您可以创建的CallAutomationClient对象,如下所示,callConnection是您从CallConnection或Answer响应中获取的CreateCall对象。 还可以从应用程序收到的回调事件中获取它。
var callAutomationClient = new CallAutomationClient("<Azure Communication Services connection string>");
CallAutomationClient callAutomationClient = new CallAutomationClientBuilder()
.connectionString("<Azure Communication Services connection string>")
.buildClient();
callAutomationClient = new CallAutomationClient(("<Azure Communication Services connection string>");
call_automation_client = CallAutomationClient.from_connection_string((("<Azure Communication Services connection string>")
发送 DTMF
你可以向外部参与者发送双音多频 (DTMF) 音调。 如果你已在通话中,并且需要邀请另一个具有分机号码或使用交互式语音响应菜单的参与者,此功能可能很有用。
Note
此功能仅适用于公用电话网络上的外部参与者,并支持一次最多发送 18 个音调。
SendDtmfAsync 方法
向外部参与者发送 DTMF 音调列表。
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);
List<DtmfTone> tones = Arrays.asList(DtmfTone.ONE, DtmfTone.TWO, DtmfTone.THREE, DtmfTone.POUND);
SendDtmfTonesOptions options = new SendDtmfTonesOptions(tones, new PhoneNumberIdentifier(c2Target));
options.setOperationContext("dtmfs-to-ivr");
callAutomationClient.getCallConnectionAsync(callConnectionId)
.getCallMediaAsync()
.sendDtmfTonesWithResponse(options)
.block();
const tones = [DtmfTone.One, DtmfTone.Two, DtmfTone.Three];
const sendDtmfTonesOptions: SendDtmfTonesOptions = {
operationContext: "dtmfs-to-ivr"
};
const result: SendDtmfTonesResult = await callAutomationClient.getCallConnection(callConnectionId)
.getCallMedia()
.sendDtmfTones(tones, {
phoneNumber: c2Target
}, sendDtmfTonesOptions);
console.log("sendDtmfTones, result=%s", result);
tones = [DtmfTone.ONE, DtmfTone.TWO, DtmfTone.THREE]
result = call_automation_client.get_call_connection(call_connection_id).send_dtmf_tones(
tones = tones,
target_participant = PhoneNumberIdentifier(c2_target),
operation_context = "dtmfs-to-ivr")
app.logger.info("Send dtmf, result=%s", result)
当应用程序发送这些 DTMF 音调时,你会收到事件更新。 可使用 SendDtmfTonesCompleted 和 SendDtmfTonesFailed 事件在应用程序中创建业务逻辑,以确定后续步骤。
示例事件:SendDtmfTonesCompleted
if (acsEvent is SendDtmfTonesCompleted sendDtmfCompleted)
{
logger.LogInformation("Send DTMF succeeded, context={context}", sendDtmfCompleted.OperationContext);
}
if (acsEvent instanceof SendDtmfTonesCompleted) {
SendDtmfTonesCompleted event = (SendDtmfTonesCompleted) acsEvent;
log.info("Send dtmf succeeded: context=" + event.getOperationContext());
}
if (event.type === "Microsoft.Communication.SendDtmfTonesCompleted") {
console.log("Send dtmf succeeded: context=%s", eventData.operationContext);
}
if event.type == "Microsoft.Communication.SendDtmfTonesCompleted":
app.logger.info("Send dtmf succeeded: context=%s", event.data['operationContext']);
示例事件:SendDtmfTonesFailed
if (acsEvent is SendDtmfTonesFailed sendDtmfFailed)
{
logger.LogInformation("Send dtmf failed: result={result}, context={context}",
sendDtmfFailed.ResultInformation?.Message, sendDtmfFailed.OperationContext);
}
if (acsEvent instanceof SendDtmfTonesFailed) {
SendDtmfTonesFailed event = (SendDtmfTonesFailed) acsEvent;
log.info("Send dtmf failed: result=" + event.getResultInformation().getMessage() + ", context="
+ event.getOperationContext());
}
if (event.type === "Microsoft.Communication.SendDtmfTonesFailed") {
console.log("sendDtmfTones failed: result=%s, context=%s",
eventData.resultInformation.message,
eventData.operationContext);
}
if event.type == "Microsoft.Communication.SendDtmfTonesFailed":
app.logger.info("Send dtmf failed: result=%s, context=%s", event.data['resultInformation']['message'], event.data['operationContext'])
连续 DTMF 识别
可在整个通话中订阅接收连续 DTMF 音调。 当目标参与者按下键盘上的键时,应用程序会收到 DTMF 音调。 当参与者按下这些音调时,它们会一个接一个地发送到应用程序。
StartContinuousDtmfRecognitionAsync 方法
开始检测参与者发送的 DTMF 音调。
await callAutomationClient.GetCallConnection(callConnectionId)
.GetCallMedia()
.StartContinuousDtmfRecognitionAsync(new PhoneNumberIdentifier(c2Target), "dtmf-reco-on-c2");
ContinuousDtmfRecognitionOptions options = new ContinuousDtmfRecognitionOptions(new PhoneNumberIdentifier(c2Target));
options.setOperationContext("dtmf-reco-on-c2");
callAutomationClient.getCallConnectionAsync(callConnectionId)
.getCallMediaAsync()
.startContinuousDtmfRecognitionWithResponse(options)
.block();
const continuousDtmfRecognitionOptions: ContinuousDtmfRecognitionOptions = {
operationContext: "dtmf-reco-on-c2"
};
await callAutomationclient.getCallConnection(callConnectionId)
.getCallMedia()
.startContinuousDtmfRecognition({
phoneNumber: c2Target
}, continuousDtmfRecognitionOptions);
call_automation_client.get_call_connection(
call_connection_id
).start_continuous_dtmf_recognition(
target_participant=PhoneNumberIdentifier(c2_target),
operation_context="dtmf-reco-on-c2",
)
app.logger.info("Started continuous DTMF recognition")
当应用程序不再希望从参与者接收 DTMF 音调时,请使用 StopContinuousDtmfRecognitionAsync 此方法让 Azure 通信服务知道停止检测 DTMF 音调。
StopContinuousDtmfRecognitionAsync
停止检测参与者发送的 DTMF 音调。
var continuousDtmfRecognitionOptions = new ContinuousDtmfRecognitionOptions(new PhoneNumberIdentifier(callerPhonenumber))
{
OperationContext = "dtmf-reco-on-c2"
};
var startContinuousDtmfRecognitionAsyncResult = await callAutomationClient.GetCallConnection(callConnectionId)
.GetCallMedia()
.StartContinuousDtmfRecognitionAsync(continuousDtmfRecognitionOptions);
ContinuousDtmfRecognitionOptions options = new ContinuousDtmfRecognitionOptions(new PhoneNumberIdentifier(c2Target));
options.setOperationContext("dtmf-reco-on-c2");
callAutomationClient.getCallConnectionAsync(callConnectionId)
.getCallMediaAsync()
.stopContinuousDtmfRecognitionWithResponse(options)
.block();
const continuousDtmfRecognitionOptions: ContinuousDtmfRecognitionOptions = {
operationContext: "dtmf-reco-on-c2"
};
await callAutomationclient.getCallConnection(callConnectionId)
.getCallMedia()
.stopContinuousDtmfRecognition({
phoneNumber: c2Target
}, continuousDtmfRecognitionOptions);
call_automation_client.get_call_connection(call_connection_id).stop_continuous_dtmf_recognition(
target_participant=PhoneNumberIdentifier(c2_target),
operation_context="dtmf-reco-on-c2")
app.logger.info("Stopped continuous DTMF recognition")
当这些操作成功或失败时,应用程序会收到事件更新。 可以使用这些事件来生成自定义业务逻辑,以配置应用程序在收到这些事件更新时需要执行的下一步。
ContinuousDtmfRecognitionToneReceived 事件
如何处理成功检测到的 DTMF 音调的示例。
if (acsEvent is ContinuousDtmfRecognitionToneReceived continuousDtmfRecognitionToneReceived)
{
logger.LogInformation("Tone detected: sequenceId={sequenceId}, tone={tone}",
continuousDtmfRecognitionToneReceived.SequenceId,
continuousDtmfRecognitionToneReceived.Tone);
}
if (acsEvent instanceof ContinuousDtmfRecognitionToneReceived) {
ContinuousDtmfRecognitionToneReceived event = (ContinuousDtmfRecognitionToneReceived) acsEvent;
log.info("Tone detected: sequenceId=" + event.getSequenceId()
+ ", tone=" + event.getTone().convertToString()
+ ", context=" + event.getOperationContext());
}
if (event.type === "Microsoft.Communication.ContinuousDtmfRecognitionToneReceived") {
console.log("Tone detected: sequenceId=%s, tone=%s, context=%s",
eventData.sequenceId,
eventData.tone,
eventData.operationContext);
}
if event.type == "Microsoft.Communication.ContinuousDtmfRecognitionToneReceived":
app.logger.info("Tone detected: sequenceId=%s, tone=%s, context=%s",
event.data['sequenceId'],
event.data['tone'],
event.data['operationContext'])
Azure 通信服务在 SequenceId 事件中为你提供 ContinuousDtmfRecognitionToneReceived。 应用程序可以使用它重新构造参与者输入 DTMF 音调的顺序。
ContinuousDtmfRecognitionFailed 事件
DTMF 音调检测失败时的处理示例。
if (acsEvent is ContinuousDtmfRecognitionToneFailed continuousDtmfRecognitionToneFailed)
{
logger.LogInformation("Start continuous DTMF recognition failed, result={result}, context={context}",
continuousDtmfRecognitionToneFailed.ResultInformation?.Message,
continuousDtmfRecognitionToneFailed.OperationContext);
}
if (acsEvent instanceof ContinuousDtmfRecognitionToneFailed) {
ContinuousDtmfRecognitionToneFailed event = (ContinuousDtmfRecognitionToneFailed) acsEvent;
log.info("Tone failed: result="+ event.getResultInformation().getMessage()
+ ", context=" + event.getOperationContext());
}
if (event.type === "Microsoft.Communication.ContinuousDtmfRecognitionToneFailed") {
console.log("Tone failed: result=%s, context=%s", eventData.resultInformation.message, eventData.operationContext);
}
if event.type == "Microsoft.Communication.ContinuousDtmfRecognitionToneFailed":
app.logger.info(
"Tone failed: result=%s, context=%s",
event.data["resultInformation"]["message"],
event.data["operationContext"],
)
ContinuousDtmfRecognitionStopped 事件
连续 DTMF 识别停止时的操作示例。 也许应用程序调用了 StopContinuousDtmfRecognitionAsync 事件或调用结束。
if (acsEvent is ContinuousDtmfRecognitionStopped continuousDtmfRecognitionStopped)
{
logger.LogInformation("Continuous DTMF recognition stopped, context={context}", continuousDtmfRecognitionStopped.OperationContext);
}
if (acsEvent instanceof ContinuousDtmfRecognitionStopped) {
ContinuousDtmfRecognitionStopped event = (ContinuousDtmfRecognitionStopped) acsEvent;
log.info("Tone stopped, context=" + event.getOperationContext());
}
if (event.type === "Microsoft.Communication.ContinuousDtmfRecognitionStopped") {
console.log("Tone stopped: context=%s", eventData.operationContext);
}
if event.type == "Microsoft.Communication.ContinuousDtmfRecognitionStopped":
app.logger.info("Tone stopped: context=%s", event.data["operationContext"])
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);
*/
// Option 1: Hold with options
PlaySource playSource = /* initialize playSource */;
HoldOptions holdOptions = new HoldOptions(target)
.setOperationCallbackUrl(appConfig.getBasecallbackuri())
.setPlaySource(playSource)
.setOperationContext("holdPstnParticipant");
client.getCallConnection(callConnectionId).getCallMedia().holdWithResponse(holdOptions, Context.NONE);
/*
// Option 2: Hold without additional options
client.getCallConnection(callConnectionId).getCallMedia().hold(target);
*/
// Option 1: Hold with options
const options = {
playSource: playSource,
operationContext: "holdUserContext",
operationCallbackUrl: "URL" // replace with actual callback URL
};
await callMedia.hold(targetuser, options);
/*
// Option 2: Hold without additional options
await callMedia.hold(targetuser);
*/
# Option 1: Hold without additional options
call_connection_client.hold(target_participant=PhoneNumberIdentifier(TARGET_PHONE_NUMBER))
'''
# Option 2: Hold with options
call_connection_client.hold(
target_participant=PhoneNumberIdentifier(TARGET_PHONE_NUMBER),
play_source=play_source,
operation_context="holdUserContext",
operation_callback_url="URL" # replace with actual callback URL
)
'''
Unhold
通过“取消保持”操作,开发人员可恢复之前暂停的参与者与系统或代理之间的对话。 当参与者被取消保持后,他们可以再次听到系统或代理的声音。
var unHoldOptions = new UnholdOptions(target)
{
OperationContext = "UnHoldPstnParticipant"
};
// Option 1
var UnHoldParticipant = await callMedia.UnholdAsync(unHoldOptions);
/*
// Option 2
var UnHoldParticipant = await callMedia.UnholdAsync(target);
*/
// Option 1
client.getCallConnection(callConnectionId).getCallMedia().unholdWithResponse(target, "unholdPstnParticipant", Context.NONE);
/*
// Option 2
client.getCallConnection(callConnectionId).getCallMedia().unhold(target);
*/
const unholdOptions = {
operationContext: "unholdUserContext"
};
// Option 1
await callMedia.unhold(target);
/*
// Option 2
await callMedia.unhold(target, unholdOptions);
*/
# Option 1
call_connection_client.unhold(target_participant=PhoneNumberIdentifier(TARGET_PHONE_NUMBER))
'''
# Option 2
call_connection_client.unhold(target_participant=PhoneNumberIdentifier(TARGET_PHONE_NUMBER), operation_context="holdUserContext")
'''
音频流式处理
使用音频流式传输,你可以从正在进行的电话中订阅实时音频流。 有关如何开始使用音频流式处理以及有关音频流式处理回调事件的详细信息,请参阅 快速入门:服务器端音频流式处理。
实时听录
通过使用实时听录,你可以访问正在进行的通话音频的实时听录。 有关如何开始使用实时听录和有关实时听录回调事件的信息的详细信息,请参阅 在应用程序中添加实时听录。
下表说明了在前一个操作仍在运行/或排队时允许哪些媒体操作运行或排队。
| 现有作 |
呼叫腿 |
Allowed |
Disallowed |
PlayToAll |
Main |
PlayToAll、Recognize(Non-Group Call)、PlayTo、Recognize(Group Call)、SendDTMF、StartContinuousDtmfRecognition |
None |
Recognize(Non-Group Call) |
Main |
PlayToAll、Recognize(Non-Group Call)、PlayTo、Recognize(Group Call)、SendDTMF、StartContinuousDtmfRecognition |
None |
PlayTo |
Sub |
PlayToAll、Recognize(Non-Group Call) |
PlayTo、Recognize(Group Call)、SendDTMF、StartContinuousDtmfRecognition |
Recognize(Group Call) |
Sub |
PlayToAll、Recognize(Non-Group Call) |
PlayTo、Recognize(Group Call)、SendDTMF、StartContinuousDtmfRecognition |
SendDTMF |
Sub |
PlayToAll、Recognize(Non-Group Call) |
PlayTo、Recognize(Group Call)、SendDTMF、StartContinuousDtmfRecognition |
StartContinuousDtmfRecognition |
Sub |
PlayToAll、、Recognize(Non-Group Call)PlayTo、Recognize(Group Call)、SendDTMF、StartContinuousDtmfRecognition |
None |