Skip to main content
Fluxer’s voice system is built on LiveKit, a scalable WebRTC infrastructure that provides real-time audio, video, and screen sharing capabilities.

Architecture Overview

The voice system consists of:
1

Client Request

User requests to join a voice channel
2

Server Selection

Fluxer selects optimal LiveKit server based on region and availability
3

Token Generation

Server generates a LiveKit token with appropriate permissions
4

WebRTC Connection

Client connects to LiveKit server using token
5

Media Streaming

Real-time audio/video streaming via WebRTC

Voice Regions

Fluxer supports multiple voice regions for optimal latency:

Region Selection

  1. Explicit Region - User/channel has a preferred region set
  2. Automatic Region - System selects based on latency and availability
  3. Fallback Region - Alternative region if preferred is unavailable
// Region preference resolution
const regionPreference = resolveVoiceRegionPreference({
  preferredRegionId: 'us-west',
  accessibleRegions: availableRegions,
  availableRegions: allRegions,
  defaultRegionId: 'us-east'
});

Region Features

id
string
Unique region identifier (e.g., us-west, eu-central)
isAccessible
boolean
Whether the user can access this region
servers
array
Available LiveKit servers in this region

Getting Voice Token

Request Token

POST /channels/{channel_id}/voice/token
Request Body:
{
  "channel_id": "123456789",
  "guild_id": "987654321",
  "region": "us-west",
  "latitude": "37.7749",
  "longitude": "-122.4194"
}
Parameters:
channel_id
snowflake
required
Voice channel ID
guild_id
snowflake
Guild ID (required for guild channels)
region
string
Preferred voice region
latitude
string
User’s latitude for region selection
longitude
string
User’s longitude for region selection
Response:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "endpoint": "wss://voice-us-west.fluxer.app",
  "connection_id": "conn_abc123xyz",
  "token_nonce": "550e8400-e29b-41d4-a716-446655440000"
}
token
string
LiveKit access token (JWT)
endpoint
string
WebSocket URL for LiveKit server
connection_id
string
Unique connection identifier
token_nonce
string
Unique nonce for this token

Voice Permissions

LiveKit tokens include permissions:
interface VoicePermissions {
  canSpeak: boolean;   // Microphone audio
  canStream: boolean;  // Screen sharing
  canVideo: boolean;   // Camera video
}

Permission Calculation

Permissions are based on:
  1. Guild Permissions - User’s role permissions
  2. Channel Overwrites - Channel-specific overrides
  3. Voice State - Server mute/deafen status
// Check voice permissions
const hasSpeak = hasPermission(permissions, Permissions.SPEAK);
const hasStream = hasPermission(permissions, Permissions.STREAM);

Voice State Management

Update Voice State

PATCH /guilds/{guild_id}/voice-states/@me
Request Body:
{
  "channel_id": "123456789",
  "self_mute": true,
  "self_deaf": false
}
channel_id
snowflake
Voice channel ID (null to disconnect)
self_mute
boolean
Self-mute status
self_deaf
boolean
Self-deafen status

Voice State Object

{
  "user_id": "123456789",
  "guild_id": "987654321",
  "channel_id": "111222333",
  "session_id": "abc123def456",
  "self_mute": false,
  "self_deaf": false,
  "self_video": false,
  "self_stream": false,
  "mute": false,
  "deaf": false,
  "suppress": false
}

Server Pinning

Fluxer pins voice rooms to specific LiveKit servers to maintain session continuity:
// Pin room to server
await voiceRoomStore.pinRoomServer(
  guildId,
  channelId,
  regionId,
  serverId,
  endpoint
);

// Get pinned server
const pinned = await voiceRoomStore.getPinnedRoomServer(
  guildId,
  channelId
);

Pin Lifecycle

  1. First User Joins - Room is pinned to a server
  2. Users Connect - All users connect to pinned server
  3. Last User Leaves - Pin is removed after timeout
  4. Region Change - Pin is removed and new server selected

LiveKit Integration

Token Format

LiveKit tokens are JWTs containing:
{
  "sub": "user_123456789_conn_abc123",
  "name": "Username",
  "video": {
    "room": "guild_987654321_channel_111222333",
    "roomJoin": true,
    "canPublish": true,
    "canSubscribe": true,
    "canPublishData": true
  },
  "metadata": {
    "user_id": "123456789",
    "guild_id": "987654321",
    "channel_id": "111222333"
  },
  "exp": 1704067200
}

Room Naming

LiveKit rooms use standardized naming:
// Guild voice channel
const roomName = `guild_${guildId}_channel_${channelId}`;

// DM voice channel
const roomName = `dm_${channelId}`;

// Group DM voice channel
const roomName = `group_${channelId}`;

Participant Identity

Participants are identified by:
const identity = `user_${userId}_${connectionId}`;
This format allows:
  • Multiple connections per user (multiple devices)
  • Easy user identification
  • Connection tracking

Voice Operations

Disconnect User

await voiceService.disconnectParticipant({
  guildId,
  channelId,
  userId,
  connectionId
});

Update Participant

await voiceService.updateParticipant({
  guildId,
  channelId,
  userId,
  mute: true,
  deaf: false
});

Disconnect Entire Channel

const result = await voiceService.disconnectChannel({
  guildId,
  channelId
});

console.log(`Disconnected ${result.disconnectedCount} participants`);

Update Permissions

await voiceService.updateParticipantPermissions({
  guildId,
  channelId,
  userId,
  connectionId,
  canSpeak: false,
  canStream: true,
  canVideo: true
});

Unclaimed Account Restrictions

Unclaimed accounts have limited voice access:
Unclaimed accounts cannot join 1-on-1 voice calls.
throw new UnclaimedAccountCannotJoinOneOnOneVoiceCallsError();
Unclaimed accounts can only join guild voice channels they own.
if (channel.type === ChannelTypes.GUILD_VOICE) {
  const isOwner = guild?.ownerId === userId;
  if (!isOwner) {
    throw new UnclaimedAccountCannotJoinVoiceChannelsError();
  }
}
Unclaimed accounts can join group DM voice calls.

Voice Channel Capacity

Voice channels have user limits:
{
  "code": "VOICE_CHANNEL_FULL",
  "message": "Voice channel is full"
}
HTTP Status: 400 Bad Request

RTC Region Updates

Users with UPDATE_RTC_REGION permission can change a channel’s voice region:
PATCH /channels/{channel_id}
Request Body:
{
  "rtc_region": "us-west"
}
Changing the region disconnects all current voice users. They must reconnect to use the new region.

Voice Quality Settings

LiveKit supports adaptive bitrate and quality settings:
// Client-side quality configuration
const room = new Room({
  adaptiveStream: true,
  dynacast: true,
  videoCaptureDefaults: {
    resolution: VideoPresets.h720.resolution,
    facingMode: 'user'
  },
  audioCaptureDefaults: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true
  }
});

Monitoring and Metrics

List Participants

const result = await liveKitService.listParticipants({
  guildId,
  channelId,
  regionId: 'us-west',
  serverId: 'server-1'
});

if (result.status === 'success') {
  console.log(`${result.participants.length} users in channel`);
}

Participant Object

{
  "identity": "user_123456789_conn_abc123",
  "state": "ACTIVE",
  "tracks": [
    {
      "sid": "TR_abc123",
      "type": "AUDIO",
      "muted": false
    }
  ],
  "metadata": "{\"user_id\":\"123456789\"}",
  "joined_at": 1704067200
}

Error Handling

Voice Errors

try {
  const token = await getVoiceToken(channelId);
} catch (error) {
  if (error.code === 'UNKNOWN_VOICE_REGION') {
    // Invalid or unavailable region
  } else if (error.code === 'VOICE_CHANNEL_FULL') {
    // Channel at capacity
  } else if (error.code === 'FEATURE_TEMPORARILY_DISABLED') {
    // No available voice servers
  }
}

Server Unavailability

When no voice servers are available:
{
  "code": "FEATURE_TEMPORARILY_DISABLED",
  "message": "Feature is temporarily disabled"
}
HTTP Status: 503 Service Unavailable

Best Practices

Handle Disconnections

Implement reconnection logic with exponential backoff for network interruptions.

Monitor Quality

Track connection quality metrics and adapt quality settings based on network conditions.

Graceful Degradation

Reduce quality (resolution, framerate) when bandwidth is limited rather than disconnecting.

Region Selection

Allow users to manually select regions if automatic selection results in poor quality.

Connection Flow

Client SDK Integration

import { Room } from 'livekit-client';

// Get token from Fluxer API
const { token, endpoint } = await getVoiceToken(channelId);

// Connect to LiveKit
const room = new Room();

await room.connect(endpoint, token);

// Enable microphone
await room.localParticipant.setMicrophoneEnabled(true);

// Listen for participants
room.on('participantConnected', (participant) => {
  console.log(`${participant.identity} joined`);
});

// Listen for tracks
room.on('trackSubscribed', (track, publication, participant) => {
  if (track.kind === 'audio') {
    const audioElement = track.attach();
    document.body.appendChild(audioElement);
  }
});

Build docs developers (and LLMs) love