livqenoDocs

SDKsLivqeno Client

Livqeno Client

@livqeno/live — calls and chat in one object, plus LiveStream for live streaming.

@livqeno/live is for browser apps that use calls and chat together, like a meeting app with a chat panel, or a live stream with a chat sidebar. It puts both behind one object, so you set up, join, and clean up in one place. It also has LiveStream for live streaming.

It runs in the browser and holds only tokens: an RTC token and a chat token, both made by your backend with the TypeScript, Python, Go, Java or PHP SDK. It never sees your API key.

The API key can't live in a browser, so one small route on your server makes the tokens. Build your token endpoint has it in TypeScript, Python, Go, Java, PHP and Dart.

  • Use it when your app needs both calls and chat.
  • Use @livqeno/rtc or @livqeno/chat alone when you need only one. It's a smaller download, and you skip code you don't use.

It's a thin wrapper. livqeno.rtc is a real RTCClient from @livqeno/rtc, and livqeno.chat is a real ChatClient from @livqeno/chat. Everything documented for those packages works here.

Install

pnpm add @livqeno/live

Set up

First, your backend makes the tokens your page needs: an RTC token (POST /v1/rooms/{roomId}/rtc-tokens), a chat token (POST /v1/chat/tokens), or both. Your page fetches them from your own endpoint.

Then, in your browser app, pass only the credentials for what you use:

import { Livqeno } from '@livqeno/live';
 
const livqeno = new Livqeno({ token, endpoint });                          // calls only
const livqeno = new Livqeno({ chatToken, chatApiUrl });                    // messaging only
const livqeno = new Livqeno({ token, endpoint, chatToken, chatApiUrl });   // both
OptionForNotes
tokenRTCFrom your backend. Never make one in the browser.
endpointRTCThe endpoint from the same token response. Required with token.
iceServersRTCPass it as-is. Never build your own.
telemetryRTCDefault true. Best-effort. Never blocks a call.
telemetryUrlRTCFrom the same token response.
autoReconnectRTCDefault true.
chatTokenChatFrom POST /v1/chat/tokens.
chatApiUrlChatThe apiUrl from the chat-token response.
chatUrlChatWorked out from chatApiUrl if you leave it out.
onChatTokenExpiringChatReturn a new token and the connection reconnects itself.
logLevelBoth

If you set chatToken, you also need chatApiUrl or telemetryUrl (they're usually the same host). Without either, the SDK throws right away.

Basic use

A typical flow: the user joins the call, turns on camera and mic, opens the chat, and later leaves. leave() exits the call but keeps chat open (useful if people keep chatting after the call ends). dispose() closes everything, for example when the user closes the page.

const room = await livqeno.join('room_123');
await room.enableCamera();
await room.enableMicrophone();
 
await livqeno.chat!.connect({ room: 'room_123' });
await livqeno.chat!.sendMessage({ text: 'Hello' });
 
await livqeno.leave();
await livqeno.dispose();

For anything else, use the real clients directly:

livqeno.rtc?.getDevices('videoinput');
livqeno.chat?.messages.list({ limit: 50 });

livqeno.chat is undefined if you passed no chat credentials. That's why the examples use !. Check it in your code.

Events

Listen on the room and the chat client directly. There's no combined event emitter:

const room = await livqeno.join('room_123');
 
room.on('participantJoined', (p) => console.log(p.identity));
livqeno.chat?.on('message', (m) => console.log(m.text));
CallReturns
room.on()The room, so you can chain calls
chat.on()A function that unsubscribes

See RTC events and Chat events.

Live streaming

A live stream is a call and a chat working together: the host's video plus a chat for viewers. LiveStream joins both in one step.

Your backend decides each person's role before they join. It calls addHost() for a host, or createViewerToken() for a viewer, and sends the result to the page. In your browser app:

import { LiveStream } from '@livqeno/live';
 
const live = await LiveStream.join(credentials);   // from addHost() or createViewerToken()
 
if (live.isHost) {
  await live.room.enableCamera();
  await live.room.enableMicrophone();
}
 
live.chat?.on('message', (m) => appendChat(m));
await live.react('❤️');
await live.leave();
MemberType / what it does
streamIdstring
identitystring | undefined
role'HOST' | 'CO_HOST' | 'VIEWER'
isHostboolean
delivery'rtc' | 'hls' — how this viewer is watching: a live call, or an HLS video stream
roomRoom, a normal RTC room
chatChatClient | undefined
react(emoji)Adds a reaction to the stream's main chat message
raiseHand() / lowerHand()A viewer asks to come on stage, or cancels that request. Needs the chat field on the join credentials.
switchRole(credentials)Switches to a new role using new credentials, without leaving and rejoining
leave()Leaves both the room and the chat

react() throws if the credentials had no chat field. Reactions are stored in chat, so without chat there's nowhere to put them.

HLS playback

A BROADCAST stream's viewer gets delivery: 'hls' instead of joining a Room. Call attachPlayer() with a <video> element, and the stream plays there:

if (live.delivery === 'hls') {
  await live.attachPlayer(videoElement);
}
 
// Later, or automatically on leave():
live.detachPlayer();
live.setPreferredQuality('medium'); // 'auto' | 'high' | 'medium' | 'low'
live.preferredQuality; // 'medium'

setPreferredQuality() picks the video quality this viewer gets, whether they're on a live call or watching HLS. 'auto' lets Livqeno choose for them. The setting carries over if they later switchRole() into a new room. More on scaling a large audience in Audience scale.

Events

live.on(event, handler) listens for things that happen during the stream. Calling it returns a function you can call later to stop listening:

const unsubscribe = live.on('stage', (event) => {
  // event.action: 'raise_hand' | 'lower_hand' | 'invited' | 'promoted' | 'demoted'
  if (event.action === 'invited' && event.forMe) showGoLivePrompt();
});
 
live.on('roleChanged', (role, room) => setRole(role));
live.on('transcriptionSegment', (segment) => appendCaption(segment));
live.on('disconnected', (reason) => showReconnecting());
 
unsubscribe(); // stop listening
EventPayload
stageLiveStreamStageEvent — someone raised or lowered their hand, or the server invited, promoted, or demoted them. See Stage.
roleChanged(role, room) — fires after switchRole() finishes
permissionsChangedThis participant's RTC permissions changed
localTrackMutedByServerA moderator muted one of this participant's tracks
disconnectedreason — the call disconnected
transcriptionSegmentLiveStreamTranscriptionSegment — one piece of a live caption. It may still be changing, or it may be the final version. See Live Transcription.
transcriptionStateChangedLiveStreamTranscriptionState — whether transcription is starting, running, or has stopped in this room
agentStateChangedAn AI voice agent in the room changed state (listening, thinking, or speaking). See AI Voice Agents.

Errors

Errors come from the underlying packages. Use their checks. A common one to handle is TOKEN_EXPIRED: ask your backend for a fresh token and try again.

import { isRTCError } from '@livqeno/rtc';
import { isLivqenoChatError } from '@livqeno/chat';
 
try {
  await livqeno.join('room_123');
} catch (error) {
  if (isRTCError(error) && error.code === 'TOKEN_EXPIRED') await refresh();
}

All codes: Errors.

Example

examples/live-streaming-demo is a two-browser demo built on LiveStream: one host tab publishes, one viewer tab watches, with live chat and reactions.

Common mistakes

MistakeWhat you'll seeFix
chatToken without chatApiUrl (or telemetryUrl)The SDK throws right away.Pass apiUrl from the chat-token response as chatApiUrl.
Using livqeno.chat! when no chat credentials were passedlivqeno.chat is undefined.Pass chat credentials, or check livqeno.chat first.
Calling react() with credentials that have no chat fieldIt throws.Reactions live in chat; make sure the credentials include chat.
Calling leave() and expecting chat to closeChat stays connected.Call dispose() to close both.

Next steps