twilio - Twilio Voice - 允许当前呼叫者收听前一个呼叫者录制的语音邮件
问题描述
希望有人能指出我正确的方向。我希望能够通过 Twilio Studio 做到这一点。如果没有,我可以学习 TwiML。这大约是我的大脑所能伸展的范围。
我在 Twilio Studio 中创建了一个简单的流程,使呼叫者能够录制语音邮件。我想为当前呼叫者添加一个选项,以便能够播放前一个呼叫者录制的语音邮件。我想我需要为此使用 Say/Play 小部件。我需要为“音频文件的 URL”使用什么才能播放之前录制的语音邮件?我假设每次呼叫者留下语音邮件时此 URL 都会更改,因此需要自动更新。我可以以某种方式使用“RecordingURL”吗?有没有使用 TwiML 的解决方案?任何帮助表示赞赏
谢谢!
解决方案
如果没有办法在通话之间保持状态,这是不可能的。最好的方法是拥有某种类型的数据库,您可以存储记录 SID 并在未来的流程中引用它。您可以使用Twilio Sync之类的工具或Airtable来执行此操作,但它确实需要代码。
如果不涉及一些编码,我想不出一种方法来做到这一点。
替代方法是列出记录记录并将最近的记录从列表中拉出,但不理想,因为您不知道最后一次记录发生的时间。
另一种方法是修改与该 Studio Flow 的 Twilio 电话号码关联的 webhook,以将最后一个 Recording SID 传递到您的流中,然后使用该 SID 动态构建要播放的录制 URL,如下面的 JSON 流所示(您可以在创建新的 Studio Flow 时导入)。
不要忘记将recordingStatuscallback 设置为上述Twilio 函数,该函数会更新电话号码webhook 以传入录制SID。您将录制语音邮件小部件的录制状态回调设置为指向您唯一的功能域和功能路径(当前设置为:)https://magnolia-kitty-3234.twil.io/phUpdate
。
随时改进以下内容并分享。
Twilio 功能代码
exports.handler = function(context, event, callback) {
let client = context.getTwilioClient();
const telephoneNumAccountSid = "PN..."; \\ set this to your Phone Number SID
const accountSid = event.AccountSid;
const studioFlowSid = "FW..."; \\ set this to your Studio Flow SID
const webhookUrl = `https://webhooks.twilio.com/v1/Accounts/${accountSid}/Flows/${studioFlowSid}`;
const recordingSid = event.RecordingSid;
client
.incomingPhoneNumbers(telephoneNumAccountSid)
.update({ voiceUrl: `${webhookUrl}?recording=${recordingSid}`, voiceFallbackUrl: `${webhookUrl}?recording=${recordingSid}` })
.then (result => {
console.log(result.voiceUrl);
callback(null, "success");
})
.catch(err => {
console.log(err);
callback("error");
});
};
工作室流 JSON
{
"description": "A New Flow",
"states": [
{
"name": "Trigger",
"type": "trigger",
"transitions": [
{
"event": "incomingMessage"
},
{
"next": "set_variables_1",
"event": "incomingCall"
},
{
"event": "incomingRequest"
}
],
"properties": {
"offset": {
"x": 0,
"y": 0
}
}
},
{
"name": "split_1",
"type": "split-based-on",
"transitions": [
{
"next": "say_play_2",
"event": "noMatch"
},
{
"next": "gather_1",
"event": "match",
"conditions": [
{
"friendly_name": "{{trigger.call.recording}}",
"arguments": [
"{{trigger.call.recording}}"
],
"type": "is_not_blank",
"value": "Is Not Blank"
}
]
}
],
"properties": {
"input": "{{trigger.call.recording}}",
"offset": {
"x": 110,
"y": 350
}
}
},
{
"name": "say_play_1",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"play": "https://api.twilio.com/2010-04-01/Accounts/{{flow.variables.accountSid}}/Recordings/{{flow.variables.recording}}.mp3",
"offset": {
"x": 450,
"y": 1110
},
"loop": 1
}
},
{
"name": "set_variables_1",
"type": "set-variables",
"transitions": [
{
"next": "split_1",
"event": "next"
}
],
"properties": {
"variables": [
{
"value": "{{trigger.call.recording}}",
"key": "recording"
},
{
"value": "{{trigger.call.AccountSid}}",
"key": "accountSid"
}
],
"offset": {
"x": 60,
"y": 170
}
}
},
{
"name": "say_play_2",
"type": "say-play",
"transitions": [
{
"next": "record_voicemail_1",
"event": "audioComplete"
}
],
"properties": {
"voice": "Polly.Joanna-Neural",
"offset": {
"x": -120,
"y": 650
},
"loop": 1,
"say": "Please leave a message at the beep!",
"language": "en-US"
}
},
{
"name": "record_voicemail_1",
"type": "record-voicemail",
"transitions": [
{
"event": "recordingComplete"
},
{
"event": "noAudio"
},
{
"event": "hangup"
}
],
"properties": {
"transcribe": false,
"offset": {
"x": -120,
"y": 860
},
"trim": "trim-silence",
"play_beep": "true",
"recording_status_callback_url": "https://magnolia-kitty-3234.twil.io/phUpdate",
"timeout": 5,
"max_length": 3600
}
},
{
"name": "gather_1",
"type": "gather-input-on-call",
"transitions": [
{
"next": "split_2",
"event": "keypress"
},
{
"event": "speech"
},
{
"event": "timeout"
}
],
"properties": {
"number_of_digits": 1,
"speech_timeout": "auto",
"offset": {
"x": 300,
"y": 650
},
"loop": 1,
"finish_on_key": "#",
"say": "There is a previous recording, press 1 if you want to listen to it or 2 if you want to leave a new voicemail.",
"stop_gather": true,
"gather_language": "en",
"profanity_filter": "true",
"timeout": 5
}
},
{
"name": "split_2",
"type": "split-based-on",
"transitions": [
{
"event": "noMatch"
},
{
"next": "say_play_1",
"event": "match",
"conditions": [
{
"friendly_name": "If value equal_to 1",
"arguments": [
"{{widgets.gather_1.Digits}}"
],
"type": "equal_to",
"value": "1"
}
]
},
{
"next": "say_play_2",
"event": "match",
"conditions": [
{
"friendly_name": "If value equal_to 2",
"arguments": [
"{{widgets.gather_1.Digits}}"
],
"type": "equal_to",
"value": "2"
}
]
}
],
"properties": {
"input": "{{widgets.gather_1.Digits}}",
"offset": {
"x": 280,
"y": 870
}
}
}
],
"initial_state": "Trigger",
"flags": {
"allow_concurrent_calls": true
}
}
艾伦
推荐阅读
- google-apps-script - 在新标签页中从脚本打开另一个 google 工作表
- angular - 如何将字符串从 angular 6 发送到 springboot 休息控制器?
- google-tag-manager - 使用 Google 跟踪代码管理器查找和替换页面文本字符串
- kubernetes - Kubernetes etcd 没有出现
- gcc - F2PY 使用 PRINT 给出未解决的外部错误
- documentum - Excel 中的 DQL 结果
- java - 二维目标拦截算法
- javascript - 抑制 onChange 触发反应
- docker - 如何在 docker 文件中运行 `git clone`?
- r - pickerInput 默认选择所有选项