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