Voice
Varion carries voice over a dedicated voice channel on the transport. The server is the source of truth and the traffic cop: it creates voice channels, holds their membership, relays voice between members of the same channel, and, for spatial channels, culls listeners by distance. The client only controls local playback and capture (volume, its own microphone, device, activation level, noise suppression, local mute); it cannot create channels or move players between them.
Audio is opaque to the server: voice frames are relayed as-is and are never decoded or transcoded. PCM and Opus codecs are supported; the format is 48 kHz, 20 ms frames.
Model
| Side | Responsibility |
|---|---|
| Server | creates and destroys channels, adds and removes players, authoritative speaker mute, priority, channel filter, relay and distance culling |
| Client | local playback volume, muting its own microphone, capture-device selection, activation level, noise suppression, local mute of a single player, reading channel state |
A voice channel is owned by the server resource that created it and is destroyed when that resource stops or restarts — no manual cleanup is needed. A client only starts transmitting after the server adds it to a channel (that channel becomes its active channel). On disconnect a player is removed from all channels automatically.
Server side
Creating a channel
varion.createVoiceChannel(spatial, maxDistance?) returns a VoiceChannel — equivalent to new varion.VoiceChannel(spatial, maxDistance). It must be called in a resource context (otherwise it throws).
spatial = true— a spatial channel: a listener hears a speaker only withinmaxDistance(3D distance from the players' positions).maxDistancedefaults to50and is clamped to1..10000.spatial = false— a non-spatial channel: every member hears everyone, distance is ignored (maxDistanceis0).
const proximity = varion.createVoiceChannel(true, 20); // spatial, 20 m
const team = varion.createVoiceChannel(false); // team-wide, no distanceVoiceChannel properties
| Property | Type | Access | Purpose |
|---|---|---|---|
id | number | read-only | channel id, allocated by the server |
type | 'voiceChannel' | read-only | object type |
valid | boolean | read-only | whether the channel is alive |
isSpatial | boolean | read-only | whether the channel is spatial |
maxDistance | number | read-only | audible radius (0 for a non-spatial channel) |
players | Player[] | read-only | current members as Player proxies |
playerCount | number | read-only | number of members |
priority | number | read/write | priority when channels overlap (defaults to 0) |
filter | number | read/write | channel audio-filter hash (0 — no filter) |
VoiceChannel methods
| Method | Purpose |
|---|---|
addPlayer(player) | add a player to the channel |
removePlayer(player) | remove a player from the channel |
hasPlayer(player) | whether the player is in the channel (alias of isPlayerInChannel) |
isPlayerInChannel(player) | whether the player is in the channel |
isPlayerMuted(player) | whether the player is muted on the server |
mutePlayer(player, muted?) | server mute (defaults to true; false unmutes) |
unmutePlayer(player) | remove the server mute |
setFilter(filterHash) | set the filter (same as filter = filterHash) |
destroy() | destroy the channel |
The player argument accepts a Player proxy or a numeric id.
Static members: VoiceChannel.all (all valid channels) and VoiceChannel.getByID(id) (the channel or null).
Server mute (authoritative)
channel.mutePlayer(player) suppresses that member's outgoing voice for the whole channel — no one else in the channel hears them. This is a server-side, authoritative decision, unlike a local client mute, which affects only what a single player hears. Calling mutePlayer(player, false) is equivalent to unmutePlayer(player). Only a player who is already a member of the channel can be muted.
Priority
If a listener shares more than one channel with a speaker, the server picks a single channel to deliver the voice: first the one with the highest priority; on a tie a non-spatial channel is preferred; on a further tie the channel with the lower id wins. So priority decides whose voice "wins" when channels overlap (for example, a team channel outranks a general proximity channel).
Filters
channel.setFilter(hash) or channel.filter = hash sets an audio-filter hash. The server forwards this hash to clients, and the DSP is applied on the client to that channel's incoming voice. Hashes are computed with varion.hash(name) (JOAAT, lowercase). Built-in presets:
| Name | Effect |
|---|---|
walkietalkie | band-pass filter (radio) |
phone | band-pass filter (telephone) |
underwater | low-pass filter (underwater) |
Any other hash is delivered to clients but has no built-in effect — the voice passes through unchanged. channel.filter = 0 removes the filter.
const radio = varion.createVoiceChannel(false);
radio.setFilter(varion.hash('walkietalkie'));
radio.priority = 10;How the server relays voice
- A client uploads one microphone frame;
VoiceChannelmembership decides who hears it. The client-provided channel id is retained only for wire compatibility — the route is determined by channel membership on the server. - Audio is opaque: the server does not decode frames, it only validates their size and relays them.
- For spatial channels the server culls members beyond
maxDistance. - Voice frames travel over an unreliable transport channel, while channel membership and configuration changes travel over the reliable one.
Example (server)
const proximity = varion.createVoiceChannel(true, 15);
varion.on('playerReady', (player) => {
proximity.addPlayer(player); // the server decides who is in which channel
});
varion.onClient('voice:report', (reporter, targetId) => {
proximity.mutePlayer(targetId); // authoritatively mute them for the whole channel
});
// No need to remove a player on disconnect — they leave all channels automatically.Client side
The client controls local playback and capture through varion.Voice. No client call changes channel membership — only the server does that.
varion.Voice — playback and capture
| Member | Type / signature | Purpose |
|---|---|---|
activeChannel | number (get) | active channel id |
pushToTalk | boolean (get) | whether the PTT key is currently held |
volume | number (get/set) | local playback volume, 0..2 |
muteInput | boolean (get/set) | mute your own microphone |
inputMuted | boolean (get) | alias of muteInput |
activationLevel | number (get/set) | voice-activation threshold, 0..100 |
noiseSuppressionEnabled | boolean (get/set) | noise suppression |
inputDevice | string | null (get/set) | capture-device id |
activityInputEnabled | boolean (get/set) | voice-activation mode (instead of PTT) |
activationKey | number (get) | PTT key |
voiceControlsEnabled | boolean (get) | whether the built-in voice controls are enabled |
toggleInput(enabled) | — | open or close the microphone manually |
getAvailableInputDevices() | { name, uuid }[] | capture devices (uuid is null for the default device) |
Players and mute (client)
| Method | Purpose |
|---|---|
getChannel(channelId?) | client VoiceChannel (defaults to the active one) or null |
getPlayersInChannel(channelId?) | Player[] in the channel (defaults to the active one) |
isPlayerTalking(player) | whether the player is talking right now |
isPlayerMuted(player) | whether the player is muted on the server in the active channel |
isPlayerLocallyMuted(player) | whether the player is muted locally |
mutePlayer(player) | locally silence the player's playback and send varion:voice:mute to the server |
unmutePlayer(player) | remove the local mute and notify the server |
clearLocallyMuted() | clear the local mute list |
A local mute affects only what this client hears; the server mute (isPlayerMuted) is authoritative. Voice.mutePlayer additionally emits the server event varion:voice:mute (channelId, playerId, muted), which a resource can handle via varion.onClient — on its own it is not an authoritative server mute.
Client VoiceChannel (read-only)
Get a channel snapshot through varion.Voice.getChannel(id) or varion.VoiceChannel.getByID(id).
| Member | Purpose |
|---|---|
id | channel id |
spatial | whether the channel is spatial |
maxDistance | audible radius (defaults to 50) |
volume (get/set) | playback volume for this channel, 0..2 |
members | array of member ids |
muted | array of ids muted on the server |
filter | AudioFilter or null |
getPlayers() | Player[] of members |
isActive() | whether this is the active channel |
Static member: VoiceChannel.getByID(id).
AudioFilter
varion.AudioFilter.create(nameOrHash) returns a cached AudioFilter by name (the name is JOAAT-hashed) or by numeric hash. The object exposes a hash field.
Top-level helpers
varion.muteInput(muted = true)— same asVoice.muteInput = ....varion.toggleVoiceControls(enabled)— enable or disable the built-in voice controls.varion.clearLocallyMuted()— same asVoice.clearLocallyMuted().
Example (client)
varion.Voice.volume = 1.2;
varion.Voice.noiseSuppressionEnabled = true;
varion.Voice.activationLevel = 35;
// "Talking" indicator in the HUD
varion.on('voice:playerStartTalking', (player) => hud.setTalking(player.id, true));
varion.on('voice:playerStopTalking', (player) => hud.setTalking(player.id, false));
// Locally silence an annoying player
varion.Voice.mutePlayer(annoyingPlayer);
// Manual push-to-talk on the N key
varion.on('keydown', (key) => { if (key === 0x4E) varion.Voice.toggleInput(true); });
varion.on('keyup', (key) => { if (key === 0x4E) varion.Voice.toggleInput(false); });Voice events
Events arrive on the client; subscribe with varion.on(name, callback). The full event contract is described in Events and RPC — this is only a short summary.
| Event | Arguments | When |
|---|---|---|
playerStartTalking / voice:playerStartTalking | (player) | a player started talking |
playerStopTalking / voice:playerStopTalking | (player) | a player stopped talking |
voice:channelChange | (previousChannelId, activeChannelId) | the active channel changed |
voice:channelCreate | (voiceChannel) | the client learned about a new channel |
voice:channelDestroy | (channelId) | a channel went away |
voice:pushToTalk | (active) | the push-to-talk state changed |
Related pages
- Server JavaScript API — the "Voice Channels" section of the full reference.
- Client JavaScript API — the complete client
varionobject. - Events and RPC — exact signatures for every event.
Source backing
This page is verified against sdk/Varion.Server/npm/@varion/server/index.d.ts (the VoiceChannel class and varion.createVoiceChannel), the Node-host runtime server/host/varion-host.cjs, the server relay server/src/CVoiceManager.{h,cpp}, and the client runtime sdk/client-js-runtime/injected/services-bootstrap.js (the Voice object and the VoiceChannel and AudioFilter classes). The voice event list is in Events and RPC.