Social LiveDocumentationPLATFORM DOCS

Joining a Room

The end-to-end sequence from tapping a room to seeing video.

Last updated Thu Jul 16 2026 00:00:00 GMT+0000 (Coordinated Universal Time)

TL;DR -When you tap a live room, the app first grabs the room details over a normal web request, then opens a realtime connection and pulls the host's video and audio through a media server. This page traces that flow for both viewers and hosts.

Who this is for -Product: the plain walk-through at the top. Mobile & Backend: the sequence diagram, the per-role audio behavior, and the reconnect logic. Engineering: everything, including the security notes.

Getting into a live room takes two connections working together. First the app asks the server "what's this room?" and gets back the basics -who's on stage, how many people are watching, and where the media lives. Then it opens a live, always-on connection to receive the video, audio, chat, and gifts as they happen.

Splitting it this way matters: the first request is the durable, reliable record, and the live connection is the fast-but-fragile stream. If the live stream hiccups, the app can always re-ask the first request and catch back up.

Viewer joins a room

This diagram traces every step a viewer's app takes, from the initial join request through to audio and video actually flowing. The SFU is the media server that relays the host's stream out to many viewers.

Rendering diagram…

Failure handling on the client: if the signaling join fails, media setup is aborted (no orphaned transports); if a consume fails, the producer resync path retries on the next update.

Audio session per role

Whether the app opens your microphone depends on your role. Viewers only listen, so their mic never turns on -which keeps Bluetooth earbuds in full-quality music mode. Only people on stage switch to the call-style audio session that opens the mic.

Viewers get a playback-only audio session (iOS playback category / Android normal media mode): the mic route never opens, Bluetooth earphones stay on A2DP (no call-profile "ding", no mic indicator, full audio quality). Only stage roles (host/guest) switch to the call-style playAndRecord + speakerphone-preferring-Bluetooth session -so the Bluetooth profile switch is expected exactly when someone joins the stage, and reverts when they leave the stage (demotion re-enters the viewer join path).

Host starts a room

Starting a room is the same idea in reverse: the host creates the room, opens the live connection, and begins sending their camera and mic instead of receiving.

  1. POST /live/rooms -creates the room + HOST participant, normalized defaults (background, privacy, guest slot count up to 9).
  2. Socket join + host media session (send transport).
  3. Produce mic/camera; wakelock keeps the screen on.
  4. Every later state change (guest approvals, PK, ending) flows through gateway events with REST as the durable record.

Rejoin & resume

Connections drop -a subway tunnel, a backgrounded app. The app is built to recover without the user losing their place.

  • Viewer reconnect: socket reconnects → rejoin room channel → REST refetch reconciles state → resync consumes any missed producers.
  • Host resume: within the 5-minute grace window, the host's rejoin cancels the close timer and the room continues; viewers never lose the room entry.

Client-side ownership

On the app side, one controller orchestrates the flow, and it deliberately never touches raw media APIs -those are delegated to dedicated modules.

live_room module drives the flow through LiveRoomController, which tracks a viewer/host state machine and exposes a non-sensitive WebRTC debug overlay (disabled by default). Media details are delegated to media_webrtc; socket details to signaling -the live room module never touches raw WebRTC APIs directly.

Anonymous sockets are rejected

Every live connection must prove who it is with a valid token. Connections without one are turned away instead of showing up as ghost "Viewer" entries in the room.

room:join requires a valid JWT on the socket. When verification fails (expired token, no token), the join is acked with "Sign-in expired — please rejoin" instead of registering a phantom audience entry keyed by the socket id -this is what used to surface as a "Viewer" row in the viewers sheet and "Viewer" chat lines. Anonymous chat:message sends are dropped for the same reason. The client refreshes its access token on room entry whenever less than 15 minutes of validity remain, so normal sessions never hit the rejection.