← Voice-driven workspace navigation
Live

Self-hosted voice: signaling to speech

Same feature, a second implementation. Instead of routing microphone audio through a managed media cloud and hosted speech APIs, this path runs the media server, speech recognition, and speech synthesis on my own VPS. The permission-checked resolver and fixed workspace views underneath are unchanged, only how the audio gets there and back is different.

Two ways to wire the same feature

Both backends are live. Rooms are tagged so a session lands on one implementation or the other, and either way it reaches the same resolver, the same access rules, and the same set of workspace views.

Media transport, managed cloud A managed WebRTC media cloud hosts the room, the SFU, and the relay fallback.
Media transport, self-hosted A self-hosted media server on my own VPS runs the SFU plus a built-in TURN relay.
Speech recognition, managed cloud A hosted speech-to-text API turns audio into a transcript.
Speech recognition, self-hosted faster-whisper runs on the same VPS, no per-call cloud API in the loop.
Speech synthesis, managed cloud A hosted text-to-speech API voices the assistant's reply.
Speech synthesis, self-hosted Kokoro TTS now voices replies too, no hosted synthesis API in the loop.

Signaling, media, and the speech hop

Signaling, the media server, and its relay all run on one self-hosted VPS behind a tunnel. Control traffic always takes one path; audio takes whichever of two paths ICE negotiation settles on for that session, not both at once.

Browser: microphone + speaker one continuous session carries mic and playback Signaling: tunnel (HTTPS/WSS) room join, ICE exchange, worker control events, always used Self-hosted media server, own VPS SFU plus a built-in TURN relay, both self-hosted Direct UDP fast when possible TURN relay over TLS, fallback Worker: speech in, speech out faster-whisper in, Kokoro TTS out, one worker same session

Signaling, always tunneled

Room join, ICE candidate exchange, and worker control events ride one tunnel as HTTPS/WSS regardless of network conditions.

Media, whichever path wins

Audio takes direct UDP when the client's NAT allows it, or falls back to the media server's built-in TURN relay over TLS on 443 for CGNAT and locked-down networks. A session sticks to one path for its whole duration.

One worker, both directions

faster-whisper turns incoming speech into text, and Kokoro TTS turns the assistant's reply back into an outbound audio track, so both legs of the conversation stay on infrastructure I run.

Why self-host this layer

No per-call speech fees

faster-whisper and Kokoro TTS run on my own VPS instead of metered cloud APIs, so cost stops scaling with every utterance.

One less provider boundary

Audio and transcript text don't have to leave infrastructure I run for the speech step, on top of the same short-lived, permission-checked room grants.

A second real implementation

Building and running both versions is the point: the same feature proven two different ways, not a rewrite that throws the first one away.

Privacy and failure boundaries

The same content-free diagnostics apply here as on the managed-cloud path. Room names, tokens, credentials, raw audio, transcript text, parsed commands, and provider responses stay out of logs. Short-lived grants limit what the browser can publish, and the backend rechecks every request before a workspace view is selected, regardless of which voice backend the room is tagged for.

Self-hosted media Self-hosted recognition Self-hosted synthesis Short-lived grants Permission-checked views Content-free diagnostics