livqenoDocs

Live StreamingViewers

Viewers

Viewers watch only. Anyone with a valid viewer token can join.

Viewers are the audience. They watch the host's video, and they can chat and react, but they can't broadcast.

Where this fits: you've created a stream and your host is joining. Now each person who opens your stream page needs a viewer credential from your backend, which their app uses to join.

There's no viewer list to manage in Livqeno. Anyone your backend gives a valid viewer credential to can join. "Who's watching" is simply whoever is connected right now. Whether a stream is public, members-only, or paid is decided by your backend, when it chooses who gets a credential.

Make a viewer token (backend)

On your backend, make one credential per viewer, each time they open the stream page. Use the viewer's id from your own login session, like carol:

const credential = await livqeno.liveStreams.createViewerToken(streamId, 'carol');

What this does:

  • Returns a credential with role: 'VIEWER': an rtc token that can only watch, and a chat token with MEMBER scope.
  • Adds the viewer to the stream's chat, so they can send messages.
  • Sends the live_stream.viewer_joined webhook. This fires when the credential is made, not when the viewer actually connects.
  • Fails with RAVEN_STREAM_VIEWER_LIMIT_EXCEEDED if 100 viewers are already connected. Credentials that were made but never used don't count.

Viewer credentials work while the stream is CREATED or LIVE, so viewers can wait on the page before the host goes live. They fail once the stream has ENDED.

Send the whole credential to the viewer's app, unchanged.

Join and watch (app)

In the viewer's app, join with the credential. A viewer doesn't turn on any devices. They show the host's video when it arrives:

const stream = await LiveStream.join(credentials); // role: 'VIEWER'
 
// Already-live tracks arrive synchronously; new ones fire trackSubscribed.
for (const participant of stream.room.remoteParticipants) {
  for (const track of participant.tracks) {
    if (track.kind === 'camera') track.attach(videoEl);
  }
}
stream.room.on('trackSubscribed', (track, participant) => {
  if (track.kind === 'camera') track.attach(videoEl);
});

If the host hasn't turned on the camera yet, the viewer simply sees nothing until they do. Show a "Waiting for the host…" message until the first video arrives.

Leave

Call leave() when the viewer closes the stream page or navigates away. It disconnects them from the video and the chat. The stream carries on for everyone else:

await stream.leave();

What viewers can't do

  • Viewers can't broadcast. A viewer token always has publish: false. No request field can change it. See Authentication.

  • Viewers can send chat messages and reactions (MEMBER scope).

  • To bring a viewer on stage, your backend calls addHost for them. They then join again with the new host credential.

Common errors

ProblemCauseFix
PERMISSION_DENIED on room.enableCamera()Viewer tokens always have publish: false.This is expected. To let a viewer broadcast, your backend calls addHost().
RAVEN_STREAM_VIEWER_LIMIT_EXCEEDED (403)100 viewers are already connected.Show a "Stream is full" message, and let the viewer try again later.
RAVEN_STREAM_INVALID_STATEThe stream has ENDED.Show "This stream has ended."
A viewer's app crashed, but no leave was recordedDropped connections aren't detected yet.Only the leave endpoint sends live_stream.viewer_left. Don't rely on it for crashes.

Next steps