Raven Docs

Viewers

Subscribe-only, and with no database row of their own — a viewer is just an SFU participant.

Viewers don't have a membership table like hosts do — anyone with a valid viewer token can join, and "who's watching" is answered by asking the SFU who's currently connected, not by querying a viewers table.

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

Joining

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);
});

Leaving

await stream.leave();

What a viewer can't do

A viewer's RTC token always has publish: false — there's no field on the viewer-token request that changes that, so this isn't something a client can request its way around. See Authentication. A viewer can still send chat messages and reactions with MEMBER scope; only publishing media is off-limits.

Common errors

ErrorWhyFix
PERMISSION_DENIED on room.enableCamera()Every viewer token has publish: false.Expected — a viewer becomes a co-host via addHost(), called by your backend, not by asking client-side.
No disconnect detected when a viewer's app crashesStreams don't yet react to a dropped media session in this phase.Only an explicit stream.leave()/leave call fires live_stream.viewer_left — don't rely on it for abrupt disconnects.