Q1: 如何优化TTS语音播放的延迟问题?
问题背景:用户看到动画后3-5秒才听到声音,体验很差。
解决方案:
-
调整文本分片大小:将后端配置从200字改为40-50字
max: sentence: length: 50 # 单个TTS请求约4-6秒音频Copyyaml
-
实现流式音频播放:使用MediaSource API边接收边播放
const mediaSource = new MediaSource(); mediaSource.addEventListener('sourceopen', async () => { const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg'); const reader = response.body.getReader(); while (true) { const {value, done} = await reader.read(); if (done) break; sourceBuffer.appendBuffer(value); await new Promise(resolve => sourceBuffer.addEventListener('updateend', resolve, {once: true}) ); } });Copyjavascript
-
预加载下一片段:在播放当前片段时提前请求下一个
效果对比:
-
优化前:等待5秒 → 播放
-
优化后:等待0.5秒 → 开始播放 → 边播边接收
Q2: 如何判断后端是否支持流式响应?
测试方法:
fetch('/test/tts', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({text: '测试文本'})
})
.then(r => {
console.log('Transfer-Encoding:', r.headers.get('transfer-encoding'));
console.log('是否流式:', r.body instanceof ReadableStream);
})
Copyjavascript
判断标准:
-
✅
Transfer-Encoding: chunkedReadableStream: true→ 支持流式
-
❌
Content-Length: xxx→ 不支持流式(必须等完整响应)
后端实现要点(Spring Boot):
@GetMapping(value = "/tts", produces = "audio/mpeg")
public StreamingResponseBody tts(@RequestParam String text) {
return outputStream -> {
ttsService.streamAudio(text, outputStream);
// Spring Boot自动添加 Transfer-Encoding: chunked
};
}
Copyjava
Q3: 网络传输的121个片段是否意味着调用了121次TTS API?
答案:不是。
原理解释:
-
121个片段 = 1次TTS调用返回的音频通过HTTP分块传输(chunked transfer)分成的网络包
-
类似快递:1个订单(API调用)→ 分成121个小包裹(网络包)运输
日志证据:
📢 调用TTS API... ← 只出现1次
📊 接收到 121 个片段 ← 对应上面那1次调用的传输过程
Copy
如果真调用121次,日志应该是:
📢 调用TTS API... (1)
📊 接收到 X 个片段
📢 调用TTS API... (2)
📊 接收到 Y 个片段
... (重复121次)
Copy
性能优化建议:不要担心片段数量,关注单次TTS生成时间(缩短文本长度)。
Q4: 音频播放跳过第一句话,如何排查?
排查步骤:
Step 1:确认后端是否完整
fetch('/test/tts', {
method: 'POST',
body: JSON.stringify({text: '第一句。第二句。第三句。'})
}).then(r => r.blob()).then(blob => {
const audio = new Audio(URL.createObjectURL(blob));
audio.play(); // 听是否从"第一句"开始
});
Copyjavascript
Step 2:检查前端文本清洗
console.log('🧼 清洗后文本:', cleanedText);
// 确认是否过滤了开头内容
Copyjavascript
Step 3:修复音频播放时机
// ❌ 错误:音频还没准备好就播放
avatarAudio.value.src = audioUrl;
avatarAudio.value.play();
// ✅ 正确:等待音频可播放
avatarAudio.value.src = audioUrl;
await new Promise(resolve => {
avatarAudio.value.addEventListener('canplay', resolve, {once: true});
});
avatarAudio.value.play();
Copyjavascript
常见原因:
-
后端分句逻辑跳过了第0句
-
TTS服务过滤了特殊字符(冒号、分隔符)
-
前端音频元素缓存问题
Q5: 如何实现多句子的队列播放,避免语音重叠?
场景:AI流式返回多个句子,需要按顺序播放,不能同时播放。
实现方案:
class AudioQueue {
constructor() {
this.queue = [];
this.isPlaying = false;
}
async add(text) {
this.queue.push(text);
if (!this.isPlaying) {
await this.playNext();
}
}
async playNext() {
if (this.queue.length === 0) {
this.isPlaying = false;
return;
}
this.isPlaying = true;
const text = this.queue.shift();
// 调用TTS并播放
const audioUrl = await fetchTTS(text);
const audio = new Audio(audioUrl);
await new Promise(resolve => {
audio.onended = resolve;
audio.play();
});
// 递归播放下一个
await this.playNext();
}
clear() {
this.queue = [];
this.isPlaying = false;
}
}
// 使用
const audioQueue = new AudioQueue();
audioQueue.add('第一句话');
audioQueue.add('第二句话'); // 自动等待第一句播放完
Copyjavascript
关键点:
-
使用队列管理多个句子
-
通过
audio.onended事件串联播放
-
提供
clear()方法处理用户打断
优化:预加载下一句的TTS音频,减少等待时间。