dsh-go-local-voice · 鲸鲸朗读

English · 中文
面向 dsh-go 手机 App 原生界面的 TTS 插件(不是网页版)
给 DeepSeek Harness / dsh-go 手机 App 原生界面用的语音朗读插件: 长按任意一条回复 →「读出来」,用小米 MiMo TTS(或字节 豆包 Seed TTS)合成,直接在 App 里播放。
为什么需要它:DSH 生态里的 TTS 插件大多只跑在 Harness 网页版——它们的声音靠浏览器的 Web Audio 播, Flutter 原生界面里根本不会触发。这个插件走的是 dsh-go 桥接的
audio能力,所以能真的出声。
功能
- 长按回复 → 读出来;也可以「存成音频」导出到手机存储(MP3)
- 小米 MiMo:
mimo-v2.5-tts内置 8 个音色(冰糖 / 茉莉 / 苏打 / 白桦 / Mia / Chloe / Milo / Dean),可加语音指令(语气、方言、情绪) - 豆包 Seed TTS:
seed-tts-2.0/ 声音复刻seed-icl-2.0(走 HTTP v3 单向流) - 长文一次念完:回合结束后台分段预取,点一次交出去的是一整段音频,不是"点一次念 120 字"
- 可停止:
停止按钮真的停得住(固定的播放实例 + 稳定的 lease) - 实时状态:输入框上方状态条显示
正在合成语音…/朗读中 0:12 / 1:40;面板里有进度条 - 音频历史:导出过的文件在面板里列出来,点一下复制路径
- Markdown 清洗:代码块、链接、表格、行内代码都不会被念出来(链接读作"链接")
安装
插件目录放在 $DSH_HOME/dsh-go/plugins/ 下(DSH_HOME 默认 ~/.dsh):
git clone https://github.com/27Synthesis/dsh-go-local-voice.git \
"$HOME/.dsh/dsh-go/plugins/local-voice"
dsh plugin --profile web add "$HOME/.dsh/dsh-go/plugins/local-voice"
装完在 App 里重启一次引擎(设置 → 引擎 → 重启引擎),然后:
- 长按助手消息 → 菜单里出现「读出来」「存成音频」
- 输入框上方「插件」胶囊 →「鲸鲸朗读」
卸载:dsh plugin --profile web remove local-voice,再删目录。
配置
第一次用先在主面板里点「打开设置」,填 API Key:
| 项 | 说明 |
|---|---|
| 小米 Key | 在 小米 MiMo 控制台 申请,形如 sk-... |
| 豆包 Key | 在豆包语音控制台申请(标准音色与 Agent Plan 都支持) |
| 豆包音色 ID | 形如 zh_female_...;用声音复刻时填复刻音色 ID |
| 每段字数 | 默认 140。约 0.08 秒/字,140 字≈11 秒,稳在单次 30 秒的生产预算内 |
| 自动预取 | 默认开。回复一结束就在后台把整条合成好,之后点「读出来」几乎秒播 |
Key 只存在本机 data/settings.json(权限 0600),不会进仓库、不会出现在界面里、不会发给模型。
data/ 已在 .gitignore 中。
它是怎么做到的(给想改代码的人)
dsh-go 原生插件的桥接协议(v1.2)有三条硬约束决定了实现方式:
media路由必须在 30 秒内产出字节(CAP_LIMITS.mediaMs)——这是生产预算,不是音频时长上限。- 音频本身可以很长(≤16 MiB),App 拿到整段自己播;
play/stop作用在同一个instance上。 - 插件不能主动推播放效果:
onCapability的返回值不会被执行成效果,所以"播完自动接下一段"做不到。
于是:
- 预取:
session/event的turn/end(completed)时,把回复按 140 字切段、并发 3 合成到data/cache/<sha1>/sNNN.mp3,全齐后拼成all.mp3。 - 播放:
read只建任务并立刻返回{type:'audio', op:'play', instance:'read', src:'media:speech', key};App 回来GET媒体路由,插件把缓存拼成一个整段 MP3(MP3 裸帧直拼是合法的,没有 ID3 头)。 - 冷缓存:最多等 15 秒,先交已经合成好的前几段(约 30~60 秒语音),剩下的后台继续;10 分钟内再点一次从上次停的地方接着念。
实测(小米 MiMo v2.5 TTS,非流式 mp3,手机网络):15 字≈2.4 s,60 字≈4.7 s,120 字≈8.3 s, 240 字≈38 s(超预算),400 字≈32.6 s,800 字≈54.9 s → 这就是为什么段长取 140。
踩过的坑
instance必须固定(这里用read)。每次播放换新实例名,stop就找不到 lease,"停止"永远按不动——不是接口不支持。- 面板的
select下拉框不可靠:音色改成list一行一个、点一下立刻写设置 + toast +refresh。 - 状态必须走数据绑定(
{"$": "data.info.status"}):视图里写死文字的话,每秒推数据界面也不会变。 - 主面板只用 v1 组件(stack/text/callout/progress/list/keyValue/actions),需要输入框的项放「朗读设置」面板(v2)。
已知限制
- 不能"回复结束自动开口":协议只允许用户点按触发播放。
- 小米目前没有语速参数(豆包有
speech_rate,面板还没暴露)。 - 暂停 / 继续:桥接的
pause实测不可靠,已移除。 - 单次播放音频上限 16 MiB(约 5.5 分钟语音),超长回复会在后台继续合成、下次点按接着念。
- 豆包那一路没有 Key 实测过(按 v3 单向流接口写的),首次使用请先短句试听。
排查
data/debug.log:点按、媒体字节数与耗时、audio.ended/stopped/error、预取情况(不记 Key、不记正文)data/settings.json:当前配置(0600)- 自查:
node $DSH_HOME/skills/dsh-go-plugin/kit/bin/check.mjs <本目录> --load
致谢
- 思路与小米/豆包接口细节参考了 dsh-xiaomi-tts(MIT)——它是网页版实现,播放侧用浏览器 Web Audio,本仓库是面向 dsh-go 原生界面的独立实现。
- 插件格式与桥接能力来自 deepseek-harness 的 dsh-go 插件协议。
No comments yet. Be the first to write one.