WebSocket 接入
最通用的实时音频通道:连接、控制消息、事件、音频格式
WebSocket 是最简单通用的接入方式:建立一条全双工连接,二进制帧发送麦克风音频,**文本帧(JSON)**收发控制消息与事件。适合自定义客户端、IoT、服务器中转。
1. 建立连接
WS wss://hearo-gw.apps.aisp24.com/v1/connect?token=<客户端 Token>&...
ℹ️ 本部署的网关地址:wss://hearo-gw.apps.aisp24.com/v1/connect(下方示例已代入,可直接复制使用;地址由平台管理员在 运营中心 → 平台设置 → 接入地址 配置)。
查询参数(除 token 外均可选):
| 参数 | 默认 | 说明 |
|---|---|---|
| token | 必填 | 客户端 Token(见 鉴权与 Token) |
| encoding | pcm_s16le | 上行编码:pcm_s16le | opus(别名 pcm/pcm16/l16 同 pcm_s16le) |
| sample_rate | 16000 | 上行采样率。pcm_s16le:16000 / 24000 / 48000;opus:必须 48000 |
| channels | 1 | 声道数:1 或 2 |
| frame_duration_ms | 20 | 帧时长:10 / 20 / 40 |
| downlink_codec | pcm_s16le | 下行编码:pcm_s16le | opus |
| name | — | 在房间花名册(roster)中显示的昵称 |
| lang | — | 你说的语言(如 zh-CN / en),供字幕/翻译使用(见 实时字幕) |
| caption_targets | — | 连接即开启本路字幕,翻译为这些语言(逗号分隔);也可连接后用 caption_start 开启 |
| session_id | 自动生成 | 会话标识,断线重连时带上同一个值 |
| resume_from_seq | 0 | 重连续传的起始帧序号(配合 session_id) |
const url =
"wss://hearo-gw.apps.aisp24.com/v1/connect" +
"?token=" + encodeURIComponent(token) +
"&encoding=pcm_s16le&sample_rate=16000&channels=1" +
"&frame_duration_ms=20&downlink_codec=pcm_s16le";
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";连接成功后,服务端先发一条 ready 文本帧(回显协商结果):
{ "type": "ready", "session_id": "...", "resume_from_seq": 0,
"encoding": "pcm_s16le", "sample_rate": 16000, "channels": 1,
"frame_duration_ms": 20 }进入哪个房间由平台的 client→room 映射决定(Token 里不含房间),见 客户端与接入模型。
2. 发送音频(二进制帧)
按协商好的格式分帧、作为二进制 WebSocket 帧发送即可。默认格式(16kHz / 单声道 / s16le)下 20ms 一帧 = 320 采样 = 640 字节。选 encoding=opus 时发送 48kHz 单声道 Opus 包。无论上行是什么格式,gateway 都会在边缘解码/重采样成统一的 16k 单声道 PCM 再进房间。
💡 按 ~1x 实时节奏发送:不要把整段音频一次性灌入;按帧、按实时节奏发送,打断(barge-in)和延迟才正常。
3. 控制消息(客户端 → 服务端,文本帧)
gateway 只在本地处理少数几种控制消息,其余原样透传给房间和房间里的 AI 智能体:
| type | 处理方 | 说明 |
|---|---|---|
| ping | gateway | 心跳,服务端回 {"type":"pong"} |
| caption_start / caption_stop | gateway | 开/关本路实时字幕(见 实时字幕) |
| audio | gateway | 可选的二进制帧头 {"type":"audio","seq":...}:为紧随其后的二进制帧标注序号 |
| config | gateway + 智能体 | 更新音频格式 / 实时调智能体模式与 VAD(见 交互模式与 VAD) |
| speech_start / turn_start | 智能体 | 开始说话/新轮次:打断当前回答并开始新一轮录音 |
| speech_end | 智能体 | 结束说话(PTT 松手):停止送入 ASR 并收尾本句 |
| commit | 智能体 | 显式收尾当前话语(PTT 释放) |
| interrupt | 智能体 | 立即取消 AI 当前回答(LLM+TTS) |
ws.send(JSON.stringify({ type: "interrupt" })); // 打断 AI
ws.send(JSON.stringify({ type: "speech_start" })); // 开始新一轮ℹ️ 交互模式决定该发哪些控制:按住说话(ptt) 用 speech_start/speech_end;自然对话(natural) 由服务端 VAD 自动判断,通常无需手动发。详见 交互模式与 VAD。
参与者发送的 kick 控制不会执行。精确断开连接属于服务端运营权限,
只能通过 Console 到 Room 的私有、鉴权操作接口发起。
4. 事件(服务端 → 客户端)
文本帧为 JSON 事件,按 type 区分:
| type | 字段 | 说明 |
|---|---|---|
| transcript | text, is_final, confidence | ASR 识别结果;is_final 前为流式部分结果 |
| caption | id, speaker, source_lang, text, translations, is_final | 实时字幕/翻译(见 实时字幕) |
| turn_started | turn_id | AI 开始回答 |
| assistant_delta | turn_id, delta | LLM 流式文本增量 |
| audio | turn_id, seq, sample_rate, channels, encoding, bytes | 紧随其后的二进制音频帧的头 |
| turn_complete | turn_id, text | AI 回答结束 |
| turn_cancelled | turn_id, reason | 被打断/中断(reason=barge_in 等) |
| session_idle | — | 多轮模式空闲超时 |
| roster | members[] | 房间成员变化({id,name,kind:"human"|"agent"}),进房即收到一份全量快照 |
| kicked | — | 你被踢出了房间 |
| pong | — | 对 ping 的应答 |
| error | message | 错误 |
下行音频:每段合成音频先来一条 {type:"audio", ...} 文本头,紧接着一个二进制帧(按 downlink_codec 编码,默认 16k PCM)。
let pendingAudio = null;
ws.onmessage = (ev) => {
if (typeof ev.data === "string") {
const msg = JSON.parse(ev.data);
if (msg.type === "audio") pendingAudio = msg; // 头
else if (msg.type === "transcript") render(msg.text);
else if (msg.type === "roster") renderMembers(msg.members);
else if (msg.type === "turn_cancelled") stopPlayback(); // 打断:停播
} else {
play(ev.data, pendingAudio); // 紧随头的二进制音频
pendingAudio = null;
}
};5. 断线重连
重连时带上同一个 session_id,并把 resume_from_seq 设为你已成功发送的最后帧序号,服务端从该序号之后续收、避免重复帧。简单客户端也可以不管这两个参数——直接重连即可(按新会话处理)。
6. 邀请 AI
连上房间后,AI 智能体由控制面调度进房间(不是通过这条 WS):调用 POST /api/v1/agents(见 REST API)、在控制台「演示」页点「邀请 AI 加入」、或给房间配默认智能体(进房自动就位,见 房间与分组)。AI 进来后会出现在 roster 里(kind=agent)。
💡 对照实时调试台:控制台的「演示」页就是一个完整的 WebSocket 客户端参考实现(含重采样、分帧、播放、打断),可边读代码边对照行为。