Skip to content

Socket.io events ​

The primary channel. Radio state is broadcast here and nowhere else — there is no REST endpoint that returns it.

Connecting ​

js
import { io } from 'socket.io-client';
const socket = io('http://localhost:3001', { withCredentials: true });

Your client's origin must be listed in CLIENT_ORIGIN on the server, or the connection will not be established.

What arrives immediately ​

The server sends these on its own, without any request:

  • sync — the full radio state;
  • radio_hosts_mode — whether live hosts are enabled;
  • usersUpdate — active listeners;
  • radio_hosts_online — only when host mode is enabled.

To obtain an artToken the client must send listener_init — see Build a client in 15 minutes.

Reading the "Sent to" column ​

ValueMeaning
everyonebroadcast to all connections
single socketaddressed to one client
room …only to members of a room, e.g. live hosts

Your client will not receive room events until it joins the corresponding role — for an ordinary listener that is expected.

Reserved events ​

Socket.io's own events — connect, disconnect, connect_error and so on — are not listed. They behave as usual and are documented by Socket.io itself.

message and error are filtered out for the same reason: in this codebase they belong to the WebRTC monitoring UDP socket, which also happens to be named socket, not to the Socket.io connection.

How this page is maintained ​

The event list is generated from the server source, so it is always complete. Descriptions are written by hand in reference/annotations/events.mjs and translated in reference/annotations/en/events.mjs; anything untranslated falls back to Ukrainian rather than going missing.

Describing an event that does not exist in the code makes npm run check fail — that is how renames get caught.

Client → server: 42 events. Server → client: 37 events. Documented in detail: 79 of 79.

Client → server ​

Events the client sends.

EventDescription
admin_activeUpgrades the connection to an admin socket and confirms the session.
admin_add_songAdds a track from the library to the queue.
admin_go_livePuts an admin on air as a host.
admin_guest_actionApproves or rejects a guest request.
admin_insert_song_groupInserts a predefined group of songs into the queue.
admin_leave_liveEnds the host's broadcast.
admin_remove_songRemoves a track from the queue by position.
admin_skip_songSkips the current track.
admin_suggestion_actionAccepts or rejects a listener's song request.
get_queueA page of the upcoming queue.
guest_check_banWhether this IP address is banned from the broadcast.
guest_connectConnects an approved guest to the broadcast.
guest_leave_liveThe guest leaves the broadcast.
guest_requestA listener asks to join the broadcast.
host_audio_chunkA chunk of microphone audio to mix into the broadcast.
host_get_background_music_listA page of available background music.
host_guest_kickRemoves a guest from the broadcast.
host_guest_muteMutes a guest's microphone.
host_mic_gainChanges your microphone level in the mix.
host_mic_toggleTurns the on-air microphone on or off.
host_pause_queuePauses the track queue — "just chatting" mode.
host_resume_queueResumes queue playback.
host_set_background_musicChooses the track that plays while the queue is paused.
listener_initRegisters the connection as a listener and asks for an art token.
moderator_ban_ipBans an IP address manually.
moderator_ban_participantBans a broadcast participant by IP.
moderator_deactivate_guest_codeDeactivates the current access code.
moderator_generate_guest_codeCreates a special-guest access code.
moderator_get_banlistThe list of banned IP addresses.
moderator_get_guest_codeThe current special-guest access code.
moderator_get_live_rosterWho is on air right now.
moderator_kickRemoves a participant from the broadcast.
moderator_muteMutes any participant of the broadcast.
moderator_regenerate_guest_codeReplaces the current access code with a new one.
moderator_unban_ipLifts a ban on an IP address.
monitor_answerWebRTC answer for the host's personal monitoring channel.
monitor_ice_candidateAn ICE candidate for the monitoring channel.
search_queueSearches the upcoming queue.
special_guest_connectJoins the broadcast with a one-off code, skipping the queue.
stream_get_seekAsks for the current position of the shared stream (stream mode only).
stream_pingMeasures round-trip latency to the server.
suggest_songRequest a song from the library to be played.

Server → client ​

Events the client should subscribe to.

EventSent toDescription
admin_authorizedsingle socketA helper admin has activated their account with a password of their own.
admin_confirmedsingle socketSession confirmed; carries the current permissions.
admin_errorsingle socketAn admin action failed.
admin_onlineeveryoneWhether an admin able to accept requests is online.
admin_successsingle socketAn admin action succeeded.
audit_new_entryeveryoneA new entry in the audit log.
background_music_now_playingroom LIVE_HOSTS_ROOMWhich track is currently filling the pause.
background_music_selection_changedroom LIVE_HOSTS_ROOMA host picked a different background track.
background_music_updatedeveryoneThe background music library has changed.
donation_resultsingle socketOutcome of a donation order: the song was queued or not.
force_logoutsingle socketThe session was terminated externally.
guest_code_updatedsingle socketThe special-guest access code has changed.
guest_force_disconnectsingle socketThe guest's participation was ended externally.
guest_pending_statusroom LIVE_HOSTS_ROOMThe state of a guest's request while they wait.
guest_queue_updatesingle socketThe queue of guest requests.
guest_request_resultsingle socketThe decision on a guest's request.
host_force_disconnectsingle socketThe host's broadcast was ended externally.
host_queue_pause_statesingle socket, room LIVE_HOSTS_ROOMThe queue has been paused or resumed.
jingles_updatedeveryoneThe set of jingles has changed.
library_updatedeveryoneThe library changed: a track was added, edited, moved or deleted.
listener_uidsingle socketThe reply to listener_init: listener identity and art token.
live_hosts_rostersingle socket, room LIVE_HOSTS_ROOMWho is currently on air: hosts and guests.
phrases_updatedeveryoneThe set of phrases has changed.
privileges_updatedsingle socketThis admin's permissions have just changed.
queue_updatedeveryoneThe queue has changed.
radio_hosts_modesingle socketWhether the server was built with live host support.
radio_hosts_onlineeveryone, single socketWhether anyone is on air right now.
stream_chat_mode_endeveryoneThe conversation ended and the queue resumes.
stream_chat_mode_starteveryoneHosts paused the queue — conversation has begun.
stream_jingle_endeveryoneThe jingle has finished.
stream_jingle_starteveryoneA jingle between songs has started.
stream_track_starteveryoneA new track started in the shared stream.
suggest_cooldown_updatesingle socketSeconds left before another song can be requested.
suggestion_resultsingle socketThe admin's decision on your song request.
suggestions_updatesingle socketThe current list of listener song requests.
synceveryone, single socketThe complete radio state. The core event of the protocol.
usersUpdateeveryone, single socketThe list of active listeners, for the header roster.

Details ​

admin_active ​

admin

Upgrades the connection to an admin socket and confirms the session.

Payload: token?: string — JWT; taken from the adminToken cookie if omitted

The mandatory first step for an admin client: until it happens the socket counts as an ordinary listener and all admin events are ignored silently. The reply is admin_confirmed or admin_error. Privileges are re-read from the database rather than taken from the token, so permission changes apply without logging in again. A previous session of the same admin is disconnected automatically — only one connection per account stays alive.

admin_add_song ​

admin

Adds a track from the library to the queue.

Payload:

js
{
  id: 'day/artist - title.mp3',
  title: 'Title', artist: 'Artist',
  orderType: 'lastinline' | 'donated',
}

Requires the queue_manage privilege. donated places the track ahead of ordinary requests. The result arrives as admin_success or admin_error, not in a callback. A cooldown applies between additions, and the server also refuses if the same track is already adjacent in the queue or the queue no longer fits before the mode switch.

admin_go_live ​

host

Puts an admin on air as a host.

Payload: no data (the first argument is ignored)

Acknowledgement payload:

js
{
  ok: true,
  queuePaused: false,
  hosts: [ /* who is on air right now */ ],
  pendingGuests: [ /* the queue of guest requests */ ],
  backgroundMusicMode: 'random' | 'hostChoice',
  selectedBackgroundMusicId: null,
}
{ error: { uk: '…', en: '…' } }

Requires the radio_host privilege and RADIO_HOSTS_MODE=true on the server. The number of on-air slots is capped by MAX_LIVE_HOST_SLOTS, and a full room returns an error. Calling it again while already live simply confirms the state. On success the socket joins the hosts room and starts receiving its events.

admin_guest_action ​

host

Approves or rejects a guest request.

Payload:

js
{ uid: '…', action: 'accept' | 'reject' }

Acknowledgement payload:

js
{ ok: true } or { error: … }

Requires radio_host. If the slot was taken between request and decision, the guest is refused with reason: "room_full".

admin_insert_song_group ​

admin

Inserts a predefined group of songs into the queue.

Payload:

js
{ groupId: string }

Requires both queue_manage and settings_groups, exactly like POST /api/admin/song-groups/:groupId/insert. Groups are configured separately through /api/admin/song-groups. Has a longer cooldown than single tracks.

admin_leave_live ​

host

Ends the host's broadcast.

Payload: no arguments

The slot is freed immediately. Disconnecting has the same effect.

admin_remove_song ​

admin

Removes a track from the queue by position.

Payload: position: number — offset from the current track, as in search_queue

Requires queue_manage. Has its own cooldown — calling too often returns an error with the number of seconds to wait.

admin_skip_song ​

admin

Skips the current track.

Payload: no arguments

Requires queue_manage. In stream mode the server refuses while a jingle is playing or the queue is paused by a host. Shares a cooldown with adding tracks.

admin_suggestion_action ​

admin

Accepts or rejects a listener's song request.

Payload:

js
{ uid: '…', action: 'add' | 'reject' }

Requires queue_manage. add queues the track as lastinline. The listener receives suggestion_result. If the request already expired, a "not found" error comes back.

get_queue ​

admin

A page of the upcoming queue.

Payload:

js
{ offset?: number = 0, limit?: number = 10 }

Acknowledgement payload:

js
{
  items: [{ id, title, artist, orderType }],
  total: 42,   // tracks ahead in total
}

The reply arrives in the callback. The current track is not included.

guest_check_ban ​

guest

Whether this IP address is banned from the broadcast.

Payload: no data

Acknowledgement payload:

js
{ banned: boolean }

Worth calling before showing the request form, to avoid false hope.

guest_connect ​

guest

Connects an approved guest to the broadcast.

Payload: no data

Acknowledgement payload:

js
{ ok: true, role: '…', nickname: '…', expiresAt: 1754899200000 }

Only succeeds once a host has approved the session and a slot is free. expiresAt is when the session will be cut off; the duration comes from the radio settings. From then on the guest uses the same microphone events as a host.

guest_leave_live ​

guest

The guest leaves the broadcast.

Payload: no arguments

guest_request ​

guest

A listener asks to join the broadcast.

Payload:

js
{ nickname: string }

Acknowledgement payload:

js
{ ok: true }
{ error: … , secsLeft?: number }

The request joins a queue shown to hosts; the decision arrives as guest_request_result. Admins cannot submit one. A cooldown and an IP ban check apply.

host_audio_chunk ​

host

A chunk of microphone audio to mix into the broadcast.

Payload: Buffer / ArrayBuffer — raw audio data

Accepted only while the microphone is enabled via host_mic_toggle — otherwise chunks are silently dropped. This is the hottest channel in the protocol: it streams continuously while the host speaks.

host_get_background_music_list ​

host

A page of available background music.

Payload:

js
{ offset?: number = 0, limit?: number = 5 }

Acknowledgement payload:

js
{ items: [ … ], total: 12, mode: 'day' }

The list depends on the radio's current mode. limit is capped at 50. Non-hosts receive an empty list.

host_guest_kick ​

host

Removes a guest from the broadcast.

Payload:

js
{ targetId: string }

Acknowledgement payload:

js
{ ok: true } or { error: … }

host_guest_mute ​

host

Mutes a guest's microphone.

Payload:

js
{ targetId: string, muted: boolean }

Acknowledgement payload:

js
{ ok: true } or { error: … }

host_mic_gain ​

host

Changes your microphone level in the mix.

Payload:

js
{ gain: number }

host_mic_toggle ​

host

Turns the on-air microphone on or off.

Payload:

js
{ on: boolean }

While the microphone is off the server discards audio chunks from this socket. Applies to both hosts and guests.

host_pause_queue ​

host

Pauses the track queue — "just chatting" mode.

Payload: no arguments

Available to hosts only, not guests. Background music fills the silence, and in every listener's sync the title becomes "Just chatting".

host_resume_queue ​

host

Resumes queue playback.

Payload: no arguments

host_set_background_music ​

host

Chooses the track that plays while the queue is paused.

Payload:

js
{ trackId: string | null }

Acknowledgement payload:

js
{ ok: true } or { error: … }

null restores random selection. Only meaningful when backgroundMusicMode is hostChoice.

listener_init ​

listener

Registers the connection as a listener and asks for an art token.

Payload: no arguments

The server does not hand out the token by itself — until the client sends this event, no listener_uid arrives and art, lyrics and audio stay closed. Admin connections ignore this event.

moderator_ban_ip ​

admin

Bans an IP address manually.

Payload:

js
{ ip: string, nickname?: string }

Acknowledgement payload:

js
{ ok: true, entry: { … } } or { error: … }

moderator_ban_participant ​

admin

Bans a broadcast participant by IP.

Payload:

js
{ targetId: string }

Acknowledgement payload:

js
{ ok: true } or { error: … }

Works for guests only: hosts cannot be banned. Requires the SQL provider.

moderator_deactivate_guest_code ​

admin

Deactivates the current access code.

Payload: no data

Acknowledgement payload:

js
{ ok: true }

moderator_generate_guest_code ​

admin

Creates a special-guest access code.

Payload:

js
{ ttlHours?: number }

Acknowledgement payload:

js
{ ok: true, code: "…", expiresAt: 1754899200000 }

If an active code already exists an error is returned — deactivate it first, or use moderator_regenerate_guest_code. The code lets someone join the broadcast without queueing, via special_guest_connect.

moderator_get_banlist ​

admin

The list of banned IP addresses.

Payload:

js
{ offset?: number = 0, limit?: number = 10 }

Acknowledgement payload:

js
{ list: [ … ], total: 7 }

Requires DATA_PROVIDER=sql — otherwise the list is unavailable.

moderator_get_guest_code ​

admin

The current special-guest access code.

Payload: no data

Acknowledgement payload:

js
{ code: "…" } — or { code: null } when no code is active

moderator_get_live_roster ​

admin

Who is on air right now.

Payload: no data

Acknowledgement payload:

js
{ roster: [ … ], hostsOnline: true }

Without the privilege an empty roster is returned, not an error.

moderator_kick ​

admin

Removes a participant from the broadcast.

Payload:

js
{ targetId: string }

Acknowledgement payload:

js
{ ok: true } or { error: … }

The participant receives host_force_disconnect or guest_force_disconnect.

moderator_mute ​

admin

Mutes any participant of the broadcast.

Payload:

js
{ targetId: string, muted: boolean }

Acknowledgement payload:

js
{ ok: true } or { error: … }

Unlike host_guest_mute, this also works on hosts.

moderator_regenerate_guest_code ​

admin

Replaces the current access code with a new one.

Payload:

js
{ ttlHours?: number }

Acknowledgement payload:

js
{ ok: true, code: "…", expiresAt: … }

The old code stops working immediately.

moderator_unban_ip ​

admin

Lifts a ban on an IP address.

Payload:

js
{ ip: string }

Acknowledgement payload:

js
{ ok: true } or { error: … }

monitor_answer ​

host

WebRTC answer for the host's personal monitoring channel.

Payload:

js
{ sdp: … }

A private low-latency channel for hearing the broadcast — separate from the shared MP3 stream, which lags. Requires the UDP ports HOST_MONITOR_ICE_PORT_MIN–MAX to be open.

monitor_ice_candidate ​

host

An ICE candidate for the monitoring channel.

Payload: a WebRTC candidate object

search_queue ​

admin

Searches the upcoming queue.

Payload:

js
{ query?: string }

Acknowledgement payload:

js
[{ id, title, artist, orderType, position }]

position is the offset from the current track, and it is exactly what admin_remove_song expects. An empty query returns the whole queue.

special_guest_connect ​

guest

Joins the broadcast with a one-off code, skipping the queue.

Payload:

js
{ code: string, nickname: string }

Acknowledgement payload:

js
{ ok: true, … } or { error: … }

The code is issued by a moderator. Attempts are rate-limited, and an expired or deactivated code returns an error.

stream_get_seek ​

listener

Asks for the current position of the shared stream (stream mode only).

Payload: clientTs: number — the client's local time

Acknowledgement payload:

js
{ seek: 87.2, duration: 214.5, trackId: 'day/artist - title.mp3' }

Only needed to display a timer: in stream mode the audio itself is synchronous by construction. Outside stream mode the server does not reply at all.

stream_ping ​

listener

Measures round-trip latency to the server.

Payload: clientTs: number

Acknowledgement payload:

js
{ serverTs: number, clientTs: number }

The server echoes your clientTs unchanged — the round-trip difference gives you a latency estimate for more accurate positioning.

suggest_song ​

listener

Request a song from the library to be played.

Payload:

js
// a song object from GET /api/library
{ id: 'day/artist - title.mp3', title: 'Title', artist: 'Artist' }

Acknowledgement payload:

js
{ ok: true }
{ error: 'cooldown', secsLeft: 240 }
{ error: 'no_admin' }   // no online admin with queue rights
{ error: 'no_uid' }     // listener_init was never sent

Uses an acknowledgement callback — the second argument to emit. The error codes here are plain strings, not locale objects, unlike the rest of the API. The cooldown is 5 minutes per uid; a request expires after 5 minutes if no admin responds.

admin_authorized ​

admin

A helper admin has activated their account with a password of their own.

Until then the account has authorized: false and can do nothing.

admin_confirmed ​

admin

Session confirmed; carries the current permissions.

Payload:

js
{
  role: 'super_admin' | 'admin',
  privileges: ['queue_manage', '…'],
  authorized: true,   // false = the helper has not activated themselves yet
}

Build your interface from this list rather than from the token contents. authorized: false means a helper admin still has to set their own password.

admin_error ​

admin

An admin action failed.

Payload: a JSON string containing a locale object

The payload is a string, not an object: JSON.parse it first, then pick a language. The same applies to admin_success.

admin_online ​

listener

Whether an admin able to accept requests is online.

Payload: boolean

Broadcast to all listeners. false means suggest_song will fail with no_admin, so the request button is worth hiding.

admin_success ​

admin

An admin action succeeded.

Payload: a JSON string containing a locale object

Often carries a code field for machine handling. See admin_error.

audit_new_entry ​

admin

A new entry in the audit log.

Payload: an entry object — the same shape as in GET /api/admin/audit

Lets you append to the log live instead of re-fetching the whole list. Optional data.details contains before/after changes and context: format and operation types.

background_music_now_playing ​

host

Which track is currently filling the pause.

Payload:

js
{ trackId: '…', filename: '…' }   // both null when background music is stopped

background_music_selection_changed ​

host

A host picked a different background track.

Keeps the choice in sync between multiple hosts on air.

background_music_updated ​

admin

The background music library has changed.

A signal to re-read the list via host_get_background_music_list.

donation_result ​

listener

Outcome of a donation order: the song was queued or not.

Payload:

js
{ donationId, accepted: true, tier: 2 | null, song: { title, artist } }
{ donationId, accepted: false, reason: 'payment_failed' | 'no_slot' }

Sent only to the socket whose listener_uid matches the donor (both are derived from IP). no_slot means the payment was confirmed but the song could not be queued (e.g. not enough time before a mode switch) — the donation is marked paid_unqueued and stays visible to the admin in the donation history.

force_logout ​

admin

The session was terminated externally.

Payload:

js
{ reason: 'admin_deleted' }

The account was deleted or revoked. The client should clear its state and return the user to the login screen.

guest_code_updated ​

admin

The special-guest access code has changed.

A signal for other admins to refresh the code they display, so two moderators do not dictate different codes.

guest_force_disconnect ​

guest

The guest's participation was ended externally.

Causes: removal, a ban, or the expiresAt time limit. The client must stop the microphone and tear down the broadcast UI.

guest_pending_status ​

guest

The state of a guest's request while they wait.

guest_queue_update ​

host

The queue of guest requests.

Sent only to admins holding the radio_host privilege.

guest_request_result ​

guest

The decision on a guest's request.

Payload:

js
{ accepted: true, nickname: '…' }
{ accepted: false, auto: false, reason: 'room_full' }

reason: "room_full" means the slot was taken while the request waited. After accepted: true the client must send guest_connect.

host_force_disconnect ​

host

The host's broadcast was ended externally.

Arrives on removal by a moderator or a forced session end. The client must tear down the broadcast UI and stop capturing the microphone.

host_queue_pause_state ​

host

The queue has been paused or resumed.

Payload:

js
{ paused: boolean }

Hosts room only. Listeners see this state through sync.

jingles_updated ​

admin

The set of jingles has changed.

A signal to re-read the list. Jingles only exist in the cloud + sql + stream configuration.

library_updated ​

admin

The library changed: a track was added, edited, moved or deleted.

Payload: no data — it is only a signal to re-read

Broadcast to everyone, not just admins. The signal does not say what changed, so simply re-request the list you need — /api/admin/songs for an admin UI or /api/library for a listener. Emitted from several places: after an upload, after saving in the editor, after a mode change and after bulk operations.

listener_uid ​

listener

The reply to listener_init: listener identity and art token.

Payload:

js
{
  uid: '…',                 // derived from the IP; the client does not choose it
  artToken: '…',
  artTokenExpiresIn: 3600,  // seconds
  cooldownSecsLeft: 0,      // until the next song request is allowed
}

Listeners behind a shared NAT receive the same uid, and therefore share a token and a song-request cooldown.

live_hosts_roster ​

host

Who is currently on air: hosts and guests.

Sent to the hosts room on every change of line-up. Ordinary listeners never receive it.

phrases_updated ​

admin

The set of phrases has changed.

A signal to re-read the list. Unlike jingles, there is no stream_phrase_start/stream_phrase_end: a phrase is mixed into the song rather than replacing it, so listener-facing track metadata never changes.

privileges_updated ​

admin

This admin's permissions have just changed.

Payload:

js
{ privileges: ['queue_manage', '…'], authorized: true }

Sent to the admin whose permissions the super admin changed. This is the event that makes permission changes take effect without logging in again — rebuild your interface from the new list.

queue_updated ​

admin

The queue has changed.

A broadcast signal to re-read the queue via get_queue. Listeners still get the radio state itself through sync.

radio_hosts_mode ​

listener

Whether the server was built with live host support.

Payload: boolean

Sent immediately on connect, without a request.

radio_hosts_online ​

listener

Whether anyone is on air right now.

Payload: boolean

Only sent when radio_hosts_mode is enabled.

stream_chat_mode_end ​

listener

The conversation ended and the queue resumes.

stream_chat_mode_start ​

listener

Hosts paused the queue — conversation has begun.

Payload:

js
{ serverTs: … }

Duplicates what sync shows as title: "Just chatting", but arrives instantly rather than within two seconds.

stream_jingle_end ​

listener

The jingle has finished.

stream_jingle_start ​

listener

A jingle between songs has started.

Payload:

js
{ jingleId: '…', serverTs: … }

Time to hide track metadata: what is playing is not a song.

stream_track_start ​

listener

A new track started in the shared stream.

Payload:

js
{ trackId: '…', duration: 214.5, serverTs: 1754899200000 }

serverTs is the start moment on the server's clock. Combined with your own latency measurement via stream_ping this gives an accurate position within the track.

suggest_cooldown_update ​

listener

Seconds left before another song can be requested.

Payload: number — seconds, 0 when a request is allowed

Sent to every connection sharing the same uid, so multiple tabs show the same countdown.

suggestion_result ​

listener

The admin's decision on your song request.

Payload:

js
{ accepted: true, auto: false, song: { title, artist, … } }

auto: true means the request expired on a timer rather than being rejected by hand. Sent only to the socket that made the request.

suggestions_update ​

admin

The current list of listener song requests.

Payload:

js
[{ uid: '…', song: { id, title, artist }, addedAt: 1754899200000 }]

Sent only to admins holding queue_manage — on connect, on every change to the list, and when the radio switches to day mode and all requests are cleared.

sync ​

listener

The complete radio state. The core event of the protocol.

Payload:

js
{
  track: 'day/artist - title.mp3',  // null while the mode is switching
  title: 'Title', artist: 'Artist', album: 'Album', year: 2020,
  duration: 214.5,
  seek: 87.2,                       // position as of serverTimeMs
  isPlaying: true,
  playlist: [ /* up to 10 upcoming tracks */ ],
  currentIndex: 3, totalTracks: 42,
  mode: 'day',                      // 'day' | 'night'
  pendingModeSwitch: null,
  skipCooldownSecsLeft: 0,
  uiSettings: { /* branding */ },
  serverTimeMs: 1754899200000,
  dayStartHour: 6, nightStartHour: 0,
}

Broadcast to everyone every 2 seconds, and once immediately on connect. While the server switches modes a shortened form arrives with isPreparing: true and track: null. When hosts pause the queue, title becomes "Just chatting" and artist goes empty.

usersUpdate ​

listener

The list of active listeners, for the header roster.

Payload:

js
[{ name: 'Name', img: 'file.png', color: '#a3f01c' }]

Listeners are anonymous: the server assigns each connection a random name from an "oligarchs" set and a random colour. The name arrives already localized.