You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用Twilio呼出后报错,如何实现音频流接入与事件处理?

排查SST环境中Twilio WebSocket呼叫错误问题

我在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 12:44:55