tts-server-android 自定义TTS插件开发指南如何从零写出第一个可用的 Android TTS 插件【免费下载链接】tts-server-android这是一个Android系统TTS应用内置微软演示接口可自定义HTTP请求可导入其他本地TTS引擎以及根据中文双引号的简单旁白/对话识别朗读 还有自动重试备用配置文本替换等更多功能。项目地址: https://gitcode.com/GitHub_Trending/tt/tts-server-androidtts-server-android 是一款开源的 Android 系统级 TTS 应用内置微软演示接口并支持插件化扩展——你只需用 JavaScript 就能把自己的 TTS 服务做成自定义TTS插件挂进系统朗读链路。这篇文章面向想接入自有 TTS 接口、但没写过 JS 插件的开发者带你从零写出第一个能用的插件并覆盖 SSML 情感合成、多语言混排这些进阶玩法。一、内置 TTS 不够用的几个痛点插件来补位你可能已经碰到过这些情况内置音色太少系统自带那几个读完就腻想换也没得选情绪不可调广告腔、耳语、新闻腔统统切不了语气永远一个调中英混排读串味我明天有 standup 会议里的英文经常被按中文语感读接不了自己的服务你部署的 TTS 接口在应用里没有入口tts-server-android 的解法很直接把合成逻辑交给一段 JavaScript 脚本由应用内嵌的Rhino JS 引擎一个跑在 Android 上的 JavaScript 解释器直接执行——你不用写一行 Kotlin也不用懂 Android 原生开发。整个插件架构分三层应用层界面、配置管理、分组与备用机制引擎层Rhino JS 引擎负责解析和运行插件脚本插件层你的 JavaScript 文件负责真正的合成请求插件本质上是文本文件按插件 ID 存放在缓存目录/Android/data/com.github.jing332.tts_server_android/caches/插件ID在应用里随时增、删、改都不需要重新编译这就是插件热插拔新插件保存完界面立刻就能选到它。二、写出第一个 PluginJS最小可运行插件先交代环境不需要专门配 Android 开发环境克隆源码主要是查模板和示例git clone https://gitcode.com/GitHub_Trending/tt/tts-server-android插件脚本本身在应用内置的编辑器里粘贴保存即可。动手前先看官方插件模板 app/src/main/assets/help/js/tts.md它列出了全部回调。一个插件脚本由两个顶层对象组成PluginJS管合成逻辑EditorJS管界面和数据回调。下面这段是最小可运行骨架——请求你的 HTTP 接口返回音频流let PluginJS { name: 我的TTS, // 应用里显示的名称 id: com.example.mytts, // 唯一ID同时作为文件目录名 author: you, version: 1, // 整数用于更新检测 // 核心入口输入文本返回音频 getAudio: function (text, locale, voice, speed, volume, pitch) { let resp ttsrv.httpPost(https://your-tts-api/v1/synthesize, { text: text, voice: voice, format: mp3 }, { Authorization: Bearer YOUR_KEY }) if (resp.code() ! 200) { throw 合成失败: HTTP- resp.code() } return resp.body().byteStream() // 音频输入流 } } let EditorJS { getLocales: function () { return [zh-CN, en-US] }, getVoices: function (locale) { // key 会作为 getAudio 的 voice 参数value 是显示名 return { demo: 演示音色 } } }getAudio的返回值比较宽容音频输入流、字节数组ByteArray/Uint8Array、ArrayBuffer或者直接返回一个 http(s) 音频地址都行。长文本场景建议改用getAudioV2(request, callback)request里带text / voice / rate / volume / pitch通过callback.write(bytes)分块写出、callback.close()收尾、callback.error(msg)报错实现边合成边播放。三个音频参数怎么换算应用传给getAudio的音频参数统一是 0~100 的整数进插件后按需换算成百分比参数传入范围换算后含义speed 语速0~100(speed × 2) − 100即 -100~100负值放慢、正值加快单位 %volume 音量0~100原样使用0 静音100 最大pitch 音调0~100pitch − 50即 -50~50负值降调、正值升调单位 %换算写法可以直接参考仓库里的示例插件 app/src/main/assets/defaultData/plugin-azure.js而超时、重试、流式播放这些合成侧的配置集中在 lib-tts/src/main/java/com/github/jing332/tts/SynthesizerConfig.kt 里定义。三、两个值得深挖的高级能力SSML 情感合成让同一音色换语气使用场景同一个音色读新闻要严肃、念广告要上扬、耳语要压低——纯文本接口做不到得上SSMLSpeech Synthesis Markup Language语音合成标记语言一种描述怎么读的 XML 标记。实现要点请求体从纯文本换成 SSML 片段mstts:express-as指定风格style、强度styledegree、角色roleprosody负责rate / pitch / volume用户在界面上的选择存进ttsrv.tts.datagetAudio时读出来拼进请求文本必须先做 XML 转义否则特殊字符会打断标记结构下面这段代码演示了读用户配置 → 拼 SSML → 发给服务的关键链路getAudio: function (text, locale, voice, speed, volume, pitch) { let rate (speed * 2) - 100 let style ttsrv.tts.data[style] || general let role ttsrv.tts.data[role] || default let ssml speak xmlnshttp://www.w3.org/2001/10/synthesis xmlns:msttshttps://www.w3.org/2001/mstts xml:lang${locale} voice name${voice} mstts:express-as style${style} styledegree1.0 role${role} prosody rate${rate}% volume${volume}${escapeXml(text)}/prosody /mstts:express-as /voice /speak return postToService(ssml) } // 特殊字符转义防止文本破坏 SSML 结构 function escapeXml(s) { return s.replace(//g, amp;).replace(//g, lt;) .replace(//g, gt;).replace(//g, quot;) }多语言混合发音中英混排不读串使用场景中英混排文本默认按主语言读音英文词容易读错。实现要点两种思路——把文本按语言切段、逐段合成再拼接或者更省事若目标音色支持二级语言Secondary Locale直接给片段套一层lang强制发音语言。音色支持哪些二级语言看它的SecondaryLocaleList示例插件的onVoiceChanged就是据此刷新语言技能下拉框的。强制指定发音语言的关键写法只有两行let body lang xml:langen-US${escapeXml(mixedText)}/lang let ssml speak xml:langzh-CNvoice name${voice}${body}/voice/speak四、EditorJS 配置界面让插件在应用里看得见摸得着插件写完用户还需要能配置它这部分全靠EditorJS对象。它管两类事数据加载与界面控件onLoadData跑在 IO 线程适合拉网络数据并落盘缓存示例插件就是把voices.json拉到本地下次直接读onLoadUI(ctx, linerLayout)直接往布局里加 Android 控件——下拉框用JSpinner、滑杆用JSeekBar、文本框用普通 EditText保存逻辑控件回调里把值写进ttsrv.tts.datagetAudio再读出来形成界面 → 数据 → 合成的闭环声明式变量密钥、区域这类必填项用PluginJS.vars声明一次用户在更多选项 → 设置变量里填代码从ttsrv.userVars读取let PluginJS { vars: { key: { label: 密钥 Key }, region: { label: 区域 Region, hint: 为空时默认 eastus } }, onLoad: function () { if (!(ttsrv.userVars[key] ).trim()) { throw 请先在设置变量中填写密钥 } } }控件侧同样简单一个下拉框加一行保存逻辑就够了onLoadUI: function (ctx, linerLayout) { let styleSpinner JSpinner(ctx, 风格) styleSpinner.setOnItemSelected((sp, pos, item) { ttsrv.tts.data[style] item.value // 写回数据getAudio 可读取 }) linerLayout.addView(styleSpinner) }配置项常见三种形态文本输入text、下拉选择select对应 JSpinner、滑杆slider对应 JSeekBar。在应用里添加你的插件入口如下五、TTS 插件上线前自查清单发布前逐项过一遍每条都是一行代码的事日志关键路径打console.log运行日志在 App 底部日志页翻 →console.log(getAudio, text)错误处理非 200 必须抛错并带状态码别静默吞掉 →if (resp.code() ! 200) throw 合成失败: resp.code()缓存相同入参的结果存起来设容量上限防止无限膨胀 →if (audioCache.has(key)) return audioCache.get(key)流式长文本改用getAudioV2分块写出边合成边播 →callback.write(chunk); callback.close()资源释放合成停止时在onStop清缓存、断连接 →onStop: function () { audioCache.clear() }耗时定位合成慢时用console.time(tts)/console.timeEnd(tts)掐表写完不必装进系统再验证——应用的网页页签可以直接选引擎、填测试文本、调语速试听六、插件还能玩出什么花样离线端侧模型集成 WebAssembly 或端侧 AI 语音合成彻底摆脱网络依赖实时变声在通话链路里做实时语音转换给语音加趣味效果多模态对话接上语音识别ASR做出说完就答的对话循环有声书流水线自动分析小说文本按情节标注情感风格批量朗读无障碍增强为视障用户做场景化语音提示按情境切换语气风格迁移把普通播报转成特定角色音色打造虚拟主播从第一行PluginJS到上线自查一个自定义TTS插件的开发闭环其实不长getAudio跑通合成、EditorJS搭好配置、清单过完就能用。核心资源都在这几处插件模板app/src/main/assets/help/js/tts.md示例插件app/src/main/assets/defaultData/plugin-azure.js合成器配置源码lib-tts/src/main/java/com/github/jing332/tts/SynthesizerConfig.kt运行时 API 文档http / websocket / file 等app/src/main/assets/help/js/遇到问题可去项目的 Issue 与讨论区发起话题下一个音色由你来写。【免费下载链接】tts-server-android这是一个Android系统TTS应用内置微软演示接口可自定义HTTP请求可导入其他本地TTS引擎以及根据中文双引号的简单旁白/对话识别朗读 还有自动重试备用配置文本替换等更多功能。项目地址: https://gitcode.com/GitHub_Trending/tt/tts-server-android创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考