Livqeno Docs

Rooms & Participants

Participant state, device selection, sending data, and the track model — on Web, React, React Native, and Flutter.

Participants

room.localParticipant.identity; // this session's own identity
room.localParticipant.tracks;   // LocalTrack[] currently published
 
room.remoteParticipants;        // RemoteParticipant[]
participant.identity;
participant.metadata;           // opaque, set when the token was minted
participant.tracks;             // RemoteTrack[] currently subscribed
 
room.on('participantJoined', (participant) => {});
room.on('participantLeft', (participant) => {});

metadata is whatever your backend attached when it minted the token — Livqeno never inspects or interprets it.

Device selection

Web-only — React Native and Flutter select devices through the OS, not a JavaScript device-enumeration API.

const devices = await client.getDevices();
// { deviceId, label, kind }[] — kind: 'videoinput' | 'audioinput' | 'audiooutput'
// labels are populated only once permission has been granted at least once
 
await room.setCameraDevice(deviceId);
await room.setMicrophoneDevice(deviceId);
await room.setSpeakerDevice(deviceId); // where the browser supports setSinkId — not Safari; throws DEVICE_NOT_FOUND there
 
const unsubscribe = client.onDeviceChange(() => {
  // re-enumerate — a camera or mic was connected or disconnected
});

On React Native, the closest equivalent is switching front/rear camera — see Flutter's switchCamera() for the Dart SDK's version of the same phone-only concern.

Sending data

await room.sendData('hello'); // string or Uint8Array
 
room.on('dataReceived', (payload, participant) => {
  console.log(new TextDecoder().decode(payload), participant?.identity);
});

Works in an empty room with no camera or microphone published. The first sendData() has to negotiate a data channel, which takes one round trip; the SDK does that itself and resolves once your payload is on the wire. Anything sent while the channel is still coming up is queued and delivered in order, so you never have to publish media first or retry.

Receiving needs a listener, and the listener is what sets it up. Livqeno fans data out over each recipient's own channel, so subscribing to dataReceived is what provisions yours — do it once after joining, before you expect anything to arrive. A message sent to a participant whose channel is still coming up is not delivered to them: sendData() reaches whoever is connected at the moment it goes out, and is not a queue that waits for stragglers.

Requires the token's publishData grant — throws PERMISSION_DENIED otherwise. Deliberately minimal: no reliability options, no per-participant targeting. If you need routed, ordered, or persisted messages between participants, that's what Chat is for — it's a first-class service, not a fallback bolted onto the data channel.

Track model

Track   (base: kind, mediaStreamTrack, mediaStream, isMuted, attach(), detach())
  ├── LocalTrack   (+ mute(), unmute(), stop())
  └── RemoteTrack

track.attach() / track.attach(existingElement) and track.detach() are the SDK's only media-element helpers — it's an SDK, not a UI component library:

room.on('trackSubscribed', (track, participant) => {
  videoElement.appendChild(track.attach());
});
 
room.on('trackUnsubscribed', (track) => {
  track.detach().forEach((el) => el.remove());
});

Common errors

ErrorWhyFix
PERMISSION_DENIED on sendData()Token was minted without publishData.Set publishData: true when calling tokens.create().
DEVICE_NOT_FOUND on setSpeakerDevice()Browser doesn't support setSinkId (Safari).Fall back to system output selection — there's no workaround.
  • Audio & Video — enabling the devices whose tracks show up here.
  • RTC → Overview — the full event list.
  • Chat — for anything data messages are too minimal for.