18 KiB
Live Plugin (V8) vs Planned SDK/ServerLib Architecture
A development-ready side-by-side comparison of:
| Document side | What it is |
|---|---|
| Live V8 | Shipping Conan Exiles proximity plugin (tsmain): a DLL loaded by the retail TeamSpeak 3 client |
| Plan (Option B) | New product on branch ts3-Testing: custom ClientLib app + custom ServerLib voice server |
This file answers: which API does each use, in what order, who sends data to whom, and what actually changes.
Table of contents
- One-sentence difference
- At-a-glance table
- The two SDKs explained
- Architecture diagrams
- Lifecycle: boot order (Live V8)
- Lifecycle: boot order (Plan)
- API map: every callback
- API map: every active call we make
- Data flow: position
- Data flow: who hears whom
- Data flow: 3D and audio effects
- Threading models
- Module map
- Operations, license, scale
- What is reusable vs rewritten
- Conclusion
1. One-sentence difference
Live V8 is a guest DLL inside retail TeamSpeak that edits voice locally.
The plan is our own TeamSpeak-compatible voice stack where a server decides who receives whose packets.
Same English word "SDK". Two different TeamSpeak products. Not an upgrade — an architecture switch.
2. At-a-glance table
| Topic | Live V8 (today) | Plan (Option B) |
|---|---|---|
| Product shape | One DLL (conan_exiles.dll) |
Client app + server process |
| SDK product | Client Plugin SDK (free) | TeamSpeak 3 SDK ClientLib + ServerLib (paid) |
| Headers | sdk/include/ts3_functions.h |
clientlib.h, serverlib.h |
| Function prefixes | ts3plugin_*, TS3Functions.* |
ts3client_*, ts3server_* |
| Voice host | Foreign retail ts3server |
Our conan-voice-server |
| User runs | TeamSpeak 3 Client + our plugin | Our Conan Voice Client app |
| Who hears whom | Client PCM mute/gain | Server ts3server_setClientWhisperList |
| Position path | Peer-to-peer sendPluginCommand("CEPOS:…") |
Client → server CLIENT_META_DATA |
| Packet bandwidth | All channel members always receive voice | Only audible targets receive voice |
| License | Free | Paid (eval free for development) |
| EOL risk | Tied to TS3 client | TS3 SDK EOL 2027-12-31 |
| Branch | tsmain |
ts3-Testing (isolated) |
3. The two SDKs explained
3.1 Live V8 — Client Plugin SDK
Evidence in this repo:
# TeamSpeak 3 Client Plugin SDK
Welcome to the official helper repository for creating native plugins for the TeamSpeak 3 Client.
- TeamSpeak loads our DLL and calls exported
ts3plugin_*functions. - We call back into TeamSpeak through
struct TS3Functionsfilled byts3plugin_setFunctionPointers. - We cannot create a virtual server, set server-side whisper routing, or replace TeamSpeak.
- Proximity = edit PCM samples in
ts3plugin_onEditPlaybackVoiceDataEvent.
There are zero ts3client_* / ts3server_* symbols in this repository.
3.2 Plan — TeamSpeak 3 SDK (ClientLib + ServerLib)
- Separate commercial download (not the Plugin SDK folder in this repo).
- We link ServerLib into
conan-voice-serverand ClientLib into our custom client. - Retail TeamSpeak cannot connect to an SDK ServerLib server (community-confirmed error
0x0705). - Users install our client app (similar to how Mumble works), not TeamSpeak + a plugin.
Plugin SDK (today) TeamSpeak 3 SDK (plan)
───────────────── ──────────────────────
ts3plugin_* ts3client_* (client app)
TS3Functions.* ts3server_* (our server)
retail TS loads DLL we host ServerLib ourselves
local PCM mix server whisper-list routing
4. Architecture diagrams
4.1 Live V8
sequenceDiagram
participant Game as Conan Mod
participant Plugin as Plugin DLL
participant Peer as Other Plugin
participant PCM as Playback PCM
participant Retail as Retail ts3server
Game->>Plugin: Pos.txt (x,y,z,yaw)
Plugin->>Retail: sendPluginCommand("CEPOS:...")
Retail->>Peer: relay plugin command
Peer->>Peer: player_table_put + proximity_math
Retail->>Plugin: voice packets from Peer
Plugin->>PCM: onEditPlaybackVoiceDataEvent gain/pan/filter
4.2 Plan (Option B)
sequenceDiagram
participant Game as Conan
participant Client as Conan Voice Client
participant Server as Conan Voice Server
participant Router as VoiceRouter
Game->>Client: IGameDataProvider position
Client->>Server: CLIENT_META_DATA + flushClientSelfUpdates
Server->>Server: PlayerManager.updatePos
Router->>Router: spatial hash -> audible set
Router->>Server: ts3server_setClientWhisperList(A,{B,C})
Note over Client,Server: A speaks - only B and C receive packets
Client->>Client: 3D attributes + optional local effects
5. Lifecycle: boot order (Live V8)
Retail TeamSpeak owns the process. Our plugin only hooks in.
1. TeamSpeak loads conan_exiles.dll
2. ts3plugin_name / version / apiVersion / author / description
3. ts3plugin_setFunctionPointers(TS3Functions) -> ts3_adapter_set_functions
4. ts3plugin_registerPluginID(id) -> needed for sendPluginCommand
5. ts3plugin_init()
6. onConnectStatusChangeEvent(STATUS_CONNECTION_ESTABLISHED)
7. Runtime loop (callback thread + CEDRAIN + PCM):
- pos watcher reads Pos.txt
- CEPOS send/receive via onPluginCommandEvent
- proximity recompute on callback thread
- onEditPlaybackVoiceDataEvent applies mix
- systemset3DListenerAttributes / channelset3DAttributes
8. ts3plugin_shutdown() -> ts3_adapter_shutdown
Key files:
| Step | File |
|---|---|
| Exports | src/ts/entry/ts3_exports.h, ts3_entry.c |
| TS API wrapper | src/ts/adapter/ts3_adapter.c |
| CEPOS | src/ts/proximity/ts3_cepos.c |
| PCM mix | src/ts/proximity/ts3_proximity_audio.c |
| 3D | src/ts/proximity/ts3_3d.c |
| Position file | src/core/mod_file/pos_file.c |
6. Lifecycle: boot order (Plan)
6.1 Server process (conan-voice-server)
1. Load config + start logger
2. memset(&ServerLibFunctions, 0, sizeof(...))
3. Assign callbacks (onClientConnected, onClientDisconnected, ...)
4. ts3server_initServerLib(&funcs, LogType_FILE|LogType_CONSOLE, logDir)
5. Create/load server keypair [VERIFY exact name]
6. ts3server_createVirtualServer(port, ip, name, keyPair, maxClients, &serverID)
7. Init PlayerManager, ProximityEngine, VoiceRouter
8. Event loop + routing tick (5-10 Hz)
9. Shutdown:
ts3server_stopVirtualServer(serverID)
ts3server_destroyServerLib()
Confirmed server APIs:
| Function | Role |
|---|---|
ts3server_initServerLib |
Start ServerLib with callback table |
ts3server_createVirtualServer |
Create the virtual server |
ts3server_stopVirtualServer |
Stop one virtual server |
ts3server_destroyServerLib |
Tear down ServerLib |
ts3server_setClientWhisperList |
Core routing API |
ts3server_getClientList |
Enumerate clients |
ts3server_getChannelList |
Enumerate channels |
ts3server_freeMemory |
Free SDK-allocated arrays |
6.2 Client app (conan-voice-client)
1. Fill ClientUIFunctions callbacks
2. ts3client_initClientLib(...)
3. ts3client_spawnNewServerConnectionHandler(0, &scHandlerID)
4. ts3client_createIdentity (or load stored identity)
5. ts3client_openPlaybackDevice / openCaptureDevice
6. ts3client_startConnection(scHandlerID, identity, host, port, nick, ...)
7. Wait onConnectStatusChangeEvent == STATUS_CONNECTION_ESTABLISHED
8. Loop:
- read game position
- setClientSelfVariableAsString(CLIENT_META_DATA, packed)
- flushClientSelfUpdates
- apply 3D listener/speaker attributes
9. Shutdown:
ts3client_stopConnection
ts3client_destroyServerConnectionHandler
ts3client_destroyClientLib
7. API map: every callback
Live V8 — ts3plugin_* (exported)
From src/ts/entry/ts3_exports.h:
| Callback | What V8 does with it |
|---|---|
ts3plugin_init / shutdown |
Start/stop plugin subsystems |
ts3plugin_setFunctionPointers |
Store TS3Functions into adapter |
ts3plugin_registerPluginID |
Required for sendPluginCommand |
onConnectStatusChangeEvent |
Adopt active connection / reset caches |
onPluginCommandEvent |
Receive CEPOS / CEDRAIN / plugin traffic |
onEditPlaybackVoiceDataEvent |
Primary proximity mix (gain/pan/filter) |
onCustom3dRolloffCalculationClientEvent |
Custom distance curve for 3D |
onClientMoveEvent / onClientMoveMovedEvent |
Hub ↔ ingame channel tracking |
onChannelDescriptionUpdateEvent |
Hub settings from root channel description |
onUpdateChannelEvent / onUpdateChannelEditedEvent |
Channel metadata refresh |
onTalkStatusChangeEvent |
Talk indicator / HUD hints |
onHotkeyEvent |
Voice-mode hotkeys |
Plan — ClientLib + ServerLib callbacks
| Side | Callback | Role |
|---|---|---|
| Client | onConnectStatusChangeEvent |
Connection lifecycle |
| Client | onEditPlaybackVoiceDataEvent |
Local effects only (reverb/lowpass) |
| Client | onCustom3dRolloffCalculationClientEvent |
Distance curve |
| Client | onClientMoveEvent |
Channel visibility |
| Client | onTalkStatusChangeEvent |
HUD |
| Server | onClientConnected |
PlayerManager.add |
| Server | onClientDisconnected |
remove + clear whisper list |
| Server | onClientMoved |
channel tracking |
| Server | onClientStartTalkingEvent / Stop |
diagnostics / HUD hints |
| Server | onAccountingErrorEvent |
license/slot errors |
Names marked VERIFY in the plan must be confirmed against serverlib.h / clientlib.h in Phase 0.
8. API map: every active call we make
Live V8 — through TS3Functions (g_ts3.* in ts3_adapter.c)
| Call | Wrapper | Why |
|---|---|---|
sendPluginCommand |
ts3_send_plugin_command_server |
CEPOS positions + CEDRAIN wakeup |
requestUnmuteClientsTemporary |
ts3_unmute_clients_for_pcm |
PCM hook only receives unmuted clients |
setClientVolumeModifier |
(inside unmute path) | Reset volume modifiers |
requestClientMove |
ts3_request_client_move |
Hub ↔ ingame |
requestChannelDescription |
ts3_request_channel_description |
Hub config |
getChannelDescription |
ts3_get_channel_description |
Parse hub settings |
systemset3DSettings |
ts3_set_3d_settings |
3D global scale |
systemset3DListenerAttributes |
ts3_set_3d_listener |
Listener pose |
channelset3DAttributes |
ts3_set_3d_client |
Per-speaker 3D position |
| get channel/client lists, nicknames, printMessage, logMessage | various | channel manage / UI / logging |
Missing in Live V8: any server API that stops packets to non-audible clients.
Plan — SDK calls
| Call | Side | Why |
|---|---|---|
ts3server_initServerLib / createVirtualServer / stop / destroy |
Server | Host voice |
ts3server_setClientWhisperList |
Server | Who hears whom |
ts3server_getClientList / getChannelList / freeMemory |
Server | Enumerate + free |
ts3client_initClientLib / spawnNewServerConnectionHandler |
Client | Host client |
ts3client_createIdentity / startConnection / stopConnection |
Client | Connect |
ts3client_setClientSelfVariableAsString(CLIENT_META_DATA) |
Client | Send position/state |
ts3client_flushClientSelfUpdates |
Client | Push metadata to server |
ts3client_systemset3DListenerAttributes |
Client | Listener 3D |
ts3client_channelset3DAttributes |
Client | Speaker 3D |
ts3client_openPlaybackDevice / openCaptureDevice |
Client | Audio devices |
9. Data flow: position
| Step | Live V8 | Plan |
|---|---|---|
| 1. Game source | Mod writes Pos.txt |
Same idea via IGameDataProvider |
| 2. Read | pos_file.c watcher (~30 ms) |
Game provider thread (~20–30 Hz) |
| 3. Pack | CEPOS 56-byte base64 | Compact binary/base64 in CLIENT_META_DATA (≤4096 B) |
| 4. Send | sendPluginCommand("CEPOS:…") to all clients |
setClientSelfVariableAsString + flushClientSelfUpdates to server |
| 5. Receive | onPluginCommandEvent → player_table_put |
Server reads metadata → PlayerManager.updatePos |
| 6. Distance math | Each client (proximity_math) |
Server (ProximityEngine) |
| 7. Trust | Client-reported (spoofable) | Client-reported (spoofable); server still owns routing/permissions |
Live V8: A ──CEPOS──▶ B,C,D (everyone learns A's position)
Plan: A ──META───▶ Server ──whisper lists──▶ only who should hear A
10. Data flow: who hears whom
Live V8 (local presentation only)
Retail server delivers everyone's voice in the channel
│
▼
onEditPlaybackVoiceDataEvent(clientID, samples[])
│
├─ distance far → multiply samples by ~0 (mute)
├─ distance near → gain + pan + lowpass/reverb
└─ must unmute via requestUnmuteClientsTemporary
otherwise PCM hook gets nothing
Result: bandwidth still paid for every speaker. Hearing is a local volume illusion.
Plan (real network routing)
VoiceRouter tick (5-10 Hz)
for each speaker S:
audible = proximity ∪ radio ∪ whisper ∪ global ∩ permissions
if audible changed:
ts3server_setClientWhisperList(serverID, S, NULL, audibleClientIds)
# or clear with (NULL, NULL)
Result: non-audible clients never receive S's voice packets.
Warning
With an active whisper list, normal channel talk for that client is disabled. Therefore all proximity/radio/whisper/global must be expressed as whisper lists.
11. Data flow: 3D and audio effects
| Concern | Live V8 | Plan |
|---|---|---|
| Listener pose | systemset3DListenerAttributes |
ts3client_systemset3DListenerAttributes |
| Speaker pose | channelset3DAttributes |
ts3client_channelset3DAttributes |
| Global 3D | systemset3DSettings |
ts3client_systemset3DSettings |
| Custom curve | onCustom3dRolloffCalculationClientEvent |
Same idea on ClientLib |
| Cave reverb / lowpass | PCM path in ts3_proximity_audio.c |
Still client PCM; server may send env state (CAVE/NORMAL) |
Portable: 3D call pattern and effect DSP.
Not portable: using PCM mute as the primary "who hears whom" mechanism.
12. Threading models
Live V8 (strict)
| Thread | May do | Must not |
|---|---|---|
| TS callback | All TS3Functions / CEDRAIN drain |
Block |
| Wakeup | Only sendPluginCommand("CEDRAIN:1") |
Other TS API |
| PCM audio | Read snapshots; apply gain/pan | TS API, locks |
| Pos watcher / UI | Set flags + wakeup | TS API |
Plan
| Thread | May do | Must not |
|---|---|---|
| SDK server callbacks | Short handoff into our queues | Heavy proximity math inline |
| Routing tick (5–10 Hz) | Compute audible sets; call setClientWhisperList |
Game file I/O |
| SDK client callbacks | Connection/PCM/3D | Block |
| Game provider (~20 Hz) | Read Conan; publish snapshot | Direct SDK calls without adapter |
Same discipline as V8: one adapter owns all SDK calls.
13. Module map
| Concern | Live V8 files | Plan modules |
|---|---|---|
| Entry / lifecycle | ts3_entry.c |
client/main.c, server/main.c |
| TS API wrapper | ts3_adapter.c |
TeamSpeakClientAdapter, TeamSpeakServerAdapter |
| Positions | pos_file.c, ts3_cepos.c |
IGameDataProvider, CLIENT_META_DATA |
| Player table | player_table.c |
PlayerManager (server) |
| Proximity math | proximity_math.c, zone_resolve.c |
ProximityEngine (server) |
| Hearability | PCM + volume modifier | VoiceRouter + whisper lists |
| 3D | ts3_3d.c |
ClientLib 3D calls |
| Hub config | hub_parser.c, ts3_server_profile.c |
root-channel / server config |
| Voice modes | voice_modes.c |
client state + server ranges |
| UI / HUD | ui_main.c, overlay |
client HUD |
14. Operations, license, scale
| Topic | Live V8 | Plan |
|---|---|---|
| User install | DLL → %AppData%\TS3Client\plugins\ |
Install custom client |
| Server ops | None (use any retail TS server) | Own Linux process, systemd, monitoring |
| Build | MSVC + MinGW cross + gcc tests | CMake: client + server + shared |
| License | Free Plugin SDK | Paid SDK; evaluation for PoC |
| Scale target | ~200 players (512 table cap) | License slot cap + routing cost |
| EOL | TS3 client lifetime | SDK EOL 2027-12-31 |
15. What is reusable vs rewritten
Reuse (rewrite into new adapters — do not blind-copy)
- Proximity math / zones (
proximity_math,zone_resolve) - PCM effects (reverb/lowpass patterns from
ts3_proximity_audio.c) - 3D call sequencing ideas from
ts3_3d.c - Position acquisition ideas from
pos_file.c/ Mumble reference - HUD / voice-mode UX concepts
Must be new
- Entire ServerLib host process
- Entire ClientLib application shell
- Whisper-list router as the hearability authority
CLIENT_META_DATA(or equivalent) transport replacing CEPOS P2P- License/accounting handling
- Deployment (systemd, crash recovery)
Explicitly abandoned for Option B
- Depending on retail TeamSpeak client
- CEPOS peer-to-peer as the primary position bus
- Local PCM mute as the primary routing mechanism
16. Conclusion
| Live V8 | Plan | |
|---|---|---|
| Strength | Ships today; free; users already have TeamSpeak | True server routing; central authority; bandwidth win |
| Weakness | Everyone still receives packets; client can spoof position; no server game logic | New client+server product; paid license; SDK EOL 2027; retail TS gone |
| Right next step | Continue maintaining on tsmain |
On ts3-Testing: prove ServerLib boot + whisper routing before Conan code |
Important
Do not write Conan integration for Option B until Phase 0–3 succeed: SDK examples build → server starts/stops → two clients talk →
ts3server_setClientWhisperListproves A hears B, not C.
Related living plan: .cursor/plans/voice_architecture_plan_ed3d8830.plan.md