UNPKG

mstf-kit

Version:

一个现代化的 JavaScript/TypeScript 工具库,提供了丰富的常用工具函数

487 lines (385 loc) 11.9 kB
# 流式音频处理器 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` 提供了一个更强大、更灵活的流式音频处理解决方案,支持多种音频格式,集成了完整的日志系统,适用于各种实时音频应用场景。