Each connection now lives in its own ServerTab with its own tree, info and chat views plus its own mic-mute, deafen, away and commander state. MainFrame keeps only the shell (menus, toolbar, status bar) and shows one tab at a time; the tab bar below the toolbar appears once a second server is open. Incoming voice from every connection mixes into the same output — each connection already renders one line per speaker. Capture is exclusive: a connection only opens the microphone while it holds it, and a toolbar button moves it to the visible tab. The mic follows a disconnect to another connected tab, and push-to-talk drives whoever holds it. Startup bookmarks are no longer mutually exclusive: every flagged bookmark opens its own tab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TS3J Client
A desktop TeamSpeak 3 client built on top of the ts3j reverse-engineered
TS3 protocol library. It looks and behaves like the official TS3 client and focuses
first on the features that matter most: real-time voice with Opus encoding and
voice-activation detection (VAD).
The project is split so the frontend can be replaced (e.g. a future web UI via TeaVM/CheerpJ) without touching the library:
| Module | Artifact | Responsibility |
|---|---|---|
core |
ts3-client-core |
Frontend-agnostic library: protocol integration, server model, connection orchestration, audio abstractions. No UI, no platform audio, no JNA. |
desktop |
ts3-client-desktop |
Desktop audio backend: Java Sound capture/playback + native Opus via JNA, implementing the core audio interfaces. |
swing |
ts3-client-swing |
Swing desktop UI + entry point. Depends on core and desktop. |
The core exposes AudioBackend / VoiceInput / VoiceOutput; the frontend injects
a concrete backend (JavaSoundAudioBackend) into TeamspeakConnection. A different
frontend supplies its own UI and audio backend while reusing core unchanged.
Features
Voice (the priority)
- Native Opus codec via a direct JNA binding to the system
libopus(no bundled/native-jar dependency). Encoding at 48 kHz, 20 ms frames. - Voice Activation Detection (VAD) with the same three modes as the TS3 client:
- Volume Gate — RMS/dBFS threshold with a live input meter and hangover.
- Automatic — a dependency-free speech detector (short-term energy + spectral flatness + dominant frequency against an adaptive noise floor).
- Hybrid — transmit only when loud enough and detected as speech.
- Optional VAD over Push-To-Talk (keep detecting voice while PTT is available).
- Capture pre-processing — a mini audio-processing chain in the same order as the
TS3 client's WebRTC APM, applied before activation and encoding (and feeding the VAD):
input gain → high-pass filter (80 Hz, always-on rumble/DC removal) → noise
suppression → typing attenuation → AGC:
- Remove background noise — spectral denoiser (decision-directed Wiener with minimum-statistics noise tracking), with an adjustable removal level.
- Typing attenuation — detects impulsive keystroke transients (short, broadband, high-frequency bursts) and ducks them while leaving sustained speech intact.
- Automatic gain control (AGC) — normalises mic loudness to a target level (fast attack / slow release, noise-gated so silence is never amplified).
- Echo cancellation (WebRTC AEC3) is intentionally omitted — it needs the loudspeaker reference signal and matters mainly for open speakers, not the typical headset.
- Push-to-Talk — bind any key; transmits only while held (while the app is focused).
- Continuous transmission mode.
- Playback mixing — each speaker gets its own Opus decoder and audio line, so multiple simultaneous talkers are mixed and one slow decode never blocks others.
- Whisper receive — targeted voice is decoded and played like normal voice.
- Per-client mute, master deafen, adjustable mic gain / playback volume.
- On-the-fly Opus tuning — bitrate, complexity, VBR, FEC and voice/music codec can all be changed live from the options dialog and take effect on the running encoder immediately (no reconnect); a voice↔music switch transparently rebuilds the encoder because Opus fixes its application mode at creation.
- Configurable capture/playback devices.
Server interaction
- Connect to any TS3 server (an identity is generated on first use if none exists).
- Channel/client tree styled like TS3, updated live from protocol events (joins, leaves, moves, channel create/edit/delete, nickname/mute/away changes).
- Talk indicators — speakers turn green live as they talk.
- Info panel — selecting a channel shows its topic and description (fetched on demand); selecting a client shows its server groups and channel group (names resolved from the server's group lists), talk power, platform and version.
- Server-group badge next to each client's nickname in the tree.
- Spacer channels — TS3
[spacer]/[*spacer]/[c/l/r spacer]names render as non-interactive separators (fill, centred, aligned). - Double-click a channel to join; right-click a client to poke, open a private chat, or locally mute them.
- Chat to the current channel or the whole server; receive channel/server/private messages and pokes.
- Server bookmarks — quick-connect menu with add/edit/remove management, each optionally pinned to a specific identity.
- Identity management (Tools → Identities) — keep several identities, mark one
as the default, pick one per server or per bookmark, rename, raise an identity's
security level, and import/export TeamSpeak-compatible
.iniidentity files. - Self status — Away (with message) and Channel Commander toggles.
- Status bar shows the server name, user count and live ping.
- Change your nickname, mute/deafen from the toolbar.
Requirements
- Java 17+ (developed/tested on Temurin 26)
- The native Opus library on the system:
- Debian/Ubuntu:
sudo apt install libopus0 - Arch:
sudo pacman -S opus - macOS:
brew install opus
- Debian/Ubuntu:
Building
The client depends on ts3j, so install that into your local Maven repo first:
cd ../ts3j && mvn -DskipTests install
cd ../ts3-client && mvn -DskipTests package
This produces a runnable fat-jar at swing/target/ts3-client.jar.
Running
java -jar swing/target/ts3-client.jar
# or, during development:
mvn -pl swing exec:java
Then use Connections → Connect…, enter a server address, port (default 9987), nickname and identity, and connect. Open Tools → Options to pick audio devices and tune voice activation while watching the live meter.
Architecture
core/ com.ts3client
├── config.Settings persisted prefs (~/.ts3jclient/settings.properties)
├── config.Bookmarks persisted server bookmarks (with per-server identity)
├── config.IdentityStore managed identities (~/.ts3jclient/identities/*.ini)
├── audio abstractions + reusable DSP (no platform code)
│ ├── AudioBackend factory for a platform's VoiceInput/VoiceOutput
│ ├── VoiceInput capture source (extends ts3j Microphone) + gating controls
│ ├── VoiceOutput voice-packet playback sink
│ ├── OpusParameters live-tunable encoder settings
│ └── SpeechDetector feature-based speech-probability VAD (Automatic/Hybrid)
└── net
├── TeamspeakConnection ties socket + audio backend + model, translates events
├── ServerModel thread-safe channel/client state
├── ChannelNode/ClientEntry view models
└── ConnectionListener frontend callbacks
desktop/ com.ts3client.audio.desktop
├── Opus JNA binding to native libopus
├── OpusEncoder/OpusDecoder thin codec wrappers
├── AudioDevices device enumeration + line opening (48 kHz/16-bit)
├── JavaSoundVoiceInput capture + VAD/PTT gating + Opus encode
├── JavaSoundVoiceOutput per-client Opus decode + playback + mixing
└── JavaSoundAudioBackend wires the above into the core AudioBackend
swing/ com.ts3client
├── Main entry point (look & feel, settings, backend injection)
└── ui
├── MainFrame window: menu, toolbar, tree | chat, status bar
├── ServerTreePanel TS3-style channel/client tree (group badges, spacers)
├── Spacers TS3 spacer-channel name parsing/rendering
├── InfoPanel channel description / client group + details view
├── ChatPanel chat log + input
├── SettingsDialog audio + VAD options with live meter
├── ConnectDialog connect form
├── BookmarksDialog manage saved servers
├── IdentitiesDialog manage identities (new/import/export/improve)
├── IdentityChooser identity drop-down shared by connect/bookmark forms
├── LevelMeter dBFS meter with threshold marker
├── Icons programmatic vector icons (no image assets)
└── Theme palette + fonts
Known limitations / next steps
- The Automatic/Hybrid VAD uses a lightweight energy/spectral detector rather than the WebRTC GMM model the official client ships; it is intentionally dependency-free and reusable in the core library.
- Group display shows names; group icons are not rendered.
- Whisper is received/played but not yet sendable from the UI.
- Playback decodes streams as mono; stereo music-bot audio is down-mixed.
- Push-to-talk is captured via Swing key events, so it only works while the app window has focus (no global hotkey).
- No file transfer, avatars, or server/channel administration UI yet.