Skip to main content
Markdown

Live streams

One person presents live to everyone watching: a product show in a shop, a class, a launch. The audience watches the picture, writes in the chat of the stream and sends reactions. The host goes live from the browser or a phone, or streams from OBS. It runs on the same connection, key and calls route as rooms and calls, on the media servers of WordPlus Cloud.

Who is who​

Your server declares streams as it declares rooms and calls, by pattern. The rule says who each person is in the stream:

// The show of the shop, show:mugs: Ann presents it, and everyone else watches, signed in or not.
realtime.streams( 'show:{id}', ( user, { id } ) => {
if ( ! showExists( id ) ) return false;
return user === presenterOf( id ) ? 'host' : 'viewer';
}, { reactions: true } );
  • The answer: false, a role, or the role with more:

    Field
    rolehost (goes live, ends it, keeps the chat) or viewer
    info{ name, avatar } as everyone sees them, your profile by default. avatar is an https:// address
    chatwrite or read
    modtrue for someone who keeps the chat without hosting
    srcwhat the host may send: camera, microphone, screen
    qualitythe best picture the host sends: 720p or 1080p
  • A visitor who is not signed in comes with user null, and may only be a viewer. A rule that gives them more is refused with no_user.

  • The settings of the stream, the third argument, are each a value or a function of the parts of its name:

    • reactions: whether it takes reactions.
    • people: who may see who is watching, mods or all.
    • chat: { offline, links, fresh }. offline is 'open' or 'closed' for the chat while the stream is not live. links: false takes links only from those who keep the chat. fresh: true empties the chat each time the stream goes live.
  • Names use letters, digits and -=@,.;:, up to 128 characters, as room names do but without _.

The calls route (/api/realtime/calls) answers streams too: nothing more to add to realtime.node() or your routes.

Watching​

const calls = await rt.calls();
const show = await calls.stream( 'show:mugs' );

show.watch( video ); // the main picture of the stream, as it changes, and its sound
show.on( 'state', ( state ) => {} ); // idle, live, ended
show.on( 'viewers', ( n ) => {} ); // how many watch, the host not counted
  • The main picture is the one the host chose: a shared screen, the picture from OBS, else the camera of the host. show.tiles has each picture with its own camera, screen and audio, for a layout of your own.
  • Sound plays from the page, and video itself stays muted. A browser that holds sound back until a click says so in show.snapshot().audioBlocked: call show.startAudio() from a click.
  • video: false in calls.stream( name, { video: false } ) joins without the picture, for a page that shows the chat or the count. watch() fetches it later.
  • show.leave() leaves the stream, its picture and its chat.

Going live​

The page of the host shows the camera before going live, then goes live with whatever it has on:

await show.preview();                  // the camera and microphone, shown in watch( video )
await show.setCamera( false ); // or setMicrophone(), setScreenShare( true )
await show.goLive(); // everyone watching sees it now
await show.end();

goLive() with nothing on turns the camera and microphone on first. A host may take an ended stream live again.

From OBS or another encoder, over WHIP:

const { url, key } = await show.ingest();   // ingest( true ) makes a new key and cuts off the old one

The picture from the encoder takes the place of the camera.

The chat and reactions​

show.on( 'message', ( m ) => add( m.from.name, m.text ) );   // each new message, show.messages has the history
await show.chat.send( 'Hello!' );
show.react( 'heart' ); // heart, clap, laugh, wow, fire

Whoever keeps the chat, the host and anyone the rule made mod:

await show.chat.delete( id );
await show.chat.pin( id );
await show.chat.timeout( user, 600 ); // from a minute to a day, they go on watching
await show.chat.slow( 30 ); // one message per 30 seconds, 0 turns it off
await show.chat.clear();
await show.kick( user ); // out now, they may come back
await show.ban( user ); // out, and kept out

A refused message rejects with its code: read_only, slow, timeout, too_long, links, not_live.

A screen of your own​

show.snapshot() is everything the screen of the stream draws: its phase, the audience, the chat, the pinned message, the pictures and their layout. show.subscribe( fn ) hears every change, as useSyncExternalStore in React wants. In React, useStream( name ) does both (React).

What WordPlus Cloud sees​

A stream is for everyone watching it, so its picture and chat are not encrypted end to end. They are encrypted on the way, and the media servers of WordPlus Cloud see them, as a stream needs.

Refusals​

calls.stream() and the actions of the host reject with a code:

Code
forbiddenYour rule said no, or no pattern matches the name
no_userYour rule gave a visitor who is not signed in more than the place of a viewer
not_includedThe plan of your app does not include live streams
bannedThis user is banned from the stream
no_hoursThe account has no credits left this month, what it may borrow included: new viewers above the plan come in once credits are bought
not_hostAn action of the host from someone who is not the host