Live Rooms
Room lifecycle, roles, stage guests, audio party rooms, backgrounds, and PK battles.
Last updated Thu Jul 16 2026 00:00:00 GMT+0000 (Coordinated Universal Time)
TL;DR -A live room is the heart of Social Live: one host broadcasts, viewers join and chat, and paid gifts fly across the screen. This page covers everything that happens inside a room.
Who this is for -Product & Design: the plain-terms and feature sections up top. Business: the gifting behaviors. Engineering: everything, including endpoints, data model, and invariants.
A live room is a live broadcast anyone can join. One person -the host -is on stage with their camera and mic. Everyone else is a viewer who watches, types in the chat, and sends gifts (animated icons bought with coins) to cheer the host on. The host can also invite viewers up onto the stage as guests.
Think of it like a tiny live TV show that any fan can jump into, comment on, and tip. The rest of this page walks through how a room is born, who can do what inside it, how gifts and chat behave, and the controls a host has to keep things running.
What a room is, in plain terms
- A room has one host and many viewers. The host controls the room: they can end it, invite guests on stage, and remove troublemakers.
- Rooms come in two flavors: video (
live) and voice-only (voice, called an audio party room). - The stage is the set of people currently broadcasting -the host, plus any guests they've approved. Viewers can raise their hand to join the stage.
- If a host's connection drops, the room doesn't die instantly. There's a short grace window to reconnect, so a flaky signal doesn't kill a live show.
- Gifts are the money moment. Viewers tap a gift, it's charged to their coins, and it animates for everyone. Hosts (and on-stage guests) earn from gifts sent to them.
Everything below adds the technical detail beneath each of these ideas.
Room lifecycle
A room moves through a handful of states from the moment a host opens the create screen to the moment it ends. This diagram shows those states and the events that move between them.
Rendering diagram…
- Creation -host picks title (with optional styled title: font/align/
effect stored as sanitized JSON
titleStyle), room kind (livevideo orvoice-see Audio Party Rooms), privacy, and a visual background (LiveRoomBackground). The host becomes aLiveParticipantwith roleHOST. - Grace window -if a host drops (app crash, network), the room enters a host-closed state with a 5-minute timer. Rejoining within the window resumes seamlessly; otherwise the room disappears from discovery and is ended. This prevents flapping rooms from a flaky connection.
- Counts -viewer/participant counters are incremented on join and decremented with floors (never negative), reconciled against actual gateway audience.
Roles
There are three roles in a room, and they differ by what media they can send and what powers they have. Viewers only receive; hosts and guests can broadcast.
| Role | Media | Powers |
|---|---|---|
HOST | Publish audio+video | End room, approve/remove guests, kick/block anyone, start PK battles |
GUEST | Publish (audio-only or A+V, chosen at accept time) | On-stage participant |
VIEWER | Receive only | Chat, gift, request stage |
Stage flow: viewer requests → host approves → guest picks media mode → guest's producers appear for everyone (see Media & SFU).
Gifts inside a room
Gifts are the core paid interaction. A viewer taps a gift, it's charged to their coin balance, and it animates on everyone's screen. Here's how that works under the hood.
Gifts are charged over REST (POST /wallet/gifts/send, idempotent -see
Gifting) and broadcast over the live gateway.
Recipients default to the host but can target any stage participant.
Self-gifting is always blocked, which also prevents hosts pumping their
own rooms.
The gift tray supports multi-recipient sends: a horizontal avatar row
of everyone on stage (host first) sits above the gift grid with an "All"
select-all button. Tapping a gift sends it to every selected person -the
client issues one POST /wallet/gifts/send (fresh idempotency key each)
and one gift:send socket announce per recipient, so each send is its own
ledger transaction and a partial failure (e.g. running out of coins midway)
never corrupts the ones already settled.
Combos: after a send, a floating combo button holds a 3-second window —
each tap resends the same gift to the same recipients with an incrementing
comboCount (display-only; every tap is its own idempotent wallet charge,
and a rejected charge stops the run instead of announcing an unpaid gift).
The server rewrites the sender's gift line in scrollback ("Sent Rose ×27")
rather than inserting one row per tap, and clients update that feed line in
place.
Every stage tile (host included) shows a session gift counter: coins
gifted to that person during this live. The gateway accumulates per-room
totals (coin value read from the catalog, never the client) and includes
recipientId / coinCost / recipientTotal on gift:sent events; the
counter is display-only session state -diamonds are settled by the wallet
at gift time -and resets when the live ends. Joining a different room
always starts from zero. Counters render short-scale abbreviated once they
reach the thousands (5,004 → 5k, 5,243 → 5.2k, 1,300,000 → 1.3M;
truncated, never rounded up).
Send animations: every gift:sent also queues an animation in the
room overlay (above the stage, tap-through, one at a time -floods keep
only the freshest few). Gifts with a remote Lottie animation URL play it;
everything else gets an icon/emoji burst, so gifts animate even before
real assets ship. Premium sends (≥100 coins or combo ×10+) render bigger
and longer -the tier-escalation hook for the SL-22 asset catalog.
Top gifters: the gateway also accumulates per-sender session totals;
gift:sent events carry a topGifters top-3 snapshot and the
stage-participants rejoin payload includes it, so the room header shows a
compact overlapping avatar stack of the top two gifters (gold/silver
rank rings) beside a bean total pill (sum of every ranked gifter's
coins). Tapping the stack or the
pill opens the gifter leaderboard flyout -every gifter ranked
highest-first with their bean amount on the right; tapping a row opens that
gifter's mini profile. Session-scoped, resets when the live ends.
Each stage box also shows a bean counter (coins gifted to that member
this session). Tapping it opens a per-guest flyout -who gifted that
member, sorted highest first (GET /live/rooms/:id/gifters/:recipientId,
authenticated, session-scoped like the leaderboard).
Room chat
The chat feed is the running conversation beside the stage. It survives leaving and rejoining because messages are stored per room, not just streamed live.
Chat messages (and gift lines) are persisted per room in LiveRoomMessage
-sender name/avatar/level snapshotted at send time, rows cascade-deleted
with the room. GET /live/rooms/:id/messages returns up to the last 500,
oldest first; clients seed their feed from it on every (re)join, merged
around whatever realtime events already arrived, so leaving and coming
back never loses the scrollback. The feed is fully scrollable back to the
first message of the stream.
Stage tiles show the participant's profile photo whenever their video isn't rendering -camera off, audio seats, still connecting, or host paused.
Comment actions: long-pressing any chat comment opens a sheet with
Reply and Copy (plus Pin for the host and Report for
others). Reply attaches a quote to the next comment: the chat:message
socket payload carries replyToId, the server resolves the target from the
stored row (authoritative -a client can't spoof the quoted sender/text) and
persists a snapshot (replyToId / replyToSenderName / replyToPreview) on
the LiveRoomMessage, so the quote survives leave/rejoin and renders above
the comment for everyone. The composer shows a cancelable quote bar while
replying.
Pinned announcement: the host can pin one message (Room controls →
Pin announcement, max 200 chars) that renders as a 📌 banner above the
chat feed for everyone, including late joiners -it lives on the room
(LiveRoom.pinnedMessage, POST /live/rooms/:id/pin, empty body unpins)
and fans out over the standard room-updated event. The pin dies with the
room. The host can also long-press any chat comment to pin it -the
request sends messageId instead of text and the server snapshots the
sender from the stored chat row, so the banner shows the comment exactly
as typed, with the sender's avatar and name.
Entrance banners
When a notable user walks in, the room shows a little animated "so-and-so joined" banner. Only higher-level users trigger it, and the level is server-verified so nobody can fake a fancy entrance.
Joins by users above the chat-badge level threshold (>5) trigger a short
animated entrance banner above the chat feed -avatar, level badge, and
"{name} joined", tinted by the level color ramp. The gateway enriches
live:viewer_joined with a server-resolved level and avatar (a client
can never claim a tier), and the client queues banners one at a time
(~2.4s, max 3 pending) so join floods never drown the room. VIP tiers
slot into the same banner when VIP subscriptions ship.
Profile flyouts
Tapping anyone in the room opens a mini profile card with quick actions -follow, message, or @-mention them into the chat.
Tapping anyone in the room -the host avatar (top-left), a guest tile on
stage, a commenter's avatar in the chat feed, or a row in the viewers
sheet -opens a mini profile flyout: photo, name, username, level, bio,
follower stats, plus Follow, Message (chains into the in-room DM thread)
and an @ mention action that pre-fills the room chat composer with
@username and opens the keyboard. Tapping the flyout's avatar jumps to
the full public profile, where tapping the photo opens it fullscreen
(pinch-zoom viewer).
Host moderation: kick & block
The host is the room's referee. From anyone's profile card they can remove or ban a disruptive person.
When the host opens anyone's flyout, a 3-dots menu (top-right) offers Block, Kick, and Kick & Block:
- Kick emits
room:kickon the live gateway. The server verifies the sender is the room host (the host can never be a target), announcesmoderation:room_action { action: "kick", userId }to the room, detaches the target's sockets, closes their media producers, removes any stage presence, and bars them from rejoining (room:joinis rejected) for as long as the room stays live. The kicked client tears down media and exits to home with a "removed by the host" notice. - Block reuses the standard block API (
POST /users/:id/block) -see Moderation. - Kick & Block performs both.
The kick ban list is in-gateway-memory per room and clears when the room ends.
Viewer list visibility
The host decides whether everyone can see who is watching, or just the host. The viewer count always stays public -only the list of names can be hidden.
The host chooses whether the room's viewer list is visible to everyone
or to the host alone. It is set at creation (create screen → Viewer list:
Everyone / Host only, default Everyone) and toggled live from Room
controls → Privacy. The flag rides LiveRoom.viewerListPublic and fans out
on the standard room-updated event, so every client flips in sync.
Only the list of who is watching is gated -the viewer count (the eyes
pill) stays visible to everyone. GET /live/rooms/:id/viewers is
authenticated: when a room is host-only and the requester is not the host,
it returns the count with an empty roster and hidden: true, so a crafted
client can never read the hidden list. The host toggles it with
POST /live/rooms/:id/viewer-visibility ({ isPublic }, host-only). Tapping
the eyes as a non-host on a hidden room shows a "host has hidden the viewer
list" notice instead of the sheet.
Comment lock
The host can freeze the chat entirely -useful to calm a room down. While locked, nobody (not even the host) sees the feed, and the backend refuses new comments too.
The host can lock room comments (Room controls → Privacy → Lock
comments). While locked the chat feed is hidden for everyone (host
included), the comment box renders disabled ("Comments are locked"), and an
open typing overlay is closed on the spot. The backend enforces it too:
createComment rejects with 403 while LiveRoom.commentsLocked is true,
covering both the REST route and the socket send-comment path. The host
toggles it with POST /live/rooms/:id/comments-locked ({ locked },
host-only); the flag rides the standard room-updated broadcast so all
clients flip in sync. Unlocking restores the feed including scrollback —
nothing is deleted.
DMs from inside a room
Viewers can read and answer their direct messages without leaving the live -the inbox slides open on top of the room.
The composer has a Messages button (unread badge included) that opens the DM inbox as a flyout over the room -viewers read and reply to DMs without leaving the live. It reuses the chat module's conversation list and thread screens in a nested navigator; unread counts refresh from the chat socket's realtime message stream.
PK battles
Two hosts go head-to-head and the audience decides the winner by gifting -whoever's gifts are worth more leads the score.
Two hosts battle head-to-head; gift value drives the score. State lives in
PkBattle with status transitions broadcast to both rooms. Only the host
of an existing live room may start one.
Discovery
Discovery is how viewers find rooms to join -a grid of what's live now, plus a feed of just the people you follow.
/discovery and /search list active rooms as API payloads (title, host,
avatar, counts, background). A follow-scoped feed shows rooms only from
accounts the viewer follows -and skips the query entirely when the user
follows no one.
Available stage grids
A video room can show different numbers of on-stage seats. Which grid sizes are offered is set by admins, and the server always has the final say so a hacked client can't force a bigger grid.
The video grid sizes a host may pick are admin-configurable (Settings →
Live rooms → Available stage grids, allowed_guest_slot_counts, stored as
guest-slot counts: 0 solo, 1→2-grid, 2→3-grid, 3→6-grid… where an N-grid
is host + guests). The default enables the small grids and disables the big
7/10 grids. GET /live/layout-options serves the allowed set; the create
screen and in-room selector hide disabled grids (a room already on a
now-disabled grid keeps its option until the host changes it). The server is
authoritative -createRoom/updateRoomLayout snap any requested size down
onto the allowed set after the room-kind clamp, so a crafted client request
can't force a disabled grid. Voice rooms are exempt (they use audio seat
layouts, not video grids).
Audio room layout
Voice rooms can render their seats in one of two styles, chosen by an admin.
Audio-party rooms render with one of two on-stage layouts, chosen by the
admin (Settings → Live rooms → Audio room layout, audio_room_layout,
served alongside the grids on GET /live/layout-options):
- Seat grid (
grid, default) -the classic seat tiles. - Bubbles (
bubbles) -each member is a translucent, glowing "soapy" bubble whose diameter scales with how much they've been gifted this session (their gifter tier), so the biggest gifters read largest. Speaking members glow brighter.
The layout only affects audio-party rooms; video rooms are unaffected.
Host inactivity auto-shutdown
If a host silently vanishes (phone dies, no reconnect), the room can't run forever with nobody driving it. A configurable countdown ends it cleanly.
A host who fully leaves a room (app killed or connection lost, with no socket reconnect) would otherwise leave a ghost live running forever. When the host's last socket goes, the gateway starts an inactivity countdown:
- The grace, in seconds, is admin-configurable (Settings → Live rooms →
Host inactivity,
host_inactivity_shutdown_seconds, default 300, clamped 30–3600). A bad value can never make rooms end instantly or hang. - The room's
endedAtis stamped tonow + grace, which hides it from discovery immediately and lets the lazy sweep reap it even across a server restart. Viewers already inside stay connected (join/getActiveRoom gate onisLive, notendedAt); counters are left intact. - A host reconnect within the window cancels the shutdown and un-hides
the room (
clearScheduledClose). - If the grace elapses, the room auto-ends via the normal end path, which
broadcasts
live:room_endedto the channel and evicts viewers.
This is distinct from an explicit host-close (a 5-minute grace with counters zeroed) -inactivity shutdown handles the host silently vanishing.
Sharing a live
Any room can be shared out to friends via a normal link. The link opens a small web page with a rich preview, and its button deep-links straight into the app.
The room's "…" menu has Share this live, which opens the OS share sheet
(WhatsApp, SMS, contacts, …) with a link to a public invite landing:
GET /live/:id/invite (unauthenticated, served from the API origin). The
landing is a small HTML page whose OpenGraph tags drive the rich preview
that messaging apps render -host name as the title, room title as the
description, host avatar as the image when it is an absolute URL. For a
missing, ended, or private room the landing renders a generic "join
Social Live" page and leaks no room detail. Its CTA uses the
sociallive:///live/:id deep link (triple slash -an empty host keeps the
whole path visible to the app's URI parser). The sociallive scheme is
registered natively on both platforms (iOS CFBundleURLTypes +
FlutterDeepLinkingEnabled, Android VIEW/BROWSABLE intent-filter +
flutter_deeplinking_enabled), and a GoRouter redirect normalizes the
legacy host-form sociallive://live/:id to /live/:id, so tapping the
button opens the room in the app.
Diagnostics
For debugging, room and media events can be logged and browsed in the admin portal behind a flag.
With ENABLE_LIVE_DIAGNOSTICS=true, room lifecycle and media events are
logged to LiveRoomEventLog (payloads sanitized) and browsable in the
admin Diagnostics page. LIVE_PERF_LOGS=true enables client-side
performance logs in the Flutter app for live flows.
Related pages
- Joining a room -the step-by-step of getting into a room.
- Audio party rooms -the voice-only variant.
- Chat & calls -the DM and calling surfaces.
- Safety & moderation -reporting, blocking, and enforcement.
- Business: Gifting -the money mechanics behind gifts.
- Architecture: Media & SFU -how one broadcast reaches many viewers.