mstf-kit
Version:
一个现代化的 JavaScript/TypeScript 工具库,提供了丰富的常用工具函数
487 lines (385 loc) • 11.9 kB
Markdown
# 流式音频处理器 V2
## 概述
`audioStreamV2` 是新版的流式音频处理器,参考 `audioRequest` 的处理方式,支持两种流式音频格式:
1. **有头部格式** (`with-header`): 每个数据块都包含完整的音频头部(如WAV头)
2. **无头部格式** (`without-header`): 只有第一个数据块包含头部,后续只有音频数据
3. **自动检测** (`auto`): 自动检测音频格式(默认)
## 主要特性
- ✅ 支持有头部和无头部两种流式格式
- ✅ 自动格式检测
- ✅ 基于 Web Audio API 的高质量播放
- ✅ 支持播放控制(暂停、恢复、中止)
- ✅ 完整的回调系统
- ✅ 支持返回完整音频 Blob
- ✅ 集成 Logger 日志系统
- ✅ 支持 Axios 集成
## 基本使用
### 使用 Fetch API
```typescript
import { processStreamAudioV2 } from 'mstf-kit';
// 发起请求
const response = await fetch('/api/audio/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: '要合成的文本' })
});
// 获取 Reader
const reader = response.body.getReader();
// 处理流式音频
const { pause, resume, abort, isPlaying, finalBlob } = await processStreamAudioV2(reader, {
autoPlay: true, // 自动播放
returnBlob: true, // 返回完整Blob
dataFormat: 'auto', // 自动检测格式
audioField: 'data.audio', // 音频数据字段路径
mimeType: 'audio/wav', // MIME类型
debug: true, // 启用调试日志
onAudioData: (blob) => {
console.log('收到音频块:', blob.size);
},
onStart: () => {
console.log('开始播放');
},
onEnded: () => {
console.log('播放结束');
},
onComplete: (blob) => {
if (blob) {
console.log('完整音频大小:', blob.size);
}
}
});
// 控制播放
pauseButton.onclick = () => pause();
playButton.onclick = () => resume();
stopButton.onclick = () => abort();
// 获取完整音频
if (finalBlob) {
const blob = await finalBlob;
const url = URL.createObjectURL(blob);
audioElement.src = url;
}
```
### 使用 Axios
```typescript
import axios from 'axios';
import { createAxiosAudioOptionsV2 } from 'mstf-kit';
// 创建配置
const { axiosConfig, pause, resume, abort, isPlaying, finalBlob } = createAxiosAudioOptionsV2({
autoPlay: true,
returnBlob: true,
dataFormat: 'with-header', // 指定格式
audioField: 'data.audio',
debug: true,
onStart: () => {
console.log('音频开始播放');
playButton.disabled = false;
},
onAudioData: (blob) => {
console.log('收到音频数据块:', blob.size);
}
});
// 发送请求
const response = await axios.post(
'/api/audio/stream',
{ text: '要合成的文本' },
axiosConfig
);
// 控制播放
const togglePlay = () => {
if (isPlaying()) {
pause();
playButton.textContent = '播放';
} else {
resume();
playButton.textContent = '暂停';
}
};
```
## 数据格式说明
### 有头部格式 (with-header)
每个数据块都包含完整的音频头部:
```
数据块1: [WAV头部 + 音频数据1]
数据块2: [WAV头部 + 音频数据2]
数据块3: [WAV头部 + 音频数据3]
...
```
服务器返回示例:
```
data: {"audio":"UklGRiQAAABXQVZFZm10...","is_last":false}
data: {"audio":"UklGRiQAAABXQVZFZm10...","is_last":false}
data: {"audio":"UklGRiQAAABXQVZFZm10...","is_last":true}
```
使用方式:
```typescript
const control = await processStreamAudioV2(reader, {
dataFormat: 'with-header',
autoPlay: true
});
```
### 无头部格式 (without-header)
只有第一个数据块包含头部,后续只有音频数据:
```
数据块1: [WAV头部 + 音频数据1]
数据块2: [音频数据2]
数据块3: [音频数据3]
...
```
服务器返回示例:
```
data: {"audio":"UklGRiQAAABXQVZFZm10...","is_last":false} // 包含头部
data: {"audio":"AQIDBAUG...","is_last":false} // 只有数据
data: {"audio":"BwgJCgsM...","is_last":true} // 只有数据
```
使用方式:
```typescript
const control = await processStreamAudioV2(reader, {
dataFormat: 'without-header',
autoPlay: true
});
```
### 自动检测 (auto)
自动检测音频格式(默认):
```typescript
const control = await processStreamAudioV2(reader, {
dataFormat: 'auto', // 或者不指定,默认就是 auto
autoPlay: true
});
```
## API 参考
### processStreamAudioV2
处理流式音频响应的主函数。
```typescript
function processStreamAudioV2(
reader: ReadableStreamDefaultReader<Uint8Array>,
options?: StreamAudioV2Options
): Promise<StreamAudioV2Control>
```
**参数:**
- `reader`: 响应流的 Reader 对象
- `options`: 处理选项
**返回:** `Promise<StreamAudioV2Control>` - 控制对象
### StreamAudioV2Options
```typescript
interface StreamAudioV2Options {
autoPlay?: boolean; // 是否自动播放,默认 false
returnBlob?: boolean; // 是否返回完整Blob,默认 false
mimeType?: string; // MIME类型,默认 'audio/wav'
audioField?: string; // 音频数据字段路径
dataFormat?: AudioDataFormat; // 数据格式,默认 'auto'
debug?: boolean; // 是否启用调试日志,默认 false
onAudioData?: (blob: Blob) => void; // 收到音频数据回调
onError?: (error: Error) => void; // 错误回调
onComplete?: (blob?: Blob) => void; // 完成回调
onStart?: () => void; // 开始播放回调
onEnded?: () => void; // 播放结束回调
onRawChunk?: (chunk: string) => void; // 原始数据块回调
}
```
### AudioDataFormat
```typescript
type AudioDataFormat = 'with-header' | 'without-header' | 'auto';
```
### StreamAudioV2Control
```typescript
interface StreamAudioV2Control {
pause: () => void; // 暂停播放
resume: () => void; // 恢复播放
abort: () => void; // 中止处理
isPlaying: () => boolean; // 是否正在播放
finalBlob?: Promise<Blob>; // 完整音频Blob(如果returnBlob为true)
}
```
### createAxiosAudioOptionsV2
创建 Axios 配置用于流式音频请求。
```typescript
function createAxiosAudioOptionsV2(
options?: StreamAudioV2Options
): {
axiosConfig: {
responseType: string;
onDownloadProgress: (progressEvent: any) => void;
};
pause: () => void;
resume: () => void;
abort: () => void;
isPlaying: () => boolean;
finalBlob?: Promise<Blob>;
}
```
## 实际应用场景
### 场景1:TTS 流式语音合成
```typescript
import { processStreamAudioV2 } from 'mstf-kit';
async function streamTTS(text: string) {
const response = await fetch('/api/tts/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const reader = response.body.getReader();
const { pause, resume, abort, finalBlob } = await processStreamAudioV2(reader, {
autoPlay: true,
returnBlob: true,
dataFormat: 'auto',
debug: true,
onStart: () => {
statusElement.textContent = '正在播放...';
pauseButton.disabled = false;
},
onEnded: () => {
statusElement.textContent = '播放完成';
pauseButton.disabled = true;
},
onAudioData: (blob) => {
// 更新进度
progressBar.value += blob.size;
}
});
// 绑定控制按钮
pauseButton.onclick = () => {
if (isPlaying()) {
pause();
pauseButton.textContent = '继续';
} else {
resume();
pauseButton.textContent = '暂停';
}
};
stopButton.onclick = () => {
abort();
statusElement.textContent = '已停止';
};
// 保存完整音频
if (finalBlob) {
const blob = await finalBlob;
downloadButton.onclick = () => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'tts-audio.wav';
a.click();
};
}
}
```
### 场景2:实时语音对话
```typescript
import { createAxiosAudioOptionsV2 } from 'mstf-kit';
import axios from 'axios';
class VoiceChat {
private currentControl: any = null;
async sendMessage(message: string) {
// 停止当前播放
if (this.currentControl) {
this.currentControl.abort();
}
// 创建新的音频流
const { axiosConfig, pause, resume, abort, isPlaying } = createAxiosAudioOptionsV2({
autoPlay: true,
dataFormat: 'without-header',
audioField: 'response.audio',
onStart: () => {
this.showStatus('AI正在回复...');
},
onEnded: () => {
this.showStatus('回复完成');
}
});
this.currentControl = { pause, resume, abort, isPlaying };
// 发送消息
await axios.post('/api/chat/voice', {
message,
voice: 'zh-CN-XiaoxiaoNeural'
}, axiosConfig);
}
pauseResponse() {
if (this.currentControl) {
this.currentControl.pause();
}
}
resumeResponse() {
if (this.currentControl) {
this.currentControl.resume();
}
}
stopResponse() {
if (this.currentControl) {
this.currentControl.abort();
this.currentControl = null;
}
}
private showStatus(message: string) {
console.log(message);
}
}
```
### 场景3:音频流录制
```typescript
import { processStreamAudioV2 } from 'mstf-kit';
async function recordStreamAudio(streamUrl: string) {
const response = await fetch(streamUrl);
const reader = response.body.getReader();
const chunks: Blob[] = [];
const { abort, finalBlob } = await processStreamAudioV2(reader, {
autoPlay: false, // 不自动播放,只录制
returnBlob: true,
dataFormat: 'auto',
onAudioData: (blob) => {
chunks.push(blob);
console.log(`已录制 ${chunks.length} 个音频块`);
},
onComplete: async (blob) => {
if (blob) {
console.log('录制完成,总大小:', blob.size);
// 保存到本地
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `recording-${Date.now()}.wav`;
a.click();
}
}
});
// 提供停止录制的方法
return {
stop: abort,
getChunks: () => chunks,
getFinalBlob: () => finalBlob
};
}
```
## 与旧版本的区别
### audioRequest vs audioStreamV2
| 特性 | audioRequest | audioStreamV2 |
|------|-------------|---------------|
| 格式支持 | 只支持无头部格式 | 支持有头部、无头部和自动检测 |
| 日志系统 | 使用 console | 集成 Logger 工具 |
| 类型定义 | 基础类型 | 完整的 TypeScript 类型 |
| 调试功能 | 有限 | 完整的调试日志 |
| API 设计 | 简单 | 更灵活和强大 |
### 迁移指南
从 `audioRequest` 迁移到 `audioStreamV2`:
```typescript
// 旧版本
import { processStreamResponse } from 'mstf-kit';
const control = await processStreamResponse(reader, {
autoPlay: true,
audioField: 'data.audio'
});
// 新版本
import { processStreamAudioV2 } from 'mstf-kit';
const control = await processStreamAudioV2(reader, {
autoPlay: true,
audioField: 'data.audio',
dataFormat: 'without-header' // 明确指定格式
});
```
## 注意事项
1. **浏览器兼容性**:需要支持 Web Audio API 的浏览器
2. **自动播放限制**:某些浏览器需要用户交互才能自动播放
3. **内存管理**:及时释放不再使用的 Blob URL
4. **格式检测**:自动检测可能需要等待第二个数据块才能确定格式
5. **调试模式**:生产环境建议关闭 debug 以提高性能
## 总结
`audioStreamV2` 提供了一个更强大、更灵活的流式音频处理解决方案,支持多种音频格式,集成了完整的日志系统,适用于各种实时音频应用场景。