跳到主要内容

实时语音与视觉输入(前端视角)

目录

核心场景

  • 语音助手:边说边转写、边生成
  • 截图问答:上传图像后定位区域并解释
  • 视频片段分析:按时间段提问并引用帧信息

实时链路设计

这张链路图想说明三件事:

  • 前端不仅是“展示层”,还承担采集、编码和状态编排
  • 实时服务的核心目标是低延迟回传,而不是一次性大结果
  • UI 要把各阶段可视化,否则用户会误以为卡死

当你按这个链路实现后,用户感知会明显改善:从“等结果”变成“看到过程”,这对实时交互非常关键。

关键点:

  • 采集、编码、上传必须可中断
  • 前端显示实时阶段(录音中/识别中/生成中)
  • 所有阶段统一 traceId 方便排障

前端状态管理建议

  • idle:未开始
  • capturing:采集中
  • streaming:实时返回中
  • failed:失败
  • done:完成

同时保存:

  • 最近 10 秒缓存片段(用于断线重试)
  • 当前输入模态(text/audio/image)
  • 延迟指标(首包时间、完整耗时)

代码示例

浏览器录音并切片上传

export async function startAudioStream(onChunk: (blob: Blob) => void) {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });

recorder.ondataavailable = (e) => {
if (e.data.size > 0) onChunk(e.data);
};

// 每 500ms 产生一片,适合实时转写
recorder.start(500);

return () => {
recorder.stop();
stream.getTracks().forEach((t) => t.stop());
};
}

视觉输入预处理

export async function compressImage(file: File, maxSize = 1280) {
const img = await createImageBitmap(file);
const scale = Math.min(1, maxSize / Math.max(img.width, img.height));
const w = Math.round(img.width * scale);
const h = Math.round(img.height * scale);

const canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d")!;
ctx.drawImage(img, 0, 0, w, h);

return await new Promise<Blob>((resolve) =>
canvas.toBlob((blob) => resolve(blob as Blob), "image/jpeg", 0.85)
);
}

状态图

最小验收标准

  • 实时输入可在 1 秒内看到反馈
  • 用户可随时中断,不残留后台任务
  • 错误时可重试并保留已生成内容