helloworld
Lifecycle + Live toggle
The smallest complete VibeLive app: two people on live video in about 60 lines,
including all CSS. The first visitor becomes the host and a room is created
automatically; guests join by opening the same link (the room code is carried
in the URL). You start Live automatically, and a single Go Live /
Go Not-Live button toggles it. This app establishes the lifecycle every
later app builds on.
Load and initialize the SDK
A single ESM import plus init() with your project ID is the only
configuration required. STUN/TURN, ICE negotiation, and signaling are all
handled invisibly — your app never touches a socket.
Sign up, create or join a room, then enter
signup() creates an anonymous identity — no email or password.
The host calls createRoom() to get a shareable room_code;
everyone enters the same room with enterByRoomCode(), which fires
channelSelected once you are safely inside.
Go live inside channelSelected, then toggle
Always do setup in channelSelected, not after
enterByRoomCode(). Camera and mic are off by default:
you must call setVideo(true) / setAudio(true), then
startLive() to open the WebRTC connection. After that, one button
flips Live ↔ Not-Live via stopLive() / startLive(),
with localJoined / localLeft keeping its label in sync.
Render remote video with registerTile()
A tile is a plain <div> holding a <video>.
registerTile() wires the stream to it — without that call nothing
renders. The SDK auto-removes the tile when the member exits, so there's no
teardown code. One addTile() helper serves both
remoteJoined and remoteStreamStart; an id guard makes
double-calls safe.
Exit and rejoin
exitRoom() leaves the room and releases the camera; the SDK removes
every registered tile for you. To rejoin, just call enterByRoomCode()
again — it re-fires channelSelected, so the same setup handler runs
and you're back live. One button toggles between the two.
Media controls & member roster
camera · mic · screen · presence
Still one self-contained file, now with camera/mic/screen-share controls and a
member-list sidebar. Button labels reflect live SDK state
(Camera On / Mic Muted / Screen Off), and the
roster stays current from presence events. The key idea: read state from the
SDK, don't track it yourself.
Toggle camera, mic, and screen share
Each toggle reads the current value off VibeLive.mediaState /
VibeLive.screenState and flips it. No local boolean to keep in
sync — the SDK is the single source of truth.
Drive button labels from memberStateChange
memberStateChange fires for any member whose media state changes —
including yourself. Update your button labels here so they always match reality,
even when a toggle takes a moment to apply. Values are 'ON',
'MUTED', or 'OFF'.
Screen share is a second stream type
A member can publish both 'camera' and 'screenshare'
streams. Register each in its own tile in the same grid, then show or hide the
screenshare tile based on state.screenVideo.
getMemberState(id) catches a share that started before its state
event arrived. (The full app takes this further, splitting screen and
cameras into separate stages.)
Seed the roster with getMembers()
Once inside the room, getMembers() returns everyone currently present.
Each member carries a displayName and a displayStatus —
a friendly label: LIVE, PRE-LIVE, or EXITED.
The sample relabels PRE-LIVE as OFFLINE in the roster,
since a member who hasn't gone live has no visible stream.
Keep the roster current with presence events
memberStateChange updates a member's status (and signals
new arrivals); memberUpdate signals a renamed member — re-read it with
getMember(id). Re-render on each event and the sidebar always matches
the room.
| New API | Purpose |
|---|---|
| setScreenshare(bool) | Start or stop publishing your screen |
| mediaState | Live { video, audio } state for self |
| screenState | Live { video } screenshare state for self |
| getMemberState(id) | Read a member's current media state on demand |
| getMembers() | Snapshot of everyone currently in the room |
| getMember(id) | Look up one member's current record |
| displayStatus | Friendly status: LIVE / PRE-LIVE / EXITED (roster shows PRE-LIVE as OFFLINE) |
| memberStateChange | Fires when any member's media or presence state changes |
| memberUpdate | A member's profile (e.g. name) changed |
Room chat
live text messaging
Core plus a chat panel — room-wide text messaging alongside the video, still one
self-contained file. Load recent history once, then listen for everything that arrives
afterward; the same channel.id drives every message call.
Load history, then listen for new messages
Inside channelSelected, fetch recent messages with
getMessages(channelId, { count }) (newest-first — reverse for display),
then register setOnMessage() for everything that arrives afterward.
The channel id is VibeLive.channel.id.
Send a message to the room
sendMessage(channelId, text) broadcasts to everyone in the room.
Echo your own line locally right away, and de-dupe against the echo using the
pid the call returns.
Render incoming messages
Each message carries a sender_display_name and
sender_member_id (compare it to your own to label your lines).
De-dupe with the message pid and always escape text before rendering.
| New API | Purpose |
|---|---|
| getMessages(channelId, opts) | Fetch recent history (newest-first) |
| setOnMessage(cb) | Register a handler for incoming messages |
| sendMessage(ch, text) | Broadcast a room-wide message |
| channel.id | The active channel id, used by all message calls |
DMs & sessions
private messages · past sessions · AI recaps
helloworld-full brings everything together on one screen — no toggle
buttons, all panels open at once — using a shared shell
(helloworld-shell.jsp) for the tiles, split-stage layout, roster and
lifecycle. On top of core + chat it adds private DMs and a
sessions panel with AI-generated recaps.
Room message vs. direct message
sendMessage(channelId, text) broadcasts to the room. Add a
memberId third argument and the same call becomes a private DM to that
member. The app stores a dmTarget; clearing it returns to room-wide. A
[DM] button on each remote tile (added via the shell's
decorateTile hook) opens a one-to-one thread.
Route incoming messages by target_type
Each incoming message reports a target_type: 'channel' for
room messages, 'member' for DMs. Combine that with
sender_member_id to label each line (To Room / PRIVATE to … / From …).
List past sessions with summaries and tags
Inside channelSelected, call getSessions(channelId).
Each returned object carries title, summary,
tags, start_time, duration_seconds and the
other_users who were present. Visibility is requestor-aware on the
server — hosts see every session, members see only the ones they were in.
Refresh when a new summary is ready
A session's summary is generated asynchronously, shortly after a member exits.
Subscribe to sessionSummaryReady and re-fetch the list when it fires —
no polling. Until the AI finishes, a fresh session shows up with its summary still
pending.
Load the transcript on request
The summary is the headline; the full transcript is fetched only when asked, with
getTranscript(channelId). It returns { status, text, segments }.
Note the transcript is retrieved per room (it covers the whole channel), while
summaries and tags are per session.
| New API | Purpose |
|---|---|
| sendMessage(ch, text, id) | Send a private DM to one member |
| target_type | Incoming routing: 'channel' or 'member' |
| getSessions(channelId) | List past sessions with summary + tags (requestor-aware) |
| getSummaries(channelId) | Alias for getSessions |
| getTranscript(channelId) | Fetch the room transcript on request |
| sessionSummaryReady | Event — a new session summary is ready to load |
| session.summary / .tags | AI-generated headline and topic labels per session |
| session.other_users | The other members present during that session |
Where to go next
- The full feature catalog — every capability of the SDK, organized by tier, lives in the Feature Overview
- Run the apps — each section links to a live, runnable version; open two browser tabs to see both sides of a room
- Read the source —
helloworld,helloworld-coreandhelloworld-chatare each one self-contained file; onlyhelloworld-fulluses a small shared helper include (helloworld-shell.jsp). The "View source" link shows each exactly as deployed - Add a test member — every app now carries a standard
Add Botbutton in its header (a demo-support feature, not core SDK code). It posts totestmd1/api/spawnBot.jsp, which spawns a headless test member that auto-joins the room and goes live viabotjoin.jsp— handy for seeing multiple members from a single tab - Use your own project ID — append
?pid=YOUR_IDto any app link to run it against your own project