Skip to main content

Voice support contract

Talos provides gateway signaling, media adapter lifecycle management, and optional LiveKit audio publishing, receiving, browser playback, E2EE, and automatic rejoining through talos-fluxer/voice. Install livekit-client separately; the core SDK has no runtime media dependencies. The bundled media integration targets the browser LiveKit SDK. Node/Bun applications can supply a transport backed by their media library; Talos does not bundle native WebRTC, FFmpeg, or a Node audio decoder.

Connections and recovery

VoiceConnection owns one transport. A gateway interruption, matching server leave/move, replacement grant, or terminal media disconnect closes it. Its active serialization field describes lifecycle; use state for transport connectivity. Custom adapters without health events cannot provide a media health guarantee. VoiceSession owns successive connections. Recovery defaults to five total rejoin attempts over the session’s lifetime, delays starting at 1 second and capped at 30 seconds, and a 30-second wait for gateway readiness per attempt. Each rejoin also has the join deadline. Successful rejoins do not reset the total budget. maxAttempts: 0 disables rejoining. Subscribe to retry, connection, state, and error to observe recovery. The connection getter changes after each rejoin. A server leave/move or explicit disconnect ends the session permanently; recovery does not undo a moderation action or move the connection back. A replacement grant causes cleanup and a fresh join to the configured channel, rather than reusing the old transport/token/key. Talos does not refresh expiring tokens proactively or continue an in-progress player, recording, or application-owned published stream across a new connection. Use the connection event to rebuild these resources. VoiceSession defaults to selfMute: true; explicitly enable the microphone when wanted. Operations while recovering reject until a connection is available. The initial caller signal/deadline stops applying after a successful join, including for a session. Use disconnect() to stop an established connection or session. Session disconnect aborts backoff, gateway waits, and pending joins. Cleanup errors from automatic shutdown or late transports are also reported through the gateway’s error event. Adapter cleanup must settle for shutdown to finish. Only one join may be pending per gateway/channel. Applications own established connections and must close them. Leave commands are best effort. Before a grant arrives, Talos has no connection ID to leave. Grants have no client attempt ID, so delayed grants after cancellation cannot be reliably distinguished from a new attempt for the same channel. Automatic rejoining inherits this protocol limit.

LiveKit setup

This example assumes a ready GatewayClient and a browser/bundler supported by livekit-client (the integration was typechecked with 2.22.3). The factory must construct the encrypted room with the same provider and worker it returns. If allocation fails inside the factory, the factory must dispose its partial resources.
For an unencrypted room, omit encryption. Encrypted grants will then be rejected. The setup follows LiveKit’s encryption guide and Fluxer’s shared-key initialization. Encryption and codecs are implemented by LiveKit and the runtime. Talos does not verify other participants’ encryption configuration or add encryption to signaling.

Publishing and queued file playback

The player fetches and decodes each complete source into memory. Browser-supported formats, CORS, and autoplay policy apply. It does not transcode unsupported formats, stream arbitrarily large files, or expose raw codec packets. A per-item abort signal cancels that item; player.disconnect() cancels active and queued items and waits for cleanup. A voice connection closing also closes its player. Transient media reconnection interrupts the active item; callers decide whether to enqueue it again.

Receiving and recording

Remote participant IDs are LiveKit identities, not necessarily bare Fluxer user IDs. Read audioTracks immediately after joining for already-subscribed tracks; then listen for additions/removals. Omit audioOutput when managing elements or recording yourself. Local deafening disables the exposed native tracks, including audio consumed by recordings; it does not unsubscribe network traffic.
Recording uses native MediaRecorder; Talos does not bundle a recorder, PCM frame processor, or storage policy. Consumers own their manually attached elements, recorders, and streams and must dispose them on removal or connection close.

Custom adapter responsibilities

connect(grant, { signal, selfMute?, selfDeaf? }) must create a separate transport for each attempt, configure security and explicit initial media preferences, and resolve only when ready. Honor cancellation and clean up partial allocations before rejecting. Talos disconnects transports returned after a cancelled join. All media methods and health events on VoiceTransport are optional, preserving signaling-only adapters. Unsupported controls/publishing reject explicitly. Implement on to expose track, speaker, error, and health events; an emitted terminal disconnected ends the connection. audioTracks supplies the initial snapshot. Advertise supportsE2EE: true only if the adapter configures the grant’s encryption before returning. Keep grants, tokens, keys, and transports out of diagnostics. VoiceConnection.toJSON() and VoiceSession.toJSON() exclude them.

Validation scope

Unit tests cover signaling, publishing, receiving, initial controls, encrypted setup, cancellation, late media disposal, recovery budgets, and cleanup races. Browser tests exercise native Web Audio decoding, URL fetching, queued publishing, actual audio samples, and resource disposal using an injected SFU boundary. Native audio checks require an audio output backend; CI supplies a PulseAudio null sink. Checks fail when an AudioContext cannot start. They do not establish live Fluxer/LiveKit calls or certify cross-client E2EE interoperability, codecs, or production network recovery. Those require an authenticated deployment and peers.