Signaling

Every channel carries messages and presence alongside the audio and video. That is what a live comment feed, a viewer count and an audience list are built from — and it needs no second connection, no second server, and no extra latency, because it travels the same path the media does.

Note

Signaling is not stored. A message reaches whoever is in the channel when it is sent; someone joining a minute later will not see it. If you need history, keep it on your own server — write messages to your database as they arrive, or send them through your backend first.

Messages#

sendMessage broadcasts to everyone in the channel. There is no addressing — everyone receives everything, which is what makes it right for comments and wrong for private replies.

An audience member can send messages even though they cannot publish video. That asymmetry is the point: a viewer of a broadcast can comment without ever being able to appear on stage.

// A comment
await engine.sendMessage('First!');
// Anything structured — a reaction, a poll vote, a raised hand
await engine.sendMessage({ kind: 'reaction', emoji: '🎉' });
engine.on('message', (m) => {
console.log(m.from?.displayName, m.text ?? m.data);
});

Note

Your own message does not come back to you. Append it locally when sendMessage resolves — it returns the message it sent, id and all. The useMessages hook does this for you.

What a message carries

FieldTypeNotes
idstringUnique to the message. Use it as a render key and to de-duplicate.
textstring?Set when you sent a string.
dataobject?Set when you sent an object.
fromAppRemoteUser?Who sent it. Undefined if it came from your own server.
sentAtDateThe sender's clock. Do not order a feed by it alone — clocks disagree.

Reliable and lossy

Messages are sent reliably by default: they arrive, in order, or the send fails. For things that are worthless a moment later — a cursor position, a live reaction burst — pass { reliable: false } and they will be dropped rather than retried under packet loss.

Web
await engine.sendMessage({ x, y }, { reliable: false });

Presence and the audience#

Everyone in the channel is visible, whether they publish or not. Roles come from the token your server minted, so the client cannot lie about them.

engine.audienceCount; // how many are watching
engine.audience; // the viewers
engine.hosts; // whoever is on stage
engine.on('audience-changed', (audience) => {
setViewers(audience.length);
});

Note

People who were already in the channel before you joined are reported too. This is the detail most implementations get wrong: they listen only for arrivals and show an empty audience to anyone who joins a stream in progress.

Reading a participant

PropertyNotes
rolehost | cohost | audience, from the token. See Roles and permissions.
isPublisherFalse for audience. Useful for deciding what to lay out on stage.
attributesThe metadata your server put in the token — display name, avatar, tier. Our own fields are stripped out.
isSpeakingLive, and updated as they speak.

Moving between stage and audience#

Promotion is a decision your server makes — a client cannot promote itself.

  • Your app asks your server to promote a viewer.
  • Your server checks its own rules and calls POST /v1/rooms/:roomName/participants/:identity/role with { "role": "cohost" }. The viewer stays connected; no rejoin.
  • Everyone else sees the audience change.

Issuing a new token and rejoining also works. Both are described in Roles and permissions.

When you want the raw bytes#

sendData is still there for anything that needs its own wire format — a binary protocol, a compressed payload, a codec of your own. Platform frames never appear on data-received, so your bytes and our messages cannot be confused for one another.

See Data messages for that lower level.

Limits worth knowing#

  • A message is capped at roughly 15 KB. Send a URL, not a file.
  • Nothing is retained — no history, no delivery to people who are offline.
  • Messages do not count against your minute quota; only session time does.
  • There is no server-side moderation. Filter on your own backend before broadcasting anything user-generated.