Introduction¶
DSH often runs within a proot container on Android. The /dev/snd device is inaccessible inside the container, so commands like ffplay and aplay fail entirely. Furthermore, the Android WebView on some ROMs lacks the Chinese voice pack, causing speechSynthesis.speak() to go silent. Consequently, both common paths for text-to-speech are non-functional.
The only stable and usable method is to play an MP3 using <audio> within the page. The plugin introduced below does exactly this: it synthesizes text into an MP3 in the DSH host process, exposes a same-origin route via the built-in DSH webServer, and the page simply points src to it. It is maintained by flyinghouse-ma.
What is it¶
dsha-tts-bridge is the host-side Text-to-Speech (TTS) bridge. It does one thing: Text → MP3, and then the page is responsible for playing it.
It does not rely on API keys or accounts; it uses the Microsoft Edge TTS WebSocket protocol (api.msedgeservices.com, with the legacy endpoint speech.platform.bing.com), rather than the official Azure TTS SDK. The plugin implements a WebSocket client (lib/websocket.js) on its own, resulting in zero npm dependencies.
Core Features¶
- Synthesizes speech within the host process and returns the result as MP3, which the page can play using
<audio>. - Exposes same-origin routes via the built-in DSH
webServer, allowing direct references within the WebView without CORS issues. - Implements an LRU cache for the last 40 synthesis results in memory; repeating the same sentence will not trigger repeated requests.
- Enforces a 15-second timeout per synthesis; it returns an error on failure without blocking the page.
- Returns
audio/mpegon success; returns502on failure along with a plain text reason.
Installation and Activation¶
The plugin is installed via DSH’s profile mechanism:
dsh plugin --profile web add @flyinghouse-ma/dsha-tts-bridge
After installation, you need to restart dsh web because it is a host-side module, and hot reload does not apply.
Typical Usage¶
After installation, any page (including WebView) can directly use <audio> for playback:
<audio src="/dsh-tts/say.mp3?text=需要你确认一下" autoplay></audio>
If you need to control volume or reuse the audio, you can use fetch to retrieve the Blob and play it:
const r = await fetch('/dsh-tts/say.mp3?text=' + encodeURIComponent('你好'));
const url = URL.createObjectURL(await r.blob());
new Audio(url).play();
Server parameters are as follows:
| Parameter | Description |
|---|---|
text |
The text to speak, maximum 200 characters |
voice |
Optional voice, default is zh-CN-XiaoxiaoNeural (Xiaoxiao, female Chinese voice) |
The 200-character limit is set for “reminder” scenarios to prevent this route from being abused as a general-purpose TTS proxy.
Suitable Scenarios and Notes¶
Suitable for the following situations: developers running DSH in an Android proot container who need short voice announcements (approval reminders, status updates); or scenarios where WebView voice is unavailable and a stable reading path is required.
Points to note:
- Requires access to Microsoft’s speech service endpoints. Returns
502if the network is unreachable; this is a known limitation. - The plugin is only responsible for synthesis; playback control (volume, interruption, queue) is decided by the page itself.
- The plugin runs with the permissions of the current
dshprocess. You should read the source code and verify the license (MIT) before deciding to install it. - Not suitable for reading long texts (200-character limit per request).
Conclusion¶
dsha-tts-bridge solves a narrow but real problem: when there is no audio device in the container and the WebView cannot pronounce Chinese, using a same-origin MP3 route restores the broadcast functionality. It does not aim for general TTS capabilities; the interface and limitations are clearly defined.
Directory page: https://www.skillhub.cn/plugins/flyinghouse-ma/dsha-tts-bridge
Source code: https://github.com/flyinghouse-ma/dsha-tts-bridge