如何通过Twilio Studio/TwiML实现收听上一通来电语音邮件录音
嘿,很高兴帮你搞定这个需求!你想让呼叫者能播放上一位留下的语音邮件,不管用Twilio Studio还是TwiML都能实现,我给你详细说下两种方案:
Twilio Studio 实现方案
你提到的RecordingURL确实是核心,但Studio本身没法自动记住最新的录音地址,得借助Twilio的Functions + Sync来存储和获取最新录音URL,步骤如下:
1. 存储最新录音URL
在你的录音流程里,当Record组件完成录音后(也就是「Recording Completed」分支),添加一个「Run Function」组件,调用你写的Twilio Function,把当前录音的URL传进去:
- 函数参数里加个
recordingUrl,值填{{widgets.你的录音组件名称.RecordingUrl}}
这个Function的作用是把最新的录音URL存在Twilio Sync里(用Sync Document来存,相当于一个键值对),代码示例:
exports.handler = async function(context, event, callback) { const client = context.getTwilioClient(); try { // 更新或创建名为latest_recording_url的Sync Document await client.sync.v1.services(context.SYNC_SERVICE_SID) .documents('latest_recording_url') .update({data: {url: event.recordingUrl}}); callback(null, {status: "success"}); } catch (err) { callback(err); } };
2. 获取并播放最新录音
当呼叫者选择播放上一条留言时(比如用Gather组件让用户按某个键触发),先调用另一个Twilio Function去Sync里取最新的录音URL:
- 函数返回后,把返回的
recordingUrl赋值给Studio的流程变量,比如{{flow.data.latest_recording_url}}
然后用Play组件,把「音频文件URL」设置成这个变量{{flow.data.latest_recording_url}}就行啦。如果还没有录音,你可以在Function里返回null,然后在Studio里加个判断,用Say组件提示用户“目前没有留言”。
获取URL的Function代码示例:
exports.handler = async function(context, event, callback) { const client = context.getTwilioClient(); try { const doc = await client.sync.v1.services(context.SYNC_SERVICE_SID) .documents('latest_recording_url') .fetch(); callback(null, {recordingUrl: doc.data.url}); } catch (err) { // 还没录音的情况,返回空值 callback(null, {recordingUrl: null}); } };
TwiML 实现方案
如果Studio的配置让你觉得麻烦,用TwiML+后端代码会更灵活,核心逻辑和Studio类似,只是用代码来处理流程:
1. 初始引导TwiML
先给呼叫者返回带选项的TwiML,让用户选择播放或录音:
<Response> <Gather numDigits="1" action="/handle-choice"> <Say>欢迎来电。按1播放上一条留言,按2录制新留言。</Say> </Gather> </Response>
2. 处理用户选择
当用户按键后,后端根据选择执行不同操作:
- 选1:从Sync(或你的数据库)取最新录音URL,用
<Play>播放;如果没有录音,用<Say>提示 - 选2:用
<Record>组件让用户留言,录音完成后把RecordingURL存储到Sync
Node.js后端示例代码:
const express = require('express'); const twilio = require('twilio'); const app = express(); app.use(express.urlencoded({ extended: false })); // 处理用户选择 app.post('/handle-choice', async (req, res) => { const twiml = new twilio.twiml.VoiceResponse(); const choice = req.body.Digits; if (choice === '1') { // 获取最新录音URL const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); try { const doc = await client.sync.v1.services(process.env.SYNC_SERVICE_SID) .documents('latest_recording_url') .fetch(); twiml.play(doc.data.url); } catch (err) { twiml.say('目前没有留言,请稍后再试。'); } } else if (choice === '2') { twiml.record({ action: '/save-recording', finishOnKey: '#', maxLength: 60 }); twiml.say('请在提示音后留言,结束时按井号键。'); } res.type('text/xml'); res.send(twiml.toString()); }); // 保存录音URL app.post('/save-recording', async (req, res) => { const recordingUrl = req.body.RecordingUrl; const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); // 存储到Sync await client.sync.v1.services(process.env.SYNC_SERVICE_SID) .documents('latest_recording_url') .update({data: {url: recordingUrl}}); const twiml = new twilio.twiml.VoiceResponse(); twiml.say('留言已保存,感谢来电!'); res.type('text/xml'); res.send(twiml.toString()); }); app.listen(3000, () => console.log('Server running on port 3000'));
注意事项
- 要确保你的Twilio账户启用了Sync服务,并且在Function或后端代码里配置好
SYNC_SERVICE_SID、TWILIO_ACCOUNT_SID和TWILIO_AUTH_TOKEN这些环境变量 - 如果担心录音URL的安全性,可以用Twilio API生成带过期时间的签名URL,不过一般内部使用的话默认URL就够用了
内容的提问来源于stack exchange,提问作者Matt

