219 lines
11 KiB
Markdown
219 lines
11 KiB
Markdown
# 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.
|