# Live Plugin (V8) vs. Planned SDK/ServerLib Architecture A detailed, side-by-side comparison of **what the shipping plugin does today** (V8, branch `tsmain`) versus **what the new plan proposes** (branch `ts3-Testing`, "Option B"): which API each uses, what stays the same, and what fundamentally changes. > [!IMPORTANT] > Both use "a TeamSpeak SDK" — but **two completely different ones**. > **Today:** the *Client Plugin SDK* (a guest inside the retail TeamSpeak client). > **Plan:** the *TeamSpeak 3 SDK* (ClientLib + ServerLib — your own client and your own server). > Same word, opposite architecture. --- ## 1. At a glance | Topic | Live V8 (today) | Plan (Option B) | |---|---|---| | SDK used | **Client Plugin SDK** | **TeamSpeak 3 SDK** (ClientLib + ServerLib) | | Header(s) | `sdk/include/ts3_functions.h` (`TS3Functions`) | `clientlib.h` + `serverlib.h` | | Function family | `ts3plugin_*` (callbacks), `ts3Functions->*` | `ts3client_*` and `ts3server_*` | | What "the server" is | Retail `ts3server` (foreign) | **Our own** `conan-voice-server` (ServerLib) | | What "the client" is | Retail TeamSpeak loads our DLL | **Our own** app embedding ClientLib | | Who decides "who hears whom" | The client, locally (volume/mute) | The **server** (`ts3server_setClientWhisperList`) | | Position exchange | Peer-to-peer via `sendPluginCommand` (CEPOS) | Client to server via `CLIENT_META_DATA` | | Audio control | `onEditPlaybackVoiceDataEvent` (edits samples) | Server-side whisper routing + client-side 3D/PCM | | 3D sound | `systemset3D*` / `channelset3DAttributes` (in plugin) | Same `ts3client_*3D*` functions (in our app) | | Needs retail TeamSpeak | **Yes** (users already have it) | **No** — a custom client app is required | | License | Free | **Paid** (free evaluation for dev only) | | Lifecycle | Tied to TS3 | TS3 SDK **EOL 2027-12-31** | | "Who hears whom" reach | Local volume only; everyone still gets packets | Real packet routing (B/C never receive A) | | Effort | Exists, ships today | Build **both** a client and a server | --- ## 2. The two SDKs — the root of the confusion The word "SDK" refers to two different TeamSpeak products. ### 2a. Today: Client Plugin SDK - Proof in the repo: `sdk/README.md`, line 1 = "TeamSpeak 3 Client Plugin SDK". - We ship a **DLL** that the **retail TeamSpeak client loads**. - We are a guest: we react to events (`ts3plugin_*`) and trigger a few client actions through the function table `TS3Functions`. - We **cannot** route voice at the server level. We can only alter incoming voice **locally** (quieter, pan, filter). ### 2b. Plan: TeamSpeak 3 SDK (ClientLib + ServerLib) - A separate download, different libraries, different function families (`ts3client_*` and `ts3server_*`). - We **host our own voice server** (ServerLib) and **build our own client** (ClientLib). - The retail TeamSpeak client is no longer involved — and in fact **cannot** connect to an SDK server (error `0x0705`, per TeamSpeak docs). > [!NOTE] > This is not an "update" — it is an architecture switch: from "a plugin guest inside > TeamSpeak" to "a standalone client + server product". --- ## 3. Architecture, side by side ### Live V8 (today) ```mermaid flowchart TD mod["Conan Exiles mod"] -->|"Pos.txt"| plugin["Our plugin (DLL inside retail TeamSpeak)"] plugin -->|"sendPluginCommand CEPOS"| peers["Other clients"] peers -->|"CEPOS packets"| plugin plugin -->|"onEditPlaybackVoiceDataEvent: gain/pan/filter"| out["Local audio output"] ts["Retail ts3server"] -.->|"transport + channels only, no game logic"| plugin ``` - Each client computes **for itself** how loud it hears the others. - Positions travel **peer-to-peer** via plugin commands (the retail server just relays them). - "Who hears whom" is really just a **volume**: voice packets still reach everyone in the channel; they are only mixed quiet/muted locally. ### Plan (Option B) ```mermaid flowchart TD game["Conan Exiles"] -->|"local IPC"| client["Conan Voice Client (custom app, ClientLib)"] client -->|"CLIENT_META_DATA = position"| server["Conan Voice Server (custom process, ServerLib)"] server -->|"PlayerManager + ProximityEngine + VoiceRouter"| router["setClientWhisperList A to B,C"] router --> transport["TeamSpeak voice transport (SDK)"] transport -->|"A's voice only to B and C"| listeners["Clients B, C (D gets nothing)"] ``` - The **server** decides centrally who hears whom, and enforces it via whisper lists (real routing). - Positions go **client to server** (no longer peer-to-peer). --- ## 4. API comparison — concrete functions ### 4a. Callbacks (events we react to) | Purpose | Live V8 (`ts3plugin_*`) | Plan (`ts3client_*` / `ts3server_*`) | |---|---|---| | Connection status | `onConnectStatusChangeEvent` | Client: `onConnectStatusChangeEvent` | | Our own data transfer | `onPluginCommandEvent` (CEPOS/CEDRAIN) | Server: read `CLIENT_META_DATA` change | | Alter audio | `onEditPlaybackVoiceDataEvent` | Client: `onEditPlaybackVoiceDataEvent` (local effects only) | | 3D rolloff | `onCustom3dRolloffCalculationClientEvent` | Client: `onCustom3dRolloffCalculationClientEvent` | | Client moves | `onClientMoveEvent` / `onClientMoveMovedEvent` | Server: `onClientMoved`; Client: `onClientMoveEvent` | | Channel description (hub) | `onChannelDescriptionUpdateEvent`, `onUpdateChannelEvent`, `onUpdateChannelEditedEvent` | Handled differently (root-channel config) | | Who is talking | `onTalkStatusChangeEvent` | Server: `onClientStartTalkingEvent` / `onClientStopTalkingEvent` | | Connect/disconnect (server) | not available to us | Server: `onClientConnected` / `onClientDisconnected` | *Evidence: `src/ts/entry/ts3_exports.h`, `src/ts/entry/ts3_entry.c`.* ### 4b. Functions we actively call (what we trigger) | Purpose | Live V8 (`TS3Functions`, `g_ts3.*`) | Plan (SDK) | |---|---|---| | Send our own data | `sendPluginCommand("CEPOS:...")` | Client: `setClientSelfVariableAsString(CLIENT_META_DATA)` + `flushClientSelfUpdates` | | Wakeup / tick | `sendPluginCommand("CEDRAIN:1")` | Own event loop in the server | | Unmute clients | `requestUnmuteClientsTemporary` | Not needed (routing is server-side) | | Per-client volume | `setClientVolumeModifier` | Mainly via whisper routing; fine gain optionally local | | Move into channel (hub) | `requestClientMove` | Server: `ts3server_*` channel ops | | 3D global | `systemset3DSettings` | Client: `ts3client_systemset3DSettings` | | 3D listener (self) | `systemset3DListenerAttributes` | Client: `ts3client_systemset3DListenerAttributes` | | 3D per speaker | `channelset3DAttributes` | Client: `ts3client_channelset3DAttributes` | | **Who hears whom (routing)** | **does not exist** (local volume only) | **Server: `ts3server_setClientWhisperList`** | | Start server | not applicable | `ts3server_initServerLib` then `ts3server_createVirtualServer` | | Stop server | not applicable | `ts3server_stopVirtualServer` then `ts3server_destroyServerLib` | | Start client | (retail client does this) | `ts3client_initClientLib` then `spawnNewServerConnectionHandler` then `startConnection` | *Evidence: `src/ts/adapter/ts3_adapter.c` (around lines 427, 653-690, 737-768).* > [!TIP] > The 3D functions (`systemset3DListenerAttributes`, `channelset3DAttributes`, > `systemset3DSettings`) are named almost identically in **both** SDKs, so that part is > largely portable. The **big** difference is routing: `ts3server_setClientWhisperList` > exists only in the ServerLib. --- ## 5. Data flow in detail ### 5a. Position exchange | | Live V8 | Plan | |---|---|---| | Source | `Pos.txt` (game mod) via `pos_file.c` | `IGameDataProvider` (ported from Mumble/V8) | | Packing | CEPOS, 56 bytes, base64 | compact binary/base64 in `CLIENT_META_DATA` (max 4096 B) | | Path | Client to **all** clients (peer-to-peer, `sendPluginCommand`) | Client to **server** (`CLIENT_META_DATA`) | | Who computes distance | **each client itself** | **the server** (authoritative) | | Trust | Client reports its position (spoofable) | Client reports position (spoofable) — server is still the authority for routing/permissions | ### 5b. "Who hears whom" - **Live V8:** purely client-side. All voice packets arrive; the plugin only makes remote speakers **quieter or muted** (`onEditPlaybackVoiceDataEvent` + `setClientVolumeModifier`). Bandwidth: every packet always flows. - **Plan:** server-side. The `VoiceRouter` builds a target list per speaker and calls `ts3server_setClientWhisperList(A, ..., {B,C})`. Anyone not on the list never receives A's voice at all. > [!WARNING] > While a whisper list is active, that client's normal channel talk path is disabled. > Therefore **everything** must be routed through whisper lists for routed players. ### 5c. Audio effects (cave reverb, lowpass) - **Live V8:** applied directly on the samples in `onEditPlaybackVoiceDataEvent` (`ts3_proximity_audio.c`, lock-free snapshot pattern). - **Plan:** the same logic stays **client-side** (the custom app also has `onEditPlaybackVoiceDataEvent`), driven by a server-provided environment state (NORMAL/CAVE/...). So the effect code is **reusable**. --- ## 6. Operations, deployment, license | Topic | Live V8 | Plan | |---|---|---| | User install | DLL into `%AppData%\TS3Client\plugins\` | Install/run a custom client app | | Server operation | none (retail server is enough) | Own process (Linux/systemd), monitoring, crash recovery | | Build | MSVC + MinGW cross, gcc unit tests | CMake for client + server + shared | | License | free (Plugin SDK) | paid, case by case; evaluation free for dev only | | Lifecycle | as long as TS3 lives | TS3 SDK **EOL 2027-12-31** — keep an `IVoiceTransport` seam as exit | | Scale | callback thread + PCM, ~200 players target | server-side routing, but slot cap per license | --- ## 7. What stays the same / is reusable - **Proximity math** (`proximity_math`, `zone_resolve`) → port into the ServerLib engine (rewrite, don't copy). - **Audio effects** (reverb/lowpass in `ts3_proximity_audio.c`) → usable almost as-is, client-side. - **3D calls** (`systemset3D*`, `channelset3DAttributes`) → nearly identical names. - **Position source** (`pos_file.c`, path detection) → wrap as `IGameDataProvider`. - **HUD / voice modes / config / hub-parser ideas** → carry over as logic. ## 8. What is fundamentally different 1. **Two new processes** (a custom client **and** a custom server) instead of one DLL. 2. **Routing moves from client to server** (whisper lists instead of local volume). 3. **Position path reverses**: no longer peer-to-peer, but client to server. 4. **Retail TeamSpeak disappears** — users need our app. 5. **License and EOL** become new concerns. --- ## 9. Conclusion - Live V8 is a **plugin inside someone else's TeamSpeak**: easy to distribute, free, but "who hears whom" is only a **local volume** (all packets always flow). - The plan is a **standalone voice product** on the TeamSpeak **server** library: **real** server-side routing, at the cost of building a client and server, a paid license, and a 2027 EOL date. - Reusable above all: **proximity math, audio effects, and 3D calls**. New and risky: **ServerLib hosting, whisper routing, data transport (`CLIENT_META_DATA`), and licensing**. > [!IMPORTANT] > The plan only makes sense once the PoC (phases 0-3) proves that the ServerLib starts > cleanly and whisper routing "A hears B, not C" actually works. Before that, no Conan code > is worth writing.