调用Twilio呼出后报错,如何实现音频流接入与事件处理?
我在SST环境中使用WebSocket对接Twilio,已设置呼出呼叫端点为my-end-point/、状态回调端点为my-end-point/record。目前呼叫可接通,但随后会提示错误并挂断(显示“there's an error Goodbye!”)。我希望通过处理Twilio的connected、start、media、stop事件来正确处理呼叫并获取音频流,但尝试的代码无法正常工作,恳请帮助排查修复。
原代码
import { ApiHandler } from "sst/node/api"; import Twilio from "twilio"; import WebSocket from "ws"; export const handler = ApiHandler(async (event) => { const accountSid = process.env.TWILIO_ACCOUNT_SID; const authToken = process.env.TWILIO_AUTH_TOKEN; // 创建认证后的Twilio客户端实例 const client = Twilio(accountSid, authToken); const ws = new WebSocket( "wss://4pi3fywj9u.execute-api.eu-central-1.amazonaws.com/JosephAdeabah" ); ws.on("open", function connectionOpen() { console.log("WebSocket连接已打开"); }); ws.on("message", async function incoming(rawMessage) { const message = rawMessage.toString(); // 将原始数据转为字符串 console.log("收到消息:", message); const msg = JSON.parse(message); let call; switch (msg.event) { case "connected": // 处理"connected"事件 console.log("呼叫已连接"); break; case "start": // 处理"start"事件 try { call = await client.calls.create({ url: "https://0sbfdfxbb0.execute-api.eu-central-1.amazonaws.com", to: "+13613092711", // 你的手机号 from: "+233542150637", // 有效的Twilio号码 statusCallback: "https://0sbfdfxbb0.execute-api.eu-central-1.amazonaws.com/record", statusCallbackEvent: [ "initiated", "ringing", "answered", "completed", ], }); // 成功,打印呼叫SID console.log(`呼叫SID:`, call.sid); return call; } catch (error) { // 错误处理 console.error(`发起呼叫失败: ${error}`); } break; case "media": if (call) { console.log("收到呼叫媒体流:", msg.payload.call_sid); // 处理媒体流逻辑 // ... } break; case "stop": // 处理"stop"事件 console.log("呼叫已停止"); break; } }); });
问题排查与修复方案
1. WebSocket连接生命周期错误
SST的ApiHandler是单次HTTP请求触发的无状态函数,请求结束后进程会被销毁,无法维持长期的WebSocket连接。Twilio Media Stream需要持续的WebSocket连接传输音频,因此必须将WebSocket逻辑迁移到长期运行的服务中(比如配置SST的Function为长期运行模式,或者使用专门的WebSocket服务端点),不能在单次API请求中初始化。
2. 呼叫变量作用域问题
原代码中call变量定义在message事件回调内部,每次收到消息都会重新初始化,导致media事件触发时call大概率为undefined。需要将call存储到外部作用域,或者用呼叫SID作为键管理活跃呼叫:
// 外部作用域存储活跃呼叫 const activeCalls = new Map(); // 在start事件中存储呼叫 activeCalls.set(call.sid, call); // 在media事件中通过SID获取对应呼叫 const currentCall = activeCalls.get(msg.payload.call_sid);
3. Twilio呼叫的url参数未返回正确TwiML
url参数指向的API端点必须返回Twilio可识别的TwiML指令,告知Twilio启动媒体流。如果返回空或无效内容,Twilio会触发错误挂断。示例TwiML响应代码:
// 对应url参数的API Handler export const twimlHandler = ApiHandler(async () => { const twiml = new Twilio.twiml.VoiceResponse(); const start = twiml.start(); start.stream({ url: "wss://你的WebSocket服务地址" }); return { statusCode: 200, headers: { "Content-Type": "application/xml" }, body: twiml.toString() }; });
4. API Handler缺失返回值
原代码的handler没有返回任何响应,SST会默认返回错误响应,影响Twilio的回调逻辑。需要添加明确的返回内容:
export const handler = ApiHandler(async (event) => { // ... 原有代码 ... // 必须返回合法响应 return { statusCode: 200, body: JSON.stringify({ message: "请求已接收" }) }; });
5. JSON解析未做错误捕获
如果Twilio发送的消息格式异常,JSON.parse会直接崩溃,导致后续事件无法处理。需添加错误捕获逻辑:
ws.on("message", async function incoming(rawMessage) { const message = rawMessage.toString(); console.log("收到消息:", message); let msg; try { msg = JSON.parse(message); } catch (err) { console.error("解析消息失败:", err); return; } // ... 后续事件处理 ... });
内容的提问来源于stack exchange,提问作者Joseph Adeabah

