AI 智能助手项目面试技术要点总结(前端部分)

1. 流式音频播放的实现

**问题:**如何实现TTS音频的流式播放,而不是等待所有音频数据接收完才播放?

解决方案:

  • 使用 MediaSource API实现边接收边播放

  • 创建 MediaSource 对象并生成 Blob URL 绑定到 audio 元素

  • 复制代码
    sourceopen

    事件中创建 SourceBuffer,指定音频格式(audio/mpeg)

  • 通过 ReadableStream 逐块读取后端流式返回的音频数据

  • 使用

    复制代码
    sourceBuffer.appendBuffer()

    追加数据,监听

    复制代码
    updateend

    事件确保顺序追加

  • 数据接收完成后调用

    复制代码
    mediaSource.endOfStream()

    结束流

关键代码:

复制代码
const mediaSource = new MediaSource();
avatarAudio.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', async () => {
  const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
  const reader = stream.getReader();
  while (true) {
    const {value, done} = await reader.read();
    if (done) break;
    await new Promise(resolve => {
      sourceBuffer.appendBuffer(value);
      sourceBuffer.addEventListener('updateend', resolve, {once: true});
    });
  }
});

Copyjavascript


2. 音频播放时跳过开头的问题

**问题:**流式音频播放时跳过了开头部分,从中间某句话才开始播放。

解决方案:

  • **根本原因:**在音频数据还未解码完成时就调用了

    复制代码
    play()

    ,导致跳过未准备好的部分

  • **解决思路:**先追加第一个数据块,等待

    复制代码
    canplay

    事件触发后再播放

  • 检查

    复制代码
    audio.readyState >= 3

    判断是否已经可以播放,避免事件监听器未触发

  • 避免在

    复制代码
    sourceopen

    回调开始就等待

    复制代码
    canplay

    ,会造成死锁(需要数据才能触发 canplay,但数据追加在回调里)

关键代码:

复制代码
// 先追加第一个数据块
const {value: firstChunk} = await reader.read();
await new Promise(resolve => {
  sourceBuffer.appendBuffer(firstChunk);
  sourceBuffer.addEventListener('updateend', resolve, {once: true});
});

// 等待音频准备好
await new Promise(resolve => {
  if (audio.readyState >= 3) resolve();
  else audio.addEventListener('canplay', resolve, {once: true});
});

audio.play();

Copyjavascript


3. 请求取消与资源管理

**问题:**AI流式输出时频繁触发TTS请求,导致多个音频重叠播放或资源泄漏。

解决方案:

  • 使用 AbortController管理 fetch 请求,新请求到来时取消上一个未完成的请求

  • 使用全局变量保存当前的 MediaSource 和 AbortController 引用

  • 在新请求开始前:

    • 调用

      复制代码
      abortController.abort()

      取消网络请求

    • 调用

      复制代码
      mediaSource.endOfStream()

      结束流

    • 清空 audio 元素的 src 并暂停播放

    • 停止视频动画

  • 使用

    复制代码
    URL.revokeObjectURL()

    释放 Blob URL,避免内存泄漏

关键代码:

复制代码
let currentAbortController = null;
let currentMediaSource = null;

// 取消上一个请求
if (currentAbortController) {
  currentAbortController.abort();
}
if (currentMediaSource?.readyState === 'open') {
  currentMediaSource.endOfStream();
}
audio.pause();
audio.src = '';

// 创建新请求
currentAbortController = new AbortController();
fetch(url, { signal: currentAbortController.signal });

Copyjavascript


4. 音频与视频动画的同步控制

**问题:**如何确保视频动画与音频完全同步(同时开始、同时停止)?

解决方案:

  • **开始同步:**在音频开始播放时启动视频循环播放

  • **结束同步:**统一封装视频停止逻辑

    复制代码
    stopVideoAnimation()

    ,在所有音频结束场景调用:

    • 音频正常播放结束(

      复制代码
      onended

    • 音频播放错误(

      复制代码
      onerror

    • 请求被取消(新请求到来时)

  • 避免代码重复,确保所有分支都能正确停止视频

关键代码:

复制代码
const stopVideoAnimation = () => {
  if (video) {
    video.pause();
    video.currentTime = 0
相关推荐
不是az2 分钟前
CSS知识点记录
前端·javascript·css
guslegend23 分钟前
第5节:动态切片策略与重叠机制提升RAG召回率
人工智能·大模型·rag
昵称暂无139 分钟前
.NET 高级开发 | i18n 原理、实现一个 i18n 框架
javascript·c#·.net
熊猫钓鱼>_>1 小时前
从“流程固化“到“意图驱动“:大模型调智能体调Skill架构深度解析
ai·架构·大模型·llm·agent·skill·openclaw
h_jQuery1 小时前
vue使用gm-crypto对数据进行sm4加密处理
前端·javascript·vue.js
阿赛工作室2 小时前
Vue中onBeforeUnmount不触发的解决方案
前端·javascript·vue.js
前进的李工2 小时前
LangChain使用AI工具赋能:解锁大语言模型无限潜力
开发语言·人工智能·语言模型·langchain·大模型
浩星2 小时前
electron系列1:Electron不是玩具,为什么桌面应用需要它?
前端·javascript·electron
疯笔码良3 小时前
【Vue】自适应布局
javascript·vue.js·css3
浩星3 小时前
electron系列2:搭建专业Electron开发环境
javascript·typescript·electron