如何将Twilio呼入呼出通话转为会议实现语音coaching功能
实现方案总思路
你的判断是对的,基于Twilio会议实现coaching是最优方案,推荐直接把现有呼入、呼出通话的逻辑从「两方直连」改成「默认进入专属会议」,不需要等到需要加coach的时候再转正在进行的通话——后者需要额外调用Twilio REST API触发通话转移,链路更长更容易出问题,默认进会议的方案两方通话体验和之前完全一致,还天然支持后续加人。
如果确实需要按需触发把已经在进行的普通通话转会议,下文也会给出对应实现。
一、默认进会议的改造方案(推荐)
1. 呼出场景改造
前端代码不需要做任何修改,还是沿用原来的device.connect触发呼叫逻辑,仅需改造后端接口:
呼出场景后端改造代码
const response = new VoiceResponse(); // 生成唯一会议ID,可和业务侧的通话记录绑定存储,后续加coach时直接用 const conferenceId = `conf_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; // 先把主叫(前端Twilio Device用户)接入会议 const dial = response.dial({ callerId: fromNumber, record: "record-from-answer", recordingStatusCallback: "/twilio/voice/record" }); dial.conference({ // 关闭会议默认等待音,两方接入后直接通话 waitUrl: '', startConferenceOnEnter: true, endConferenceOnExit: true }, conferenceId); res.set("Content-Type", "text/xml"); res.send(response.toString());
额外新增两个回调接口,用于主叫接入后自动呼叫被叫进同一个会议:
// 会议状态回调接口,需要在Twilio控制台配置或者创建会议时指定statusCallback app.post('/twilio/conference/event', async (req, res) => { const { ConferenceSid, StatusCallBackEvent, To } = req.body; // 主叫成功进入会议后,触发外呼被叫 if (StatusCallBackEvent === 'participant-join' && req.body.From === fromNumber) { const twilioClient = require('twilio')(accountSid, authToken); // 外呼被叫 await twilioClient.calls.create({ url: `/twilio/conference/join?confId=${conferenceId}`, // 被叫接通后进会议的TwiML接口 to: To, from: fromNumber }) } res.sendStatus(200); }) // 被叫进会议的通用TwiML接口 app.post('/twilio/conference/join', (req, res) => { const response = new VoiceResponse(); const dial = response.dial(); dial.conference({ waitUrl: '', startConferenceOnEnter: true, endConferenceOnExit: true }, req.query.confId); res.set("Content-Type", "text/xml"); res.send(response.toString()); })
2. 呼入场景改造
呼入场景后端改造代码
const response = new VoiceResponse(); // 生成唯一会议ID,和当前呼入记录绑定存储 const conferenceId = `conf_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; // 先呼前端坐席用户 const dial = response.dial({ callerId: req.body.From, answerOnBridge: true, record: "record-from-answer", recordingStatusCallback: "/twilio/voice/record", }); dial.conference({ waitUrl: '', startConferenceOnEnter: true, endConferenceOnExit: true }, conferenceId); res.set("Content-Type", "text/xml"); res.send(response.toString());
坐席接入会议后的逻辑和呼出场景完全一致,在/twilio/conference/event回调里判断坐席接入成功后,把呼入用户接入同一个会议即可。
注意:如果需要coach加入后,原有两方挂断会议也不会自动结束,把
endConferenceOnExit参数改为false即可。
二、按需转移现有普通通话的实现方案
如果不想修改现有默认通话逻辑,仅在需要加coach的时候才把正在进行的两方通话转会议,按以下步骤操作:
- 提前在通话建立时,把两方的
CallSid存储到业务数据库和当前通话绑定 - 触发转会议时,分别调用Twilio API把两路通话重定向到进会议的TwiML接口即可,转移过程中仅有极短静音,用户感知很弱:
const twilioClient = require('twilio')(accountSid, authToken); const conferenceId = '生成的会议ID'; // 转移第一路通话 await twilioClient.calls(第一路通话的CallSid).update({ url: `/twilio/conference/join?confId=${conferenceId}`, method: 'POST' }); // 转移第二路通话 await twilioClient.calls(第二路通话的CallSid).update({ url: `/twilio/conference/join?confId=${conferenceId}`, method: 'POST' });
三、三种coach模式的实现
Twilio会议的参与者参数天然支持三种模式,呼叫coach进会议时传入对应参数即可,参与者SID可以通过Twilio会议参与者列表接口获取,也可以在每个参会人加入会议时存储到业务侧:
- 主动参会模式:coach可听可讲,所有参会人都能听到coach的声音,coach也能听到所有人的声音
await twilioClient.conferences(会议的ConferenceSid).participants.create({ to: coach的手机号码, from: 你的平台外呼号码, muted: false, coach: null }) - 被动旁听模式:coach仅能听到所有参会人声音,不能发言
await twilioClient.conferences(会议的ConferenceSid).participants.create({ to: coach的手机号码, from: 你的平台外呼号码, muted: true, coach: null }) - 仅对单方耳语模式:coach的发言仅指定的一方能听到,coach可以听到所有参会人的声音
await twilioClient.conferences(会议的ConferenceSid).participants.create({ to: coach的手机号码, from: 你的平台外呼号码, // 填入需要被耳语的参会方的ParticipantSid coach: 目标参会方的ParticipantSid })
内容的提问来源于stack exchange,提问作者Dominik Myszkowski
相关产品推荐
相关产品推荐

