Voice calls
For games (and any app where people talk live). A voice call lives in a realtime channel: create the channel with voice, and the players in it
can talk to each other — a party, a squad, a match, a big gathering. Up to 100 people per call. Up to 8, the players connect to
each other (peer to peer); from 9, the call goes through a KUMODeck relay server (SFU). The channel's size picks the mode for
you, and you use the call the same way in both. It is off until you turn it on, and it needs realtime channels:
kumodeck features on realtimeChannels voice
kumodeck config push --env developmentKUMODeck does not record calls. Voices are only delivered to the listeners; the connection set-up messages are passed on and dropped. KUMODeck keeps only who is in the call right now and when they joined.
Start a call#
const party = await kumo.realtime.create({ voice: true, maxMembers: 4, join: 'invited' });
await party.invite(playerId); // the people who may join (share party.name with them)
// Call join() from a button: the browser asks for the microphone, and audio can only start after a tap or click
await party.voice.join(); // { mic: false } = listen only
party.voice.mute(); // your own microphone
party.voice.unmute();
party.voice.on('speaking', ({ playerId, speaking }) => { … }); // light up who is talking
await party.voice.leave(); // leaves the call, stays in the channelA large call is written the same way:
const hall = await kumo.realtime.create({ voice: true, maxMembers: 60 }); // 9 or more = relay server
hall.voice.mode; // 'sfu'Only a channel created with voice has a call. In any other channel ch.voice is null. The other players join the channel as
usual (kumo.realtime.join(party.name)) and then ch.voice.join(). After a dropped connection the SDK joins the channel
and the call again by itself.
Size and mode#
| How the channel is created | Mode (ch.voice.mode) | |
|---|---|---|
voice: true, maxMembers 8 or less (default 8) | 'p2p' | the players connect to each other. Voices go through a relay, so nobody sees another player's IP address |
voice: true, maxMembers 9 to 100 | 'sfu' | goes through the relay server automatically |
voice: { mode: 'sfu' } | 'sfu' | the relay server even for a small call (maxMembers defaults to 50). For channels where muting has to be certain (see below) |
maxMembers above 100 | — | voice_too_large |
The mode is fixed when the channel is created and never changes during a call.
Rules in a relay-server ('sfu') call
- You hear the 8 people who spoke most recently (not counting you). Nobody can follow more than a few voices at once, and this keeps what each player listens to (and what it costs) from growing with the square of the call's size.
ch.voice.peersis who you hear right now (up to 8), andspeakingarrives for them.ch.voice.membersis everyone in the call. - Mute is enforced by the server. The relay server stops sending the voice of anyone muted by the owner or your server, or not on the speakers list. A modified client cannot hear them either.
- Nobody sees another player's IP address (voices come from the relay server).
State, events and errors#
| State | |
|---|---|
ch.voice.mode | 'p2p' or 'sfu' (read only) |
ch.voice.joined | you are in the call |
ch.voice.muted | your microphone is muted |
ch.voice.canSpeak | others can hear you (false when the owner or your server muted you, or you are not on the speakers list) |
ch.voice.members | who is in the call: [{ id, canSpeak }] |
ch.voice.peers | whose voice reaches you: { playerId, state, canSpeak, speaking, relayed } (in 'sfu', the top 8 you hear right now) |
| Event | Payload |
|---|---|
peer | a connection changed: { playerId, state, canSpeak, speaking, relayed } |
speaking | { playerId, speaking } |
members | the whole list, whenever it changes |
playerJoined / playerLeft | { id, canSpeak } / { playerId } (someone else) |
self | { canSpeak } — you were muted or unmuted |
closed | { reason } — see below |
closed reason | When | The channel |
|---|---|---|
left | you left the call | you stay |
kicked / banned | you were removed from the channel | you leave it |
time_limit | the call passed 4 hours | you stay (and can join the call again) |
suspended | KUMODeck staff stopped voice calls in this channel or game (to deal with abuse, for example). The call cannot be joined until they lift it; text messages keep working | you stay |
unavailable | the call cannot go on right now (your prepaid balance ran out, for example) | you stay |
connection_lost | the connection dropped and could not be restored | — |
| Error | When |
|---|---|
webrtc_unavailable | the browser has no WebRTC |
mic_denied | the player refused the microphone (you can still offer { mic: false }) |
voice_disabled | this channel was not created with voice |
voice_too_large | maxMembers above 100 (above 8 in a channel set to peer to peer) |
voice_suspended | KUMODeck staff have stopped voice calls in this channel or game |
voice_unavailable | relay-server calls are not available right now; try joining again a little later |
voice_not_joined | a call action before join() |
feature_disabled | features.voice is off |
player_required | a player without a player id (see below) |
rate_limited | joined the call again too many times (an hourly limit) |
From your server (secret key)#
Your server can set up a channel with a call: PUT /v1/realtime/channels/@name with { "voice": true }
(and your usual join, send, maxMembers, allow, speakers). The size decides the mode as above;
{ "voice": { "mode": "sfu" } } uses the relay server even for a small call, and { "voice": { "mode": "p2p" } } works only
up to 8 people (voice_too_large from 9). To grow an existing peer-to-peer channel to 9 or more, send
{ "voice": { "mode": "sfu" } } together with maxMembers (maxMembers alone does not change the mode and fails with
voice_too_large). GET shows voice (with its mode) and who is in the call.
The voice setting cannot change while someone is in the call (409 voice_in_use).
In a peer-to-peer channel every voice goes through a relay by default, so players never see each other's IP address. Only your
server can turn that off for a channel with { "voice": { "relayOnly": false } } (players who connect directly cost nothing to
relay). Players under 13 are always relayed. The setting has no effect in a relay-server channel.
Mute, kick and ban#
The channel's rules apply to the call: the owner's (or your server's) mute, a send: 'invited' channel's speakers list,
kick and ban.
- Kick and ban cut the connection itself. The player leaves the channel and the call, and their access to the relay is revoked at once. Players you ban from your game cannot join any channel. A kicked player may join again if the channel lets them in; to keep someone out, ban them.
- How mute works depends on the mode. In a relay-server (
'sfu') call the server stops sending that voice. In a peer-to-peer ('p2p') call KUMODeck tells everyone that the muted player may not speak, and each listener's SDK stops playing that voice. The audio itself still reaches the others, so a modified client could play it. If muting has to be certain (calls with strangers, for example), create the channel withvoice: { mode: 'sfu' }— or kick. - A display name proves nothing. Anyone can pick the same name as another player, or one like "Admin". Show who is the owner or staff from player ids (
ownerId), never from the name. - What players say is your responsibility. Decide who may speak, and keep a way to act quickly: mute, kick, ban. When a call is abused, KUMODeck staff may also stop voice calls, and only voice calls, in a channel or a game (
suspended).
Limits#
| People per call | 100. Peer to peer: up to 8 (maxMembers defaults to 8 with voice: true, 50 with { mode: 'sfu' }) |
| Who you hear | peer to peer: everyone in the call. Relay server: the 8 who spoke most recently, not counting you |
| One call | 4 hours, then closed with time_limit. Players stay in the channel and can join the call again |
| Who can join | players with a player id (the default — the SDK signs players in as guests). Not in open channels |
| Channels | only @ channels created with voice (by a player or your server). Not channels without @ |
When your prepaid balance runs out, new calls cannot start and people in a call are taken out of it (closed with
unavailable), like the other player-facing features.
Cost#
Charged as KUMODeck's usage fee (at cost), from your prepaid balance. What costs is the audio delivered to each listener. Silent players send almost nothing (the SDK sends no audio during silence), and peer-to-peer pairs that connect directly cost nothing.
Peer to peer (up to 8):
| For 1 hour | One person talks at a time | Everyone talks all the time |
|---|---|---|
| 2 people | about 0.13 cents | about 0.26 cents |
| 4 people | about 0.4 cents | about 1.5 cents |
| 8 people | about 0.9 cents | about 7.2 cents |
Relay server (9 or more). Each player hears at most 8 others, so the cost grows roughly in line with the number of people:
| For 1 hour | About 2 people talking at once | Everyone talks all the time |
|---|---|---|
| 16 people | about 4 cents | about 16 cents |
| 50 people | about 13 cents | about 51 cents |
| 100 people | about 26 cents | about $1 |