A modern, self-hosted browser client for TeamSpeak, TeaSpeak, and GreenTeaSpeak servers — no install, just open a tab.
🌐 Website · 💬 Open Client · 🕹️ Live Demo · 🚀 Quick Start · 📖 Installation · 🗺️ Roadmap · 🐛 Report Bug
The live demo runs entirely in your browser with simulated data — no real TeamSpeak server involved. It's there to show the UI, not the real connection.
| Desktop | Mobile |
|---|---|
![]() |
![]() |
Run the ready-made container and open http://localhost:8080:
docker run -d --name webspeak3 --restart unless-stopped -p 8080:8080 moepchi/webspeak3:latestEnter the address and port of any reachable TeamSpeak 3, TeamSpeak 6, TeaSpeak, or GreenTeaSpeak server in the connection dialog. The server does not need to be installed on the same machine, modified, or operated by you.
Voice requires a secure context. Microphone access works on
localhost; for access from other devices, place WebSpeak3 behind an HTTPS reverse proxy.
| 🔌 Real TeamSpeak protocol | Connects to actual TS3/TS6, TeaSpeak, and GreenTeaSpeak servers over a WebSocket gateway — the server stays exactly as-is |
| 🎙️ Low-latency voice | Opus-encoded voice with voice activation ("Sprachaktivierung") and adjustable sensitivity |
| 🤫 Whisper | Target your voice at specific channels or clients instead of your whole current channel |
| 🔊 Custom audio output picker | Route playback to any output device — works even in browsers without AudioContext.setSinkId |
| 💬 Full text chat | Channel, server-wide, and private (1:1) chat, each in its own tab — Shift+Enter for a newline |
| 🌳 Live channel/client tree | Correct ordering, status icons (channel commander, away, muted, deafened), country flags, click to switch channels |
| 🖱️ Right-click context menu | Private chat, poke, copy name, and whisper-target toggle straight from a client's row |
| 📜 Server log | Join/leave/switch, channel-group, and channel/server-change events shown inline, like the native client |
| 🔔 Custom sound notifications | Per-event sounds (connect, poke, messages, ...) with volume control and .ts3soundpack import |
| 🪪 Persistent identity | Keeps the same client UID across sessions instead of generating a new one every connect |
| 👉 Poke & away status | Poke clients with an optional message; set yourself away with presets or a custom status |
| ⭐ Favorites & reconnect | Remembers your last server/nickname; switch servers without leaving your tab |
| 📱 Mobile-friendly layout | Responsive single-column layout for narrow screens, not just a shrunk desktop UI |
| 🌍 Localized UI | Interface available in German, English, and Simplified Chinese, detected automatically or switchable in Options |
| 🌗 Dark / light theme | Clean, modern UI that adapts to your preference |
| 🔁 Multi-join (server tabs) | Several connections in parallel — one tab per server, like GreenTeaSpeak 2. Audio stays on the active tab |
| 🔁 Seamless reconnect | Switch or add connections mid-session without tearing down unrelated tabs |
| 🍵 GreenTeaSpeak support | Connects to real GreenTeaSpeak servers (not just TS3/TS6/TeaSpeak), plus an optional GTS-styled UI chrome (dark panels, gold menu bar, connection-tab strip) alongside the default look |
| 🖥️ Screen streaming to/from TS6 | ALPHA · UNSTABLE Share your screen to native TeamSpeak 6 clients and watch theirs. Built on TS6's undocumented Stream/Call protocol — expect it to break; see Roadmap |
🏗️ Architecture details
Browsers can't send raw UDP, which is what TeamSpeak's native protocol runs over, so a pure client-side implementation isn't possible — a server-side gateway is required that speaks the real TS protocol on one side and WebSocket to the browser on the other.
Browser (web/) <--WebSocket--> Gateway (gateway/) <--stdin/stdout JSON--> Rust connector (connector/) <--TS3/TS6/TeaSpeak/GreenTeaSpeak protocol--> Server
web/— Vite + React frontend. TS3-lookalike UI: channel tree, chat tabs, voice controls. Optional GreenTeaSpeak-styled chrome for the connection-tab strip and menu bar.gateway/— Node.js/TypeScript WebSocket server. Spawns the Rust connector per browser WebSocket and relays newline-delimited JSON events between it and the browser. Multi-join opens one/wsconnection per server tab (parallel connectors).connector/— Rust binary wrappingtsclientlib(vendored as a git submodule intsclientlib/, currently tracking a fork with TeaSpeak/GreenTeaSpeak protocol support), the actual TS3/TS6/TeaSpeak protocol implementation. Handles connecting, channel/client state, chat, and Opus-encoded voice. Auto-detects the server dialect on connect (or it can be forced via--server-type).
| Browser | Status | Notes |
|---|---|---|
| Chrome / Edge / Chromium | ✅ Primary target | Recommended for the most complete audio and device support |
| Firefox | ✅ Supported | Core client and voice functionality are supported |
| Safari | 🧪 Experimental | Audio and microphone behavior still needs broader real-device testing |
| Mobile Chromium | ✅ Supported | Responsive client with microphone support over HTTPS |
| Mobile Safari | 🧪 Experimental | UI is responsive; audio behavior remains under active validation |
WebSpeak3 is a public beta. See the beta roadmap for the current scope, known limitations, and the next milestones.
Clone the repo (the Rust connector depends on a git submodule, so pull it in too):
git clone --recurse-submodules https://github.com/Moepchi/webspeak3.git
cd webspeak3This is what ships in docker-compose.yml:
services:
webspeak3:
build: .
image: moepchi/webspeak3:latest
container_name: webspeak3
restart: unless-stopped
environment:
- PORT=8080Port publishing is left out on purpose — set it in a local, gitignored docker-compose.override.yml so it doesn't clash with whatever else is already using a port on your host:
services:
webspeak3:
ports:
- "8080:8080"Then docker compose up -d and open http://localhost:8080 (or whatever host port you chose).
Prefer to skip the build entirely? A prebuilt image is published on Docker Hub:
docker run -d -p 8080:8080 --name webspeak3 moepchi/webspeak3:latestThe UI shows a small Ko-fi donation button in the bottom right corner. Building it yourself and would rather not carry someone else's donation link? docker compose build --build-arg DONATE_URL= removes it (any other value points it somewhere else). Outside Docker the same thing is a build-time variable: VITE_DONATE_URL= npm run build in web/.
The Design Store (browse/upload community themes) points at the maintainer's shared catalog by default, same as the official client — no setup needed. Want to run your own store instead, backed by your own gateway (STORE_ENABLED=1, see below)? docker compose build --build-arg STORE_URL=https://your-gateway.example.com/api/store/themes. Outside Docker: VITE_STORE_URL=https://your-gateway.example.com/api/store/themes npm run build in web/.
All optional; add them under environment: in your compose override next to PORT.
| Variable | Default | Purpose |
|---|---|---|
PORT |
8080 |
Port the gateway listens on. |
WEB_STATIC |
on | Set to 0 to disable serving the built UI (web/dist) entirely — the gateway then only answers /ws, /api/feedback and /healthz. Use this when the UI is hosted elsewhere (e.g. a CDN/static host) and the gateway is purely a backend. |
WEB_DIST |
web/dist next to the gateway |
Overrides the directory served when WEB_STATIC is on. Only needed for non-standard layouts. |
BROADCAST_TOKEN |
unset | Bearer token required to call /api/broadcast (send an in-app notice to all connected clients before a maintenance restart). Endpoint 404s while unset. |
TLS_CERT / TLS_KEY |
unset | Paths to serve the gateway over HTTPS/WSS directly instead of behind a reverse proxy/tunnel. Both must be set together. |
STORE_ENABLED |
off | Set to 1 to turn this gateway's own /api/store/themes endpoint on. Only needed if you want to run your own Design Store instead of using the maintainer's shared one (see STORE_URL build arg above) — the endpoint 404s while unset. |
STORE_DATA_FILE |
store-themes.json in the container's working dir |
Where submitted themes (including screenshots) are stored. Mount a volume over this path if they should survive a container recreate. |
STORE_ALLOWED_ORIGIN |
* |
CORS origin allowed to call the store endpoint, if you want to restrict it to your own frontend's origin. |
STORE_ADMIN_TOKEN |
unset | Bearer token that lets its holder publish under the reserved author name(s) and moderate (delete) any theme. Without it, nobody can. |
🛠️ Manual installation (without Docker)
Prerequisites
- Node.js 20+ and npm
- Rust (stable toolchain) — rustup.rs
- CMake and a C/C++ toolchain — needed to build the Opus codec library (
audiopus_sys) used for voice. On Windows, the Visual Studio "Desktop development with C++" workload covers this; on Linux, installcmakeandbuild-essential(or equivalent); on macOS,cmakevia Homebrew plus Xcode command line tools. - git
1. Clone the repository
The Rust connector depends on the tsclientlib crate, vendored as a git submodule — make sure to pull it in too:
git clone --recurse-submodules https://github.com/Moepchi/webspeak3.git
cd webspeak3If you already cloned without --recurse-submodules:
git submodule update --init --recursive2. Build the Rust connector
cd connector
cargo buildNote: if the build fails with a CMake error like
Compatibility with CMake < 3.5 has been removed, it's because the vendored Opus source uses an oldcmake_minimum_required. Work around it with:CMAKE_POLICY_VERSION_MINIMUM=3.5 cargo build(On Windows PowerShell:
$env:CMAKE_POLICY_VERSION_MINIMUM = "3.5"; cargo build)
This produces connector/target/debug/ts-connector (or ts-connector.exe on Windows), which the gateway spawns automatically — no manual step needed after this.
3. Install and start the gateway
cd gateway
npm install
npm run devThis starts the WebSocket gateway on ws://localhost:8080. Open http://localhost:8080 for the UI (serves web/dist). After UI changes run cd web && npm run build, then reload. Optional Vite (npm run dev in web/, typically :5173) is only for HMR. WEB_STATIC=0 npm run dev serves API/WebSocket only.
4. Install and start the web frontend
In a separate terminal:
cd web
npm install
npm run devVite will print a local dev URL (typically http://localhost:5173) — open it in a browser.
The Vite app connects to the gateway at ws://localhost:8080/ws.
Preferred UI: http://localhost:8080 (static
web/dist— refresh aftercd web && npm run build). Vite:5173is optional HMR only.
5. Connect
In the web UI, enter the address of a TeamSpeak server and a nickname, then click Connect. Voice requires microphone permission when you enable the mic button; the output-device picker (if your browser supports it) requires no extra permission.
Rebuilding after changes
- Frontend and gateway changes hot-reload automatically (
npm run devin both cases). - Connector changes require a rebuild (
cargo buildinconnector/) and a reconnect from the browser — if the oldts-connectorbinary is still running (an active browser connection), disconnect first or the build will fail to overwrite the binary.
Skip the connect dialog entirely and drop visitors straight into a TeamSpeak server. Two ways to do it — use either, or both together.
Add a connect parameter to your instance's URL and send that link out — in a Discord post, a landing page button, a QR code, wherever:
https://your-instance/?connect=voice.example.com&nickname=Guest&channel=Lobby&token=abc123
| Parameter | Required | What it does |
|---|---|---|
connect |
yes | Server address to join, host or host:port |
nickname |
no | Nickname to connect with — omit it and the visitor gets an auto-generated Guest-XXXX |
channel |
no | Default channel to join |
token |
no | Server/channel privilege key, if the target requires one |
This works on any deployment — no rebuild, no configuration. WebSpeak3 connects as soon as the page loads and immediately strips these parameters from the address bar, so refreshing the tab (or someone screenshotting/forwarding the URL later) won't silently reconnect off a stale link.
Running a dedicated instance for one community or event? Set a default at build time so every visitor auto-connects with zero parameters, no link needed:
docker compose build --build-arg DEFAULT_SERVER=voice.example.com --build-arg DEFAULT_CHANNEL=LobbyOutside Docker, the same thing is a build-time variable:
VITE_DEFAULT_SERVER=voice.example.com VITE_DEFAULT_CHANNEL=Lobby npm run build(in web/). Leave DEFAULT_CHANNEL/VITE_DEFAULT_CHANNEL out to just join the server's default channel.
A share link (above) always takes priority over the baked-in default when both are present — so you can point most visitors at your default server while still sending specific people somewhere else via link.
Either way: on your own hosted instance, the mandatory first-visit Terms of Use is shown and accepted before anything auto-connects; and if the connection fails, the connect dialog opens prefilled so the visitor can fix it and retry by hand.
The vast majority of this project's code was written by Claude Code (Anthropic's Claude), working iteratively with the repo owner one feature at a time.
WebSpeak3 is an independent, open-source, self-hosted project and is not affiliated with, associated with, authorized by, endorsed by, or in any way officially connected with TeamSpeak Systems GmbH.
"TeamSpeak", "TS3", and related logos or names are registered trademarks of TeamSpeak Systems GmbH. Likewise, WebSpeak3 is not affiliated with, associated with, authorized by, or endorsed by TeaSpeak or GreenTeaSpeak; "TeaSpeak" and "GreenTeaSpeak" are names of their respective projects/ operators. All product and company names are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.


