超视距遥操(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 秒一次)。

text
              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

ts
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 事件)

ts
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. 进入遥操

ts
const session = sdk.createTeleopSession({ agentId });
const enterEventId = session.enter(); // 返回 eventId,ACK 在 message 事件里

4. 推麦克风音频

浏览器侧建议 24kHz / 单声道 / 16-bit PCM,每 20~40ms 一片,base64 后 sendAudio:

ts
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. 结束一段 / 退出

ts
session.finishAudio();  // 结束当前音频段(不退出遥操)
session.exit();         // 退出遥操,exit_ack 在 message 事件里

事件族与协议

AgentEventType(常量对象)里的遥操事件:

常量wire 值说明
AgentEventType.TeleopEnteragentsdk.teleop.enter进入遥操(SDK → 网关)
AgentEventType.TeleopEnterAckagentsdk.teleop.enter_ack进入回执
AgentEventType.TeleopAudioStartagentsdk.teleop_audio.start音频段开始(可携带 param)
AgentEventType.TeleopAudioDeltaagentsdk.teleop_audio.delta音频增量,audio 为 base64,audioLen 为解码字节数,可选 gain 为音频增益倍数(默认 1.0)
AgentEventType.TeleopAudioDoneagentsdk.teleop_audio.done音频段结束
AgentEventType.TeleopKeepaliveagentsdk.teleop.keepalive机器人在线探活(SDK → 网关)
AgentEventType.TeleopKeepaliveAckagentsdk.teleop.keepalive_ack探活回执(网关 → SDK)
AgentEventType.TeleopExitagentsdk.teleop.exit退出遥操
AgentEventType.TeleopExitAckagentsdk.teleop.exit_ack退出回执

上行 enter / keepalive / exit 消息 wire 结构:

json
{
  "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 字段:

键类型取值示例说明
rolestring"male" / "female"遥操音色选择
thresholdnumber-14 ~ 14VAD 灵敏度阈值
ts
const eventId = session.startAudio({ role: 'male', threshold: 0 });

机器人在线检测(keepAlive)

遥操会话通常较长,机器人可能因网络中断 / 断电等掉线。业务侧应定时(建议每 10 秒)调用 session.keepAlive(),并在 message 事件里判 TeleopKeepaliveAck:

ts
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: ""。上报失败不影响主流程。

常见问题

  1. 进入没反应:确认已在 sdk.on('message') 里解析 TeleopEnterAck;JS SDK 没有 enter 回调,ACK 只从 message 事件来。
  2. sendAudio 抛错:必须先 startAudio 再 sendAudio / finishAudio,否则内部无 audioEventId 会抛异常。
  3. 每片 audio delta 多长:20~40ms 的 s16le PCM base64 后发送;示例用 960 样本 / 帧(40ms @ 24kHz)。
  4. 一次遥操能几段讲话:任意多次。每段都是 startAudio → sendAudio × N → finishAudio,无需 exit 再 enter;每段用各自的 eventId(startAudio 返回值)。
  5. 多机器人:每个 agentId 建一个 TeleopSession;SDK 按 teleopId 区分,互不冲突(可复用同一个 AgentSdk 连接)。