超视距遥操(Teleop)指南 · JS / 浏览器
超视距遥操(Teleop)指南 · JS / 浏览器
⚠️ 尚未上线:本能力为 v1.5.0-SNAPSHOT 开发中特性,接口与协议可能调整,正式发布前请勿用于生产环境。
v1.5.0。Teleop = 超视距遥操:Web 端操作员通过 SDK 在线接管机器人的语音交互。JS SDK 复用与 Java 相同的 wire 协议,但运行在浏览器,鉴权 / 心跳经服务端代理,ACK 经
sdk.on('message')统一分发。示例源码在agentsdk_for_js/example/teleop/。
使用场景
- 机器人在现场无人守卫或网络 / 光线不佳处,需要远端操作员对特定请求接管应答;
- 二开 Web 控制台需要旁路 LLM,直接由人工语音接管一段窗口;
- 操作员浏览器的音频经服务端代理上行到网关,网关按
agentId下发到目标机器人执行。
JS SDK 与其他语言 SDK 一致,不承担视频拉流;若要在 Web 端同时看到机器人现场画面,请自行集成 WebRTC 拉流方案(示例用了腾讯 TRRO 的 Web SDK,见 example/teleop/trro-client.js)。
调用流程
严格顺序:enter → 任意次 startAudio / sendAudio / finishAudio → exit。
遥操存续期间可任意频次调用 keepAlive 探活(推荐每 10 秒一次)。
enter exit
┌────────────────────────┐ ┌────────────────────────┐
│ session.enter() │ │ session.exit() │
│ → enter_ack (message) │ │ → exit_ack (message) │
└───────────┬────────────┘ └───────────┬────────────┘
│ 任意次 (fire-and-forget) │
▼ │
┌────────────────────────┐ │
│ startAudio(param?) │ │
│ sendAudio(base64) │──────────────────┘
│ finishAudio() │
└────────────────────────┘
┌────────────────────────┐
│ session.keepAlive() │ ← 建议每 10 秒一次;keepalive_ack.code != 0 视为离线
│ → keepalive_ack (message)
└────────────────────────┘
enter/keepAlive/exit返回本次消息的eventId(string),其 ACK 通过sdk.on('message')收到type === "*_ack"的消息判读,code === 0为成功;startAudio/sendAudio/finishAudio是 fire-and-forget,本地不排队等 ACK;- 与原生 SDK 不同,JS SDK 没有
TeleopEnterCallback之类的回调类:一切下行都从message事件走。
浏览器最小集成
1. 创建并连接 SDK
import { createAgentSdk, AgentEventType } from 'linksoul-agentsdk';
const sdk = createAgentSdk(appId, {
authProvider: async () => {
const res = await fetch('/api/ws-auth', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ url: upstreamUrl, appId }),
});
return { url: (await res.json()).url };
},
reconnect: true,
});
await sdk.connect();
authProvider / 服务端签名代理见 快速开始。
2. 监听 ACK(统一在 message 事件)
sdk.on('message', ({ data }) => {
let msg;
try { msg = JSON.parse(typeof data === 'string' ? data : ''); } catch { return; }
switch (msg.type) {
case AgentEventType.TeleopEnterAck:
if (msg.code === 0) startTalking(); // 进入成功
break;
case AgentEventType.TeleopKeepaliveAck:
if (msg.code !== 0) handleRobotOffline(); // 机器人离线
break;
case AgentEventType.TeleopExitAck:
cleanup();
break;
}
});
3. 进入遥操
const session = sdk.createTeleopSession({ agentId });
const enterEventId = session.enter(); // 返回 eventId,ACK 在 message 事件里
4. 推麦克风音频
浏览器侧建议 24kHz / 单声道 / 16-bit PCM,每 20~40ms 一片,base64 后 sendAudio:
const startEventId = session.startAudio({ role: 'male', threshold: 0 });
// AudioWorklet 每帧回调(960 样本 = 40ms @ 24kHz):
function onPcmFrame(int16Buffer: ArrayBuffer) {
session.sendAudio(toBase64(int16Buffer)); // eventId 缺省用 startAudio 的返回值
}
function toBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = '';
for (const b of bytes) binary += String.fromCharCode(b);
return btoa(binary);
}
采集与重采样(麦克风原生采样率 → 24kHz)见示例 example/teleop/pcm-processor.js(AudioWorklet)。
5. 结束一段 / 退出
session.finishAudio(); // 结束当前音频段(不退出遥操)
session.exit(); // 退出遥操,exit_ack 在 message 事件里
事件族与协议
AgentEventType(常量对象)里的遥操事件:
| 常量 | wire 值 | 说明 |
|---|---|---|
AgentEventType.TeleopEnter | agentsdk.teleop.enter | 进入遥操(SDK → 网关) |
AgentEventType.TeleopEnterAck | agentsdk.teleop.enter_ack | 进入回执 |
AgentEventType.TeleopAudioStart | agentsdk.teleop_audio.start | 音频段开始(可携带 param) |
AgentEventType.TeleopAudioDelta | agentsdk.teleop_audio.delta | 音频增量,audio 为 base64,audioLen 为解码字节数,可选 gain 为音频增益倍数(默认 1.0) |
AgentEventType.TeleopAudioDone | agentsdk.teleop_audio.done | 音频段结束 |
AgentEventType.TeleopKeepalive | agentsdk.teleop.keepalive | 机器人在线探活(SDK → 网关) |
AgentEventType.TeleopKeepaliveAck | agentsdk.teleop.keepalive_ack | 探活回执(网关 → SDK) |
AgentEventType.TeleopExit | agentsdk.teleop.exit | 退出遥操 |
AgentEventType.TeleopExitAck | agentsdk.teleop.exit_ack | 退出回执 |
上行 enter / keepalive / exit 消息 wire 结构:
{
"type": "<event type>",
"agentId": "<目标机器人 agentId>",
"teleopId": "teleop_xxxxxxxxxxxxxxxxxxxxx",
"eventId": "event_xxxxxxxxxxxxxxxxxxxxx",
"agentMode": "teleop"
}
sendAudio 的 delta 额外携带 audio(base64)与 audioLen(解码后字节数);startAudio 额外携带扁平的 param。agentMode 恒为 "teleop"(AgentPolicy.Teleop)。
startAudio 参数
startAudio(param?) 支持在每段音频开始时携带一份普通对象,会被扁平地放到消息的 param 字段:
| 键 | 类型 | 取值示例 | 说明 |
|---|---|---|---|
role | string | "male" / "female" | 遥操音色选择 |
threshold | number | -14 ~ 14 | VAD 灵敏度阈值 |
const eventId = session.startAudio({ role: 'male', threshold: 0 });
机器人在线检测(keepAlive)
遥操会话通常较长,机器人可能因网络中断 / 断电等掉线。业务侧应定时(建议每 10 秒)调用 session.keepAlive(),并在 message 事件里判 TeleopKeepaliveAck:
let online = true;
let timer: number | undefined;
function startKeepAlive() {
timer = window.setInterval(() => {
if (online) session.keepAlive();
}, 10_000);
}
sdk.on('message', ({ data }) => {
let msg; try { msg = JSON.parse(String(data)); } catch { return; }
if (msg.type === AgentEventType.TeleopKeepaliveAck && msg.code !== 0) {
online = false;
clearInterval(timer);
session.exit(); // 掉线自动退出
}
});
code | 含义 |
|---|---|
0 | 机器人在线且响应正常 |
-1 | 服务端返回失败——机器人已离线,应停止定时、exit 退出 |
主动 exit 后也要 clearInterval(timer) 停止探活。
连接生命周期与重连
sdk.connect()打开 WebSocket(内部调用authProvider);sdk.close()主动关闭;sdk.release()关闭并从单例表移除;reconnect: true(默认)在非主动关闭时按固定 3s 间隔重连(无指数退避),每次重连重新调用authProvider换取新签名 URL;- keep-alive:连接空闲 20s 后进入探测态,发最多 3 次 JSON ping(间隔 1s),仍无响应则以 code 4000 关闭连接触发重连——浏览器下由服务端代理把 JSON ping 转成协议级 Ping;
- 连接层事件:
connected/disconnected({willReconnect})/reconnecting({attempt})/keepalive({state,probes})/error。
重连后遥操不自动恢复:连接恢复只保证 WebSocket 复通,遥操会话状态需业务侧在
connected事件里重新createTeleopSession → enter。
SDK meta 上报
每次 connected(含重连)SDK 会自动发送一条 agentsdk.sdk_meta.report:sdkLanguage: "js"、sdkVersion、os / osVersion(从 userAgent 解析)、arch: "browser"、macAddress(浏览器无真实 MAC,用 localStorage 持久化的 client_<hex> 代替)、username: ""。上报失败不影响主流程。
常见问题
- 进入没反应:确认已在
sdk.on('message')里解析TeleopEnterAck;JS SDK 没有 enter 回调,ACK 只从 message 事件来。 sendAudio抛错:必须先startAudio再sendAudio/finishAudio,否则内部无audioEventId会抛异常。- 每片 audio delta 多长:20~40ms 的 s16le PCM base64 后发送;示例用 960 样本 / 帧(40ms @ 24kHz)。
- 一次遥操能几段讲话:任意多次。每段都是
startAudio → sendAudio × N → finishAudio,无需exit再enter;每段用各自的eventId(startAudio返回值)。 - 多机器人:每个
agentId建一个TeleopSession;SDK 按teleopId区分,互不冲突(可复用同一个AgentSdk连接)。