实时语音与视觉输入(前端视角)
目录
核心场景
- 语音助手:边说边转写、边生成
- 截图问答:上传图像后定位区域并解释
- 视频片段分析:按时间段提问并引用帧信息
实时链路设计
这张链路图想说明三件事:
- 前端不仅是“展示层”,还承担采集、编码和状态编排
- 实时服务的核心目标是低延迟回传,而不是一次性大结果
- 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 秒内看到反馈
- 用户可随时中断,不残留后台任务
- 错误时可重试并保留已生成内容