# wsock.tcp.httpStreamMp3：内存 MP3 URL

把陆续到达的 MP3 字节提供为普通 HTTP URL。一个对象对应一条音频和一个独立的本机 HTTP 服务。

- **不依赖 TTS 引擎或播放器**，不创建、读取或写入音频文件。
- 边 `write` 边发送，无需等到完整音频生成。
- 音频分块保留到 `close`；没有环形回收、自动过期、重试或多会话管理器。
- 直接复用 `wsock.tcp.simpleHttpServer`；成功写入后发送下一块，发送失败便退出该请求。

## 最小接口

```aardio
import wsock.tcp.httpStreamMp3;

var stream,err=wsock.tcp.httpStreamMp3();
if(!stream) return null,err;

// 创建后即可把 stream.url 交给播放器，不必等待首块音频。
// 收到编码音频时多次调用：
// stream.write(mp3Bytes);

// 输入成功结束。HTTP 发完剩余数据才结束，URL 与数据继续保留。
// stream.finish();

// 若来源失败，用 stream.finish(err)，不要伪装成成功 EOF。
// 不再使用时调用 stream.close()；一个对象只用于一次输入过程。
```

可直接运行的窗体示例：`~/examples/Media/Audio/edgeTTS-http.aardio`。
示例使用普通 `WMPlayer.OCX` 打开 URL；替换播放器时只替换打开/停止地址的代码。

**服务端边发送不等于所有播放器都会立即起播。** 本机验证中，`web.form` 的 IE `<audio>` 可以在输入完成前开始播放；`WMPlayer.OCX` 能播放同一 URL，但会等 HTTP 响应结束后再播放。示例用于演示地址交接；若强调低延迟，应选用支持该 MP3 URL 渐进播放的客户端。

## 接入 TTS

在已有消息循环的窗体程序中，把 HTTP 流直接作为引擎的输出对象即可：合成成功调用 `stream.finish()`，失败调用 `stream.finish(错误信息)`，取消不会调用它。

```aardio
import web.edgeTextToSpeech;
import wsock.tcp.httpStreamMp3;

var stream,err=wsock.tcp.httpStreamMp3();
if(!stream) return null,err;
var engine=web.edgeTextToSpeech();
var task,err=engine.synthesize({text="你好，这是内存音频流。"},stream); // 音频直接写入 URL
if(!task){stream.close();return null,err;}

// 把 stream.url 交给现有播放器。窗体关闭时：
// 1. 停止播放器；2. task.cancel()；3. stream.close()。
// 合成结束只会 finish，不会 close：客户端可能还要读取音频。
```

这里接收的是 MP3 编码字节，不是 PCM，不要求每个字节块都是完整 MP3 文件。HTTP 服务不转码，也不保证独立音频文件拼接后的无缝播放。

## 生命周期

| 操作/属性 | 含义 |
|---|---|
| `url` | 创建成功即可使用的地址，只监听 `127.0.0.1`；路径随机，每个实例独立 |
| `write(data)` | 接受字符串或 buffer，按原始字节追加；成功返回 true，失败返回 null,err。buffer 会复制，空字节串是无操作 |
| `finish()` | 封口；已有请求发完数据后正常结束。重复正常 finish 返回 true |
| `finish(err)` | 标记来源失败；正在发送的响应中断，不混入错误文本；新请求返回 503 |
| `close()` | 释放全部保留音频、唤醒等待中的请求并停止自有服务；可重复调用 |
| `state` | `open`、`finished`、`failed`、`closed`，只读状态 |
| `size` | 保留的编码字节数；close 后为 0 |
| `error` | 来源或容量错误（如有） |

**对象方法在创建线程中调用**；TTS 回调可直接写入，HTTP 读取在独立工作线程中执行。保留对象到使用结束，不要在创建它的短线程退出后继续依赖 URL。

读到目前末尾但输入尚未完成时，HTTP 请求等待新数据。每个请求有自己的块索引，读取不会消耗共享音频。

`finish` 不是播放结束，`close` 也不能撤回播放器已经缓存的声音。停止声音仍需调用播放器自己的停止接口。

## 可选设置

```aardio
import wsock.tcp.httpStreamMp3;
var stream,err=wsock.tcp.httpStreamMp3({
    port=0;             // 系统自动分配本机端口
    threadNum=1;        // HTTP 请求工作线程数，不是语音路数
    maxBytes=33554432;  // 单次输入最多保留 32 MiB 压缩音频
});
if(stream) stream.close();
```

默认值已适合桌面单路 TTS。达到容量上限时，拒绝整个超限字节块、返回错误并封口，不覆盖历史数据、不等待已读数据腾空间，也不自动写入硬盘。明确不需要该音频时调用 `close` 即可释放全部内存。

## HTTP 范围

- 支持 GET、HEAD；HEAD 不消耗音频。
- 输入尚未完成时，HTTP/1.1 使用 chunked，HTTP/1.0 使用连接关闭定界。
- 输入成功完成后，新请求返回固定长度的完整内容。
- **不实现 Range/seek/断点续传**：忽略 Range，始终从头返回完整 200，发送 `Accept-Ranges: none`。
- 发送 `Cache-Control: no-store`；只保证此库没有音频落盘逻辑，不承诺第三方播放器或操作系统内部完全不写盘。

这是有限音频的简易本地 URL，不是长期广播、多租户或可拖动进度的媒体服务器。
