Voice
A game that joins a room has voice chat with no code of its own. The room forwards each player’s microphone to every other player, and the engine plays what each page hears. Every player starts every session silent: she hears the others at once, and the room gets nothing of her until she holds V or taps the microphone button. This page covers what the player controls, how a game shows who is speaking, and what the engine does to keep voice clear.
The microphone starts silent
Section titled “The microphone starts silent”Each session starts with the player’s microphone off, in every game and every room, as PC action games start a player on push to talk and the EU’s guidance for minors asks of a microphone. Nothing keeps a live microphone across a reload, a rejoin or a move to another game: what persists is her voice mode and her talk key.
- On a keyboard, she holds V to talk, and the microphone button shows “Hold V”. The key is hers to change in Settings, and open mic, the other voice mode, makes a press of the key turn her microphone on and another turn it off.
- On a touch screen, a tap on the microphone button turns her microphone on, and another turns it off.
- On any device, a tap or a click on the button turns it on and off, so a desktop player on push to talk can also leave it on.
The room stops her sending when the page is hidden, and when she leaves the room. A key held as the page loses the focus counts as let go.
A gamepad has no talk button yet. The engine has no gamepad bindings to check one against, so a button it took could also fire a game’s own use of it, and turn a microphone on by accident. It comes with the gamepad bindings the engine plans for 0.2.0.
When the microphone is asked for
Section titled “When the microphone is asked for”The engine asks the browser for the microphone at the player’s first press of the microphone button or her talk key, never as she joins. That press sends nothing: a browser that answers late never turns her on by itself, so she presses again to talk. A browser that already allows the microphone for the site, as one that remembers her answer does, needs no ask, and her first press talks. A game with no room never asks.
A player who refuses still hears everyone else. The button shows “Mic blocked”, and once she hears another player, a one-line notice in the bottom-left corner says for ten seconds that the others cannot hear her, and that Escape says how to fix it; a player alone in a room sees none, since nobody is missing her voice. The Escape menu and the Microphone row in Settings say how to allow the microphone from the address bar’s site controls. When the player allows the microphone there, voice takes it with no reload, the next time she presses.
What the player sets
Section titled “What the player sets”The Audio tab of the Settings panel holds the voice rows. The rows past Microphone show once the room opens voice.
| Row | Range | Default | What it changes |
|---|---|---|---|
| Microphone | On or off | Off | Whether the room gets her voice while she speaks, as a tap on the microphone button does. Off at the start of every session. |
| Voice mode | Push to talk, Open mic | Push to talk | What her talk key does: held to talk, or pressed to turn her microphone on and off. Not shown on a touch screen. |
| Talk key | Any key the game leaves free | V | The key she holds to talk, or under open mic presses. Not shown on a touch screen. |
| Microphone volume | 0 to 100 | 100 | The microphone’s level before it is sent. A live meter under it shows the level. |
| Noise suppression | On or off | On | The browser’s removal of steady noise, such as a fan. |
| Automatic gain | On or off | On | The browser’s leveling of the voice. It can change the system microphone level. |
| One row per player | 0% to 200%, and a mute button | 100% | How loud that player is to this player alone. Past 100% plays them louder. |
The meter under Microphone volume shows the microphone’s level while it is captured, and turns green while the room gets the player’s voice, so the player sees she is heard. Discord shows an input meter in its voice settings, and Roblox animates the microphone icon while the player speaks.
The voice mode and the talk key hold across visits and, in the player app, across games. Her microphone’s on or off does not: every session starts it off.
The two voice modes
Section titled “The two voice modes”Push to talk is the default. It sends while the player holds her talk key, and for 0.1 s after. A key that a text field or a button takes does not count, and the page does not hear a key while the browser tab is out of focus. After her first press, the page keeps her microphone captured and sends nothing until she holds the key, so the key sends at once.
Open mic makes a press of the talk key turn her microphone on and another turn it off. While it is on, a gate sends her while she speaks and holds a quiet room back: it opens the moment the microphone’s level passes -60 dBFS, 20 ms early so the first sound goes through, and holds for 0.3 s after the last loud moment so pauses and quiet word endings go through. The threshold is fixed, and no row shows it. After the browser’s noise suppression, a pause between words measures about -79 dBFS and speech about -26 dBFS, so the gate sits well clear of both. A microphone turned on with the button runs through the same gate in either mode.
The device’s main input decides what the player sees, never the window’s size: a narrow desktop window keeps the key, and a touch screen shows neither the mode nor the key, since a tap on the button turns the microphone on and off.
A page that saved the older ungated open mic starts on the gate at its lowest threshold, -80 dBFS, which sends as much as it did; Reset audio moves it to the shipped threshold. A page saved before push to talk was the default, when every save held open mic and V, starts on push to talk and the game’s talk key; a mode or a key it saved that differs from those old defaults stays.
How the threshold was measured
Section titled “How the threshold was measured”Two players in one room, each page running the engine’s own gate on a steady tone from its microphone, the other page measuring what it plays after the room and the Opus codec. At -60 dBFS, a tone at -61 dBFS or quieter arrives as silence, 1 in 32,768, and a tone at -59 dBFS or louder arrives at its own level. A tone at -26 dBFS, speech after the browser’s gain control, arrives whole. The hosted room’s voice tests keep that check: speech passes, a quiet room at -70 dBFS does not, and the same quiet room passes at the lowest threshold.
The same -60 passed every sentence of a recorded speech test and the first 100 ms of 89 to 100% of them, where -50 cut into the quiet first sound of one sentence in five.
What the others do
Section titled “What the others do”| Product | Modes listed | Default | Where the player mutes | Notes | Source |
|---|---|---|---|---|---|
| Roblox | None: the microphone on or off | Open mic with a mute, on every device | A microphone icon in the fixed top-left controls, always on screen | A creator cannot hide or move it; SetCoreGuiEnabled covers other pieces. Green moves in the icon while the player speaks, a red slash while muted, and a speaker icon shows over others’ heads. |
Roblox help 4405807645972; the developer forum’s “Updated Experience Controls now live” |
| Fortnite | Open Mic, Push to Talk | Not published | The voice controls in the profile panel | Settings are saved to the Epic account. The proximity speaker HUD element is mandatory, and the HUD Controller device cannot hide voice. | Epic help a000084783; Epic’s HUD Controller device page |
| Valorant | Party: push to talk or voice activation. Team: push to talk only | Push to talk, U and V | Not listed | Riot support, Valorant voice chat troubleshooting | |
| Overwatch 2 | Push to Talk, Open Mic | Push to talk | The Social menu | Blizzard help 31694, behind a login: unconfirmed | |
| Apex Legends | Push to Talk (PC only), Open Mic | Push to talk on PC | Not listed | Its “Open Mic” is a threshold gate, with an Open Mic Record Threshold slider. | EA help, Apex voice chat; EA’s Apex accessibility page |
| Call of Duty | Push to Talk, Open Mic | Not listed; the PC key is V | Not listed | Open Mic Sensitivity runs 0 to 100. | Activision support, Black Ops Cold War accessibility |
| Rocket League | Open mic, with a Push to Talk checkbox | Open mic | Not listed | Epic help a202300000015902 | |
| Rec Room | Push-to-Talk, Always On | Not listed | Not listed | Rec Room’s help center: unconfirmed | |
| VRChat | Push to Talk, Toggle, Always On | Push to talk on iOS | The V key or the Quick Menu | A microphone threshold, 5% by default, and a HUD microphone icon the player can drag. | VRChat’s iOS platform FAQ; its 2025.2.1 and 2026.2.2 release notes |
| Call of Duty: Mobile | Toggle button, push to talk, always on | Muted | An always-visible HUD microphone button | Activision’s blog, October 2019 controls post | |
| Discord | Voice Activity, Push to Talk | Not listed | Not listed | Voice activity takes a slider or an automatic sensitivity, which Discord itself says cuts sentence ends. Its Social SDK defaults to -60 dB. | Discord support 211376518; Social SDK |
| Xbox and PlayStation 5 | None: an open mic with a mute | Open mic | The PS5 controller’s mute button; the Xbox headset’s button | Xbox offers a noise suppression switch. | Xbox Wire, 2022-09-07; PlayStation’s DualSense support |
| Browsers | Not a product with modes | Echo cancellation, noise suppression and automatic gain on | Not applicable | Opus sends no discontinuous transmission unless asked. | W3C mediacapture-main, on Opus DTX; RFC 7587 |
Eight products list exactly two modes, three list a mute only, and two list three; none lists an ungated open mic and a gated voice activity as separate choices. Of the gates that publish a default, Discord’s SDK sits at -60 dB and VRChat at 5%, about -29 dBFS if that share is linear, which VRChat does not document. The engine ships -60 dBFS, fixed rather than adaptive, since Discord’s automatic sensitivity cuts sentence ends.
The talk key
Section titled “The talk key”The talk key is the engine’s pushToTalk action in the game’s key table, on V, the key Valorant’s team voice and Roblox’s own push-to-talk example use:
- A game that binds V fails as its world is made, in development and in the room, with an error that names the game’s binding and the fix: give the binding another key, move the talk key with
controls({ keys: { pushToTalk: ["KeyT"] } }), or drop it withcontrols({ keys: { pushToTalk: [] } }). That is the earliest a clash can be known, before any player presses V. The same check covers every key the game’s plugins bind, for an input, a command or a page action. - A game that drops the talk key leaves voice to the microphone button, and to a key a player chooses for herself.
- A player’s own key replaces the game’s in Settings. The row refuses a key the game binds, naming whose it is, and keeps listening for another. A key she chose in another game that this game binds stays the game’s here: she talks on the game’s talk key, and the row says so.
The ability bar’s keys are its own prop, outside the key table, so the bar checks them itself as it mounts: a bar on the talk key throws in development, naming the same fixes, and logs the error in a deployed room. Her talk key never takes a key the bar holds, and the Talk key row refuses one.
The microphone button
Section titled “The microphone button”A game with voice shows the engine’s microphone button in the top-left corner, 44 px tall, on every device, beside the Menu button on a touch screen and before the performance overlay when it shows. The button shows what the microphone does now and how the player changes it, with her talk key named, as Valorant and VRChat name theirs. A tap or a click turns the microphone on and off, as the Microphone row does, the first asking the browser for it.
| State | Words on a keyboard | Words on a touch screen | What it means |
|---|---|---|---|
| Ready | Hold V | Not shown | Push to talk: silent until she holds her key. |
| Talking | Talking | Not shown | She holds her key: the room gets her voice. |
| Live | Mic live, V to mute | Mic live, tap to mute | Her microphone is on. On push to talk the keyboard words say click. |
| Muted | Muted, V to talk | Muted, tap to talk | Off, with a red slash. A game with no talk key says click. |
| Asking | Allow the mic | Allow the mic | The browser is asking her. |
| Blocked | Mic blocked | Mic blocked | She, or the browser, refused it; the hover text says how to allow it. |
| No microphone | No mic found | No mic found | The browser found none. |
| Voice off for a phase | Voice off | Voice off | The game has voice off for now: slashed, and it takes no tap. |
| Development room | No voice in dev | No voice in dev | The development room runs no voice: it takes no tap. |
Her level fills the microphone in green while the room gets her voice. With the Escape menu open, the button stays in its corner above the menu, so a player in a game that captures the cursor reaches it there. Roblox keeps its microphone in the same corner.
The button shows once the room opens voice, or once it says it runs none, as the development room does. It is the one place every player sees whether she is heard in every game, so a creator cannot hide or move it:
- Restyle it through the theme’s colours: the button takes the menu’s
--color-menu-surfaceand--color-menu-edge, and its own--color-voice-speakingfor the level and--color-voice-mutedfor the slash. A game sets them on:rootin its stylesheet, as the menus take theirs. - Remove it by turning voice off for the game, with
defineRoom({ voice: false }), as Turning voice off says. The page then asks for no microphone, shows no voice rows and draws no button. - Start players on open mic with
defaultVoiceMode={VoiceMode.OpenMic}onGame, besidedefaultVolumes. Her microphone still starts off: open mic changes what her key does, not whether she starts heard. A mode the player saved on this device keeps its value, and Reset audio returns to the game’s.
<HideSetting setting="microphone" /> hides the voice rows in Settings and leaves the button.
Drawing a game’s own microphone button
Section titled “Drawing a game’s own microphone button”useVoice holds the player’s microphone as the engine’s button reads it, and a game reads it to draw its own:
muted: whether her microphone is off. It istrueat the start of every session.pushing: whether she holds her talk key now.armed: whether she has pressed her talk key this session, which keeps her microphone captured.microphone: whether the room takes her microphone at all.
useVoiceSession‘s access says what the browser answered, talking whether the room gets her speech now, and off whether the game has voice off. useSettings’ voiceMode and pushToTalkKey say what her key does and which key she chose. useVoice is read-only: no call a game makes turns a player’s microphone on, since only her own press may. A game’s own button sends her to the engine’s, or to Settings, to turn it on.
What the other engines give a creator
Section titled “What the other engines give a creator”| Engine | Starts silent | Push to talk | What a creator controls |
|---|---|---|---|
| Spawnite | Every session, on every device, for every game | V, the engine’s pushToTalk action; open mic as a setting; a tap on the button on touch |
Move or drop the key with controls(), start on open mic, restyle the button, turn voice off |
| Roblox | Voice is off until the player opts in, after an age check | None built in: AudioDeviceInput.Muted toggled from a creator’s input, V in Roblox’s own example |
Turn voice on or off per experience, and mute a player’s AudioDeviceInput from a server script |
| Unity with Vivox | The developer’s choice | The developer’s own: SetChannelTransmissionModeAsync and MuteInputDevice |
Everything, since the developer builds the controls |
| Unreal with EOS | The developer’s choice | The developer’s own: the player controller’s StartTalking, StopTalking and ToggleSpeaking |
Everything, since the developer builds the controls |
| Godot | No voice is built in | No voice is built in | Not applicable |
Unity, Unreal and Godot leave the default to each game. Roblox, a platform whose players move from game to game as this one’s do, decides it once for every experience, and so does this engine: a player knows in every game that she is silent until she presses. Two controls the others give are left out: a creator cannot start players with a live microphone, and cannot turn one on from code, since either would take the choice from the player.
Turning voice off
Section titled “Turning voice off”A game turns voice off for the whole game, or for a phase of play such as a round where only the lobby talks. The room enforces both: a page that keeps sending reaches nobody.
For the whole game
Section titled “For the whole game”Set voice: false in the game’s defineRoom:
import { defineRoom } from "@spawnite/engine/core";
export const room = defineRoom({ maxPlayers: 8, voice: false });The room starts no voice, and no page asks for the microphone, loads voice’s code, opens voice’s connection or draws the microphone button. spawnite build writes voice: false into the bundle’s manifest, so the store page can say the game has no voice.
For a phase of play
Section titled “For a phase of play”Call setVoice(step, { enabled }) from an authoritative system. The following plugin keeps voice on in the lobby and off while a round plays:
import type { World } from "koota";import { createQuery, definePlugin, setVoice, type AuthoritativeStep,} from "@spawnite/engine/core";import { RoundMachine, RoundTrait } from "@spawnite/engine/rounds";
const playing = createQuery(RoundTrait, RoundMachine.is.playing);
/** Voice in the lobby alone: off while a round plays, on between rounds. */function voiceInLobby(world: World, step: AuthoritativeStep) { setVoice(step, { enabled: world.queryFirst(playing) === undefined });}
export const lobbyVoice = definePlugin({ name: "lobby-voice", // No runsOn: voice is the room's call, and a game played alone has none. systems: { rules: { voiceInLobby } },});When voice turns off, the room pauses every microphone, including one that is speaking, and tells each page. Each page stops capturing the microphone and shows voice as off: the microphone button shows a slash and takes no tap, and the Microphone row in Settings says voice is off for now. A player who joins while voice is off joins with it off. When voice turns on again, each microphone comes back only where its player has it on, so a player who never pressed, or who muted while voice was off, stays silent. Neither control turns a microphone on.
Calling setVoice every step with the same value changes nothing; the room tells the pages only when the value changes. A predicted system cannot call it: the type refuses its step, since a predicted system runs again on the page after a correction. In a game whose room sets voice: false, voice stays off in every phase. A checkpoint keeps the phase, so a recording continues across the change.
Reading voice’s state in a view
Section titled “Reading voice’s state in a view”useVoiceSession’s off says why voice is off, so a game’s own HUD can show it:
VoiceOff.Gamein a game whose room setsvoice: false.VoiceOff.Phasewhile a system has voice off.nullwhile voice runs, and outside a room.
No voice hook throws in a game with voice off. useVoice, useSpeaking and usePlayerVoice read as in a room where nobody speaks.
The spawnite upgrade entry microphone-starts-silent lists what a game that drew its own microphone button changes.
What the other engines give
Section titled “What the other engines give”| Engine | Off for the whole game | Off for a phase |
|---|---|---|
| Spawnite | defineRoom({ voice: false }) |
setVoice(step, { enabled: false }), for every player at once |
| Roblox | Turn voice off in the experience’s Communication settings | Per player, by muting each player’s AudioDeviceInput from a server script |
| Unity with Vivox | Do not join a voice channel | Leave the channel, or mute each player’s transmission |
| Unreal with EOS | Do not create a voice room | Leave the room, or mute each player |
| Godot, PlayCanvas | No voice is built in | No voice is built in |
setVoice is one call for the whole room, because the common case, voice in the lobby alone, is for everyone. Muting chosen players, such as the eliminated in a round, is voice routing, which the engine does not offer yet.
Showing who is speaking
Section titled “Showing who is speaking”useSpeaking(player) returns whether a player is speaking, given her player entity: a player who controls nothing speaks too, since her voice is hers and not her character’s. usePlayerByPresence(character) finds the player whose character a mark hangs over. The page’s own player counts while their microphone sends speech; another player counts while this page plays their speech. It turns on with the first word and stays on for 0.3 s after the last, so a mark drawn from it does not flicker between words.
import type { Entity } from "koota";import { Icon, useSpeaking } from "@spawnite/engine";
function SpeakingMark({ player }: { player: Entity }) { const speaking = useSpeaking(player); return speaking ? <Icon name="mic" className="size-3.5" /> : null;}Holdfast draws it beside each warden’s name in its scoreboard.
Each player’s volume
Section titled “Each player’s volume”Each other player’s row, under People on the Audio tab of Settings, runs from 0% to 200%, and starts at 100%, which plays them as they send. Past 100%, the row adds up to 6 dB, so 200% plays a quiet player at about twice their amplitude. The mute button beside it silences them for this player alone. Discord’s per-user slider has the same range and curve, and Roblox’s per-player volume runs from 0 to 2. The rows last as long as the page is in the room, since a player’s key names someone else in the next room.
The Voice volume row in Settings sets every voice at once, on the same 0% to 200% scale, and the two multiply.
A game that draws its own player list, as Roblox’s list offers each person’s volume, puts the same controls beside each name with usePlayerVoice(player), given her player entity. It reads and sets the values the Settings rows show, and playerVolumeRange holds the row’s bounds:
import type { Entity } from "koota";import { playerVolumeRange, usePlayerVoice } from "@spawnite/engine";
function VoiceControls({ player }: { player: Entity }) { const { heard, volume, muted, setVolume, setMuted } = usePlayerVoice(player); if (!heard) return null; return ( <> <input type="range" {...playerVolumeRange} value={volume} onChange={(event) => setVolume(Number(event.target.value))} /> <button onClick={() => setMuted(!muted)}> {muted ? "Unmute" : "Mute"} </button> </> );}The page plays each voice through a gain of its own on the page’s audio context, then through one limiter so a boosted voice never clips, as LiveKit’s Web Audio mix does. An audio element still plays each voice, muted, because Chrome hands a remote WebRTC stream to Web Audio only while an element plays it. Desktop Chrome’s echo cancellation hears Web Audio’s output as it hears an element’s. A page with no Web Audio plays the element itself, up to 100%.
useVoice holds the player’s microphone, which her presses write and a game reads, as Drawing a game’s own microphone button says. useVoiceSession holds the rest of voice as it runs: the browser’s answer about the microphone, who is speaking, and the player’s volume for each other player.
Voice in development
Section titled “Voice in development”Voice runs on the platform: the rooms a published game plays in forward each player’s microphone. The development room, which spawnite play runs on your machine, runs no voice. It tells each page so, and the page asks for no microphone. The Microphone row in Settings says that voice runs when the game plays on the platform, and useVoiceSession’s unavailable reads true. Both ways to turn voice off work in the development room: off reads as it would on the platform, and the microphone button shows voice as off while a system has it off.
A game’s code needs nothing for this. useVoice, useVoiceSession and useSpeaking work as in any room where nobody speaks: the room never opens voice, nobody is heard, and nobody is speaking.
How the engine keeps voice clear
Section titled “How the engine keeps voice clear”The engine sends Opus in mono at up to 24 kbit/s in 20 ms packets, which keeps a room’s bandwidth low. Three settings keep it clear on a real network:
- In-band forward error correction is on. Each packet carries a rough copy of the one before, so a lost packet is rebuilt rather than guessed. This is the browser’s own default, which the room’s answer used to turn off.
- Discontinuous transmission is on. A silent microphone sends one small packet every 400 ms instead of 50 a second.
- Each player’s audio starts with at least 120 ms in the receiving page’s jitter buffer. WebRTC starts it at 80 ms and lets it shrink to about 20 ms on a quiet link, so the first late packets stutter while it grows back; with stalls of 60 to 120 ms in the first seconds, the floor keeps the first sentence as clear as on a clean link.
The browser’s echo cancellation is always on, so a player on speakers does not send the others their own voices back.