@mingxy/ocosay 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +556 -0
- package/TECH_PLAN.md +352 -0
- package/__mocks__/@opencode-ai/plugin.ts +32 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/core/backends/afplay-backend.d.ts +33 -0
- package/dist/core/backends/afplay-backend.d.ts.map +1 -0
- package/dist/core/backends/afplay-backend.js +144 -0
- package/dist/core/backends/afplay-backend.js.map +1 -0
- package/dist/core/backends/aplay-backend.d.ts +33 -0
- package/dist/core/backends/aplay-backend.d.ts.map +1 -0
- package/dist/core/backends/aplay-backend.js +142 -0
- package/dist/core/backends/aplay-backend.js.map +1 -0
- package/dist/core/backends/base.d.ts +94 -0
- package/dist/core/backends/base.d.ts.map +1 -0
- package/dist/core/backends/base.js +6 -0
- package/dist/core/backends/base.js.map +1 -0
- package/dist/core/backends/index.d.ts +29 -0
- package/dist/core/backends/index.d.ts.map +1 -0
- package/dist/core/backends/index.js +114 -0
- package/dist/core/backends/index.js.map +1 -0
- package/dist/core/backends/naudiodon-backend.d.ts +52 -0
- package/dist/core/backends/naudiodon-backend.d.ts.map +1 -0
- package/dist/core/backends/naudiodon-backend.js +123 -0
- package/dist/core/backends/naudiodon-backend.js.map +1 -0
- package/dist/core/backends/powershell-backend.d.ts +34 -0
- package/dist/core/backends/powershell-backend.d.ts.map +1 -0
- package/dist/core/backends/powershell-backend.js +154 -0
- package/dist/core/backends/powershell-backend.js.map +1 -0
- package/dist/core/player.d.ts +97 -0
- package/dist/core/player.d.ts.map +1 -0
- package/dist/core/player.js +268 -0
- package/dist/core/player.js.map +1 -0
- package/dist/core/speaker.d.ts +97 -0
- package/dist/core/speaker.d.ts.map +1 -0
- package/dist/core/speaker.js +218 -0
- package/dist/core/speaker.js.map +1 -0
- package/dist/core/stream-player.d.ts +107 -0
- package/dist/core/stream-player.d.ts.map +1 -0
- package/dist/core/stream-player.js +272 -0
- package/dist/core/stream-player.js.map +1 -0
- package/dist/core/stream-reader.d.ts +86 -0
- package/dist/core/stream-reader.d.ts.map +1 -0
- package/dist/core/stream-reader.js +172 -0
- package/dist/core/stream-reader.js.map +1 -0
- package/dist/core/streaming-synthesizer.d.ts +51 -0
- package/dist/core/streaming-synthesizer.d.ts.map +1 -0
- package/dist/core/streaming-synthesizer.js +103 -0
- package/dist/core/streaming-synthesizer.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +37 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +151 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/base.d.ts +55 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +95 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/minimax.d.ts +84 -0
- package/dist/providers/minimax.d.ts.map +1 -0
- package/dist/providers/minimax.js +387 -0
- package/dist/providers/minimax.js.map +1 -0
- package/dist/tools/tts.d.ts +147 -0
- package/dist/tools/tts.d.ts.map +1 -0
- package/dist/tools/tts.js +232 -0
- package/dist/tools/tts.js.map +1 -0
- package/jest.config.js +15 -0
- package/package.json +49 -0
- package/src/config.ts +121 -0
- package/src/core/backends/afplay-backend.ts +162 -0
- package/src/core/backends/aplay-backend.ts +160 -0
- package/src/core/backends/base.ts +117 -0
- package/src/core/backends/index.ts +128 -0
- package/src/core/backends/naudiodon-backend.ts +164 -0
- package/src/core/backends/powershell-backend.ts +173 -0
- package/src/core/player.ts +322 -0
- package/src/core/speaker.ts +283 -0
- package/src/core/stream-player.ts +326 -0
- package/src/core/stream-reader.ts +190 -0
- package/src/core/streaming-synthesizer.ts +123 -0
- package/src/core/types.ts +185 -0
- package/src/index.ts +233 -0
- package/src/plugin.ts +166 -0
- package/src/providers/base.ts +150 -0
- package/src/providers/minimax.ts +515 -0
- package/src/tools/tts.ts +277 -0
- package/src/types/naudiodon.d.ts +19 -0
- package/tests/__mocks__/@opencode-ai/plugin.ts +32 -0
- package/tests/backends.test.ts +831 -0
- package/tests/index.test.ts +201 -0
- package/tests/integration-test.d.ts +6 -0
- package/tests/integration-test.d.ts.map +1 -0
- package/tests/integration-test.js +84 -0
- package/tests/integration-test.js.map +1 -0
- package/tests/integration-test.ts +93 -0
- package/tests/p1-fixes.test.ts +160 -0
- package/tests/plugin.test.ts +311 -0
- package/tests/provider.test.d.ts +2 -0
- package/tests/provider.test.d.ts.map +1 -0
- package/tests/provider.test.js +69 -0
- package/tests/provider.test.js.map +1 -0
- package/tests/provider.test.ts +87 -0
- package/tests/speaker.test.d.ts +2 -0
- package/tests/speaker.test.d.ts.map +1 -0
- package/tests/speaker.test.js +63 -0
- package/tests/speaker.test.js.map +1 -0
- package/tests/speaker.test.ts +232 -0
- package/tests/stream-player.test.ts +303 -0
- package/tests/stream-reader.test.ts +269 -0
- package/tests/streaming-synthesizer.test.ts +225 -0
- package/tests/tts-tools.test.ts +270 -0
- package/tests/types.test.d.ts +2 -0
- package/tests/types.test.d.ts.map +1 -0
- package/tests/types.test.js +61 -0
- package/tests/types.test.js.map +1 -0
- package/tests/types.test.ts +63 -0
- package/tsconfig.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# ocosay - OpenCode TTS 播放插件
|
|
2
|
+
|
|
3
|
+
> 🎙️ 让 AI 开口说话 — 支持豆包模式边接收边朗读的 TTS 插件
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@mengxy/ocosay)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Why This Exists
|
|
9
|
+
|
|
10
|
+
在 AI 助手对话场景中,文字回复往往显得生硬。**ocosay** 让 AI 能够"开口说话",通过流式 TTS 技术实现边接收边朗读的豆包模式,极大提升对话交互体验。
|
|
11
|
+
|
|
12
|
+
**解决的问题:**
|
|
13
|
+
- 长文本等待焦虑 — 流式播放,首包延迟低
|
|
14
|
+
- 缺乏情感反馈 — 多种音色选择,支持语速/音调调节
|
|
15
|
+
- 交互体验单一 — 从"看"升级到"听"
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- 🎙️ **多 TTS 模型支持** - MiniMax 作为第一个实现,可扩展其他提供商
|
|
20
|
+
- 🔊 **多种合成模式** - 同步、异步、流式三种模式可选
|
|
21
|
+
- 🎭 **音色克隆** - 支持音色快速复刻
|
|
22
|
+
- 🎛️ **播放控制** - 暂停、恢复、停止
|
|
23
|
+
- 📋 **音色列表** - 查询可用音色
|
|
24
|
+
- 🔌 **OpenCode Plugin** - 无缝集成 OpenCode
|
|
25
|
+
- 📡 **豆包模式** - autoRead + TuiEventBus,边接收边朗读
|
|
26
|
+
|
|
27
|
+
## Platform Support
|
|
28
|
+
|
|
29
|
+
| 平台 | 支持状态 | 说明 |
|
|
30
|
+
|------|----------|------|
|
|
31
|
+
| macOS | ✅ 完全支持 | Darwin 架构,native 播放 |
|
|
32
|
+
| Linux | ✅ 完全支持 | 需安装 mpv/libuv |
|
|
33
|
+
| Windows | ⚠️ 部分支持 | WSL 环境推荐 |
|
|
34
|
+
|
|
35
|
+
## 安装方式
|
|
36
|
+
|
|
37
|
+
### 方式一:ocx 安装(推荐)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ocx install @mengxy/ocosay
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 方式二:手动配置
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @mengxy/ocosay
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
在 OpenCode 配置文件中添加:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"plugins": [
|
|
54
|
+
"@mengxy/ocosay"
|
|
55
|
+
],
|
|
56
|
+
"minimax": {
|
|
57
|
+
"apiKey": "your-api-key",
|
|
58
|
+
"voiceId": "male-qn-qingse"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 快速开始
|
|
64
|
+
|
|
65
|
+
### 初始化
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { initialize } from 'ocosay'
|
|
69
|
+
|
|
70
|
+
await initialize({
|
|
71
|
+
providers: {
|
|
72
|
+
minimax: {
|
|
73
|
+
apiKey: 'your-api-key',
|
|
74
|
+
voiceId: 'male-qn-qingse',
|
|
75
|
+
model: 'stream'
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 基本使用
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { speak, stop, pause, resume } from 'ocosay'
|
|
85
|
+
|
|
86
|
+
// 说话
|
|
87
|
+
await speak('你好,世界!')
|
|
88
|
+
|
|
89
|
+
// 暂停
|
|
90
|
+
pause()
|
|
91
|
+
|
|
92
|
+
// 恢复
|
|
93
|
+
resume()
|
|
94
|
+
|
|
95
|
+
// 停止
|
|
96
|
+
await stop()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 指定音色和模式
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
await speak('你好,世界!', {
|
|
103
|
+
provider: 'minimax',
|
|
104
|
+
voice: 'female-shaonv',
|
|
105
|
+
model: 'sync',
|
|
106
|
+
speed: 1.0,
|
|
107
|
+
volume: 80
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 豆包模式
|
|
112
|
+
|
|
113
|
+
豆包模式是 ocosay 的核心特性 — 开启后,AI 助手的回复会**边接收边朗读**,实现类似豆包 App 的流畅播报体验。
|
|
114
|
+
|
|
115
|
+
### 核心组件
|
|
116
|
+
|
|
117
|
+
| 组件 | 说明 |
|
|
118
|
+
|------|------|
|
|
119
|
+
| `StreamReader` | 文本缓冲器,收集并缓冲流式文本 |
|
|
120
|
+
| `StreamingSynthesizer` | 流式合成器,边接收边合成音频 |
|
|
121
|
+
| `StreamPlayer` | 流式播放器,边收边播 |
|
|
122
|
+
| `TuiEventBus` | 事件总线,监听 OpenCode 事件流 |
|
|
123
|
+
|
|
124
|
+
> **注意**:`autoRead` 是 `initialize()` 的配置选项(`autoRead: true`),而非独立组件。
|
|
125
|
+
|
|
126
|
+
### 启用豆包模式
|
|
127
|
+
|
|
128
|
+
豆包模式通过 `initialize()` 的 `autoRead: true` 配置选项启用:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { initialize } from 'ocosay'
|
|
132
|
+
|
|
133
|
+
// 启用豆包模式
|
|
134
|
+
await initialize({
|
|
135
|
+
providers: {
|
|
136
|
+
minimax: {
|
|
137
|
+
apiKey: 'your-api-key',
|
|
138
|
+
voiceId: 'male-qn-qingse'
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
autoRead: true // 开启边接收边朗读
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 数据流与事件
|
|
146
|
+
|
|
147
|
+
豆包模式的数据流如下:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
TuiEventBus (message.part.delta)
|
|
151
|
+
→ StreamReader (缓冲文本)
|
|
152
|
+
→ StreamingSynthesizer (流式合成)
|
|
153
|
+
→ StreamPlayer (边收边播)
|
|
154
|
+
→ TuiEventBus (message.part.end)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
| 事件 | 说明 |
|
|
158
|
+
|------|------|
|
|
159
|
+
| `message.part.delta` | AI回复文本增量事件,携带 `delta` 字段 |
|
|
160
|
+
| `message.part.end` | AI回复片段结束事件 |
|
|
161
|
+
|
|
162
|
+
### TuiEventBus 事件监听
|
|
163
|
+
|
|
164
|
+
TuiEventBus 负责监听 OpenCode 的事件流,实现边接收边朗读:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { TuiEventBus } from 'ocosay'
|
|
168
|
+
|
|
169
|
+
const bus = new TuiEventBus()
|
|
170
|
+
|
|
171
|
+
// 监听文本增量事件
|
|
172
|
+
bus.on('message.part.delta', (event) => {
|
|
173
|
+
// event.properties.delta 包含新增的文本
|
|
174
|
+
console.log('收到文本:', event.properties.delta)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// 监听回复片段结束
|
|
178
|
+
bus.on('message.part.end', () => {
|
|
179
|
+
console.log('回复片段结束')
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 工具列表
|
|
184
|
+
|
|
185
|
+
ocosay 提供 10 个工具用于 OpenCode 集成:
|
|
186
|
+
|
|
187
|
+
| 工具名 | 描述 | 参数 |
|
|
188
|
+
|--------|------|------|
|
|
189
|
+
| `tts_speak` | 将文本转换为语音并播放 | `text` (必填), `provider`, `voice`, `model`, `speed`, `volume`, `pitch` |
|
|
190
|
+
| `tts_stop` | 停止当前 TTS 播放 | - |
|
|
191
|
+
| `tts_pause` | 暂停当前 TTS 播放 | - |
|
|
192
|
+
| `tts_resume` | 恢复暂停的 TTS 播放 | - |
|
|
193
|
+
| `tts_list_voices` | 列出可用的音色 | `provider` |
|
|
194
|
+
| `tts_list_providers` | 列出所有已注册的 TTS 提供商 | - |
|
|
195
|
+
| `tts_status` | 获取当前 TTS 播放状态 | - |
|
|
196
|
+
| `tts_stream_speak` | 启动流式朗读(豆包模式) | `text`, `voice`, `model` |
|
|
197
|
+
| `tts_stream_stop` | 停止当前流式朗读 | - |
|
|
198
|
+
| `tts_stream_status` | 获取当前流式朗读状态 | - |
|
|
199
|
+
|
|
200
|
+
### tts_speak
|
|
201
|
+
|
|
202
|
+
将文本转换为语音并播放。
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// 工具调用
|
|
206
|
+
await tts_speak({
|
|
207
|
+
text: '这是要播放的文本内容',
|
|
208
|
+
provider: 'minimax',
|
|
209
|
+
voice: 'male-qn-qingse',
|
|
210
|
+
speed: 1.0
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### tts_stop
|
|
215
|
+
|
|
216
|
+
停止当前 TTS 播放。
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// 工具调用
|
|
220
|
+
await tts_stop()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### tts_pause
|
|
224
|
+
|
|
225
|
+
暂停当前 TTS 播放。
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// 工具调用
|
|
229
|
+
await tts_pause()
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### tts_resume
|
|
233
|
+
|
|
234
|
+
恢复暂停的 TTS 播放。
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// 工具调用
|
|
238
|
+
await tts_resume()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### tts_list_voices
|
|
242
|
+
|
|
243
|
+
列出可用的音色。
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// 工具调用
|
|
247
|
+
const result = await tts_list_voices({ provider: 'minimax' })
|
|
248
|
+
// 返回: { success: true, voices: [...] }
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### tts_list_providers
|
|
252
|
+
|
|
253
|
+
列出所有已注册的 TTS 提供商。
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// 工具调用
|
|
257
|
+
const result = await tts_list_providers()
|
|
258
|
+
// 返回: { success: true, providers: ['minimax'] }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### tts_status
|
|
262
|
+
|
|
263
|
+
获取当前 TTS 播放状态。
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// 工具调用
|
|
267
|
+
const status = await tts_status()
|
|
268
|
+
// 返回: { success: true, isPlaying: boolean, isPaused: boolean }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### tts_stream_speak
|
|
272
|
+
|
|
273
|
+
启动流式朗读(豆包模式),订阅AI回复并边生成边朗读。
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// 工具调用
|
|
277
|
+
await tts_stream_speak({
|
|
278
|
+
text: '初始文本(可选)',
|
|
279
|
+
voice: 'female-shaonv',
|
|
280
|
+
model: 'stream'
|
|
281
|
+
})
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### tts_stream_stop
|
|
285
|
+
|
|
286
|
+
停止当前流式朗读。
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// 工具调用
|
|
290
|
+
await tts_stream_stop()
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### tts_stream_status
|
|
294
|
+
|
|
295
|
+
获取当前流式朗读状态。
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// 工具调用
|
|
299
|
+
const status = await tts_stream_status()
|
|
300
|
+
// 返回: { success: true, isActive: boolean, bytesWritten: number, state: string }
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## API 参考
|
|
304
|
+
|
|
305
|
+
### initialize(config?)
|
|
306
|
+
|
|
307
|
+
初始化 ocosay 插件。**必须首先调用此方法。**
|
|
308
|
+
|
|
309
|
+
| 参数 | 类型 | 说明 |
|
|
310
|
+
|------|------|------|
|
|
311
|
+
| config.defaultProvider | string | 默认 TTS 提供商,默认 minimax |
|
|
312
|
+
| config.defaultModel | 'sync' \| 'async' \| 'stream' | 默认合成模式,默认 stream |
|
|
313
|
+
| config.defaultVoice | string | 默认音色 ID |
|
|
314
|
+
| config.providers.minimax | MiniMaxConfig | MiniMax 提供商配置 |
|
|
315
|
+
| config.autoRead | boolean | 启用豆包模式(边接收边朗读) |
|
|
316
|
+
| config.streamBufferSize | number | 流式缓冲大小,默认 30 |
|
|
317
|
+
| config.streamBufferTimeout | number | 流式缓冲超时(ms),默认 2000 |
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { initialize } from 'ocosay'
|
|
321
|
+
|
|
322
|
+
await initialize({
|
|
323
|
+
providers: {
|
|
324
|
+
minimax: {
|
|
325
|
+
apiKey: 'your-api-key',
|
|
326
|
+
voiceId: 'male-qn-qingse',
|
|
327
|
+
model: 'stream'
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
autoRead: true // 启用豆包模式
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### destroy()
|
|
335
|
+
|
|
336
|
+
释放所有资源,清理插件状态。应在插件卸载或会话结束时调用。
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { destroy } from 'ocosay'
|
|
340
|
+
|
|
341
|
+
await destroy()
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### TuiEventBus
|
|
345
|
+
|
|
346
|
+
事件总线类,用于监听 OpenCode 事件流。
|
|
347
|
+
|
|
348
|
+
| 方法 | 说明 |
|
|
349
|
+
|------|------|
|
|
350
|
+
| `on(event, handler)` | 注册事件监听器 |
|
|
351
|
+
| `off(event, handler)` | 注销事件监听器 |
|
|
352
|
+
|
|
353
|
+
**可用事件:**
|
|
354
|
+
|
|
355
|
+
| 事件名 | 说明 | 事件属性 |
|
|
356
|
+
|--------|------|----------|
|
|
357
|
+
| `message.part.delta` | AI回复文本增量 | `sessionId`, `messageId`, `partId`, `properties.delta` |
|
|
358
|
+
| `message.part.end` | AI回复片段结束 | - |
|
|
359
|
+
|
|
360
|
+
### speak(text, options?)
|
|
361
|
+
|
|
362
|
+
将文本转换为语音并播放。
|
|
363
|
+
|
|
364
|
+
| 参数 | 类型 | 说明 |
|
|
365
|
+
|------|------|------|
|
|
366
|
+
| text | string | 要转换的文本 |
|
|
367
|
+
| options.provider | string | TTS 提供商,默认 minimax |
|
|
368
|
+
| options.voice | string | 音色 ID |
|
|
369
|
+
| options.model | 'sync' \| 'async' \| 'stream' | 合成模式,默认 stream |
|
|
370
|
+
| options.speed | number | 语速 0.5-2.0 |
|
|
371
|
+
| options.volume | number | 音量 0-100 |
|
|
372
|
+
| options.pitch | number | 音调 0.5-2.0 |
|
|
373
|
+
|
|
374
|
+
### stop()
|
|
375
|
+
|
|
376
|
+
停止当前播放。
|
|
377
|
+
|
|
378
|
+
### pause()
|
|
379
|
+
|
|
380
|
+
暂停当前播放。
|
|
381
|
+
|
|
382
|
+
### resume()
|
|
383
|
+
|
|
384
|
+
恢复暂停的播放。
|
|
385
|
+
|
|
386
|
+
### listVoices(provider?)
|
|
387
|
+
|
|
388
|
+
列出可用的音色。
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const voices = await listVoices('minimax')
|
|
392
|
+
console.log(voices)
|
|
393
|
+
// [{ id: 'male-qn-qingse', name: '青年清澈', ... }]
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### isAutoReadEnabled()
|
|
397
|
+
|
|
398
|
+
检查豆包模式是否已启用。
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { isAutoReadEnabled } from 'ocosay'
|
|
402
|
+
|
|
403
|
+
const enabled = isAutoReadEnabled() // 返回: boolean
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### isStreamEnabled()
|
|
407
|
+
|
|
408
|
+
检查流式组件是否已初始化。
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { isStreamEnabled } from 'ocosay'
|
|
412
|
+
|
|
413
|
+
const enabled = isStreamEnabled() // 返回: boolean
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### getStreamStatus()
|
|
417
|
+
|
|
418
|
+
获取流式播放状态。
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { getStreamStatus } from 'ocosay'
|
|
422
|
+
|
|
423
|
+
const status = getStreamStatus()
|
|
424
|
+
// 返回: { isActive: boolean, bytesWritten: number, state: string }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## MiniMax 音色列表
|
|
428
|
+
|
|
429
|
+
| ID | 名称 | 语言 | 性别 |
|
|
430
|
+
|----|------|------|------|
|
|
431
|
+
| male-qn-qingse | 青年清澈 | zh-CN | male |
|
|
432
|
+
| male-qn-qingse_2 | 青年清澈v2 | zh-CN | male |
|
|
433
|
+
| female-shaonv | 少女 | zh-CN | female |
|
|
434
|
+
| male-baiming | 成熟男声 | zh-CN | male |
|
|
435
|
+
| female-tianmei | 甜美女声 | zh-CN | female |
|
|
436
|
+
|
|
437
|
+
## 合成模式
|
|
438
|
+
|
|
439
|
+
| 模式 | 说明 | 适用场景 |
|
|
440
|
+
|------|----------|----------|
|
|
441
|
+
| stream | 流式合成,边生成边播放 | 长文本,首包延迟低 |
|
|
442
|
+
| sync | 同步合成,等待完整音频 | 短文本,一体化返回 |
|
|
443
|
+
| async | 异步合成,轮询获取结果 | 长文本,需要任务队列 |
|
|
444
|
+
|
|
445
|
+
## 架构设计
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
449
|
+
│ OpenCode │
|
|
450
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
451
|
+
│ │ Plugin (ocosay) │ │
|
|
452
|
+
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
|
|
453
|
+
│ │ │ Speaker │ │ │
|
|
454
|
+
│ │ │ ┌───────────────────────┐ ┌───────────────────────────┐ │ │ │
|
|
455
|
+
│ │ │ │ StreamReader │ │ StreamingSynthesizer │ │ │ │
|
|
456
|
+
│ │ │ │ (文本缓冲) │ │ (流式合成) │ │ │ │
|
|
457
|
+
│ │ │ └───────────────────────┘ └───────────────────────────┘ │ │ │
|
|
458
|
+
│ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │
|
|
459
|
+
│ │ │ │ StreamPlayer (边收边播) │ │ │ │
|
|
460
|
+
│ │ │ └─────────────────────────────────────────────────────┘ │ │ │
|
|
461
|
+
│ │ │ │ │ │ │
|
|
462
|
+
│ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │
|
|
463
|
+
│ │ │ │ TuiEventBus │ │ │ │
|
|
464
|
+
│ │ │ │ (监听 OpenCode 事件流) │ │ │ │
|
|
465
|
+
│ │ │ └─────────────────────────────────────────────────────┘ │ │ │
|
|
466
|
+
│ │ └───────────────────────────────────────────────────────────┘ │ │
|
|
467
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
468
|
+
│ │ │
|
|
469
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
470
|
+
│ │ TTS Providers (可扩展) │ │
|
|
471
|
+
│ │ ┌──────────────────┐ ┌────────────────────────────────────┐ │ │
|
|
472
|
+
│ │ │ MiniMax │ │ (Future) │ │ │
|
|
473
|
+
│ │ └──────────────────┘ └────────────────────────────────────┘ │ │
|
|
474
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
475
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## 错误处理
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { speak } from 'ocosay'
|
|
482
|
+
import { TTSError, TTSErrorCode } from 'ocosay'
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
await speak('你好')
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (error instanceof TTSError) {
|
|
488
|
+
console.error(`[${error.code}] ${error.message}`)
|
|
489
|
+
console.error(`Provider: ${error.provider}`)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### 错误码
|
|
495
|
+
|
|
496
|
+
| 错误码 | 说明 |
|
|
497
|
+
|--------|------|
|
|
498
|
+
| NETWORK | 网络错误 |
|
|
499
|
+
| AUTH | 认证失败 |
|
|
500
|
+
| QUOTA | 配额超限 |
|
|
501
|
+
| INVALID_VOICE | 无效音色 |
|
|
502
|
+
| INVALID_PARAMS | 无效参数 |
|
|
503
|
+
| PLAYER_ERROR | 播放错误 |
|
|
504
|
+
| UNKNOWN | 未知错误 |
|
|
505
|
+
|
|
506
|
+
## Troubleshooting / FAQ
|
|
507
|
+
|
|
508
|
+
### Q: 首包延迟太长怎么办?
|
|
509
|
+
|
|
510
|
+
**A:** 切换到 `stream` 模式,并选择较短的音色如 `female-shaonv`。
|
|
511
|
+
|
|
512
|
+
### Q: 播放出现卡顿?
|
|
513
|
+
|
|
514
|
+
**A:** 检查网络状况,或降低语速 `speed: 0.8`。
|
|
515
|
+
|
|
516
|
+
### Q: 如何切换不同音色?
|
|
517
|
+
|
|
518
|
+
**A:** 使用 `listVoices()` 查看可用音色,通过 `speak(text, { voice: 'voice-id' })` 指定。
|
|
519
|
+
|
|
520
|
+
### Q: 豆包模式不工作?
|
|
521
|
+
|
|
522
|
+
**A:** 确认 TuiEventBus 已正确初始化,并检查 `autoRead` 开关状态。
|
|
523
|
+
|
|
524
|
+
### Q: 报错 AUTH?
|
|
525
|
+
|
|
526
|
+
**A:** 检查 API Key 是否正确配置,或密钥是否过期。
|
|
527
|
+
|
|
528
|
+
## 开发
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
# 安装依赖
|
|
532
|
+
npm install
|
|
533
|
+
|
|
534
|
+
# 构建
|
|
535
|
+
npm run build
|
|
536
|
+
|
|
537
|
+
# 运行测试
|
|
538
|
+
npm test
|
|
539
|
+
|
|
540
|
+
# 监听模式
|
|
541
|
+
npm run watch
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Contributing
|
|
545
|
+
|
|
546
|
+
欢迎提交 Issue 和 PR!
|
|
547
|
+
|
|
548
|
+
1. Fork 本仓库
|
|
549
|
+
2. 创建特性分支 (`git checkout -b feature/amazing`)
|
|
550
|
+
3. 提交更改 (`git commit -m 'Add amazing feature'`)
|
|
551
|
+
4. 推送分支 (`git push origin feature/amazing`)
|
|
552
|
+
5. 提交 Pull Request
|
|
553
|
+
|
|
554
|
+
## License
|
|
555
|
+
|
|
556
|
+
MIT
|