APPSOCOMDocs

Web client

@appsocom/client joins a room from any browser app, with React, Vue, Svelte or no framework at all. It handles the media connection, reconnects, video quality and the room's events. You decide how it looks.

bash
npm install @appsocom/client

Join a room

ts
import { joinRoom, attachTrack } from "@appsocom/client";

const room = await joinRoom({
  url: "https://api.appsocom.dev",
  token,          // from your backend, see Join tokens
  audio: true,    // start with the mic on
  video: true,    // start with the camera on
});

Useful options:

Option
mode: "audio" Voice call. The camera is never opened, and no camera video is received. Screen shares still come through.
simulcast Send each camera in three sizes so every viewer gets one that fits. On by default.
adaptive Lower the video you receive when your connection struggles, then raise it again. On by default.
autoSubscribe { video: false } receives video only for the tiles you ask for (see below).
backgroundEffect, noiseSuppression Background blur or replacement, and mic noise suppression.

Show people

ts
room.on("local-track", (kind, track) => {
  if (kind === "video") attachTrack(myVideo, track);
});

room.on("participant-joined", (p) => addTile(p.id, p.name));
room.on("participant-left", (p) => removeTile(p.id));
room.on("participant-updated", (p) => showMuted(p.id, !p.state.micOn));

room.on("track", (p, kind, track) => {
  if (kind === "video") {
    attachTrack(tile(p.id).video, track);
    room.fitVideoQuality(p.id, tile(p.id).video); // asks for the size this tile needs
  }
  if (kind === "screenVideo") attachTrack(screenVideo(p.id), track);
  // Remote audio plays by itself.
});

Controls

ts
micButton.onclick = () => room.toggleMic();
camButton.onclick = () => room.toggleCamera();
screenButton.onclick = () => room.toggleScreenShare(); // must run inside a click handler
leaveButton.onclick = () => room.leave();

room.setAudioOnly(true);               // data saver: no camera video in or out
room.setAudioOutputDevice(deviceId);   // speakers, where the browser supports it

Chat, messages and hands

ts
room.send({ type: "chat", text: "hi" });        // your own app messages to everyone
room.on("message", (data, from) => { /* … */ });
room.setCustomState({ typing: true });          // shows up in p.state.custom for everyone
room.raiseHand();                               // and room.lowerHand()
room.react("👍");                               // see Engagement

Connection and quality

Calls survive Wi-Fi to mobile data switches, a laptop waking up and short outages. The client reconnects by itself for up to 2 minutes, and then emits left("connection-lost").

ts
room.on("connection", (s) => banner.show(s === "reconnecting" ? "Reconnecting…" : null));
room.on("connection-quality", (id, q) => setSignalIcon(id, q)); // "excellent" | "good" | "poor" | "lost"
room.on("active-speakers", (ids) => highlight(ids[0]));        // loudest first
room.on("video-paused", (p, paused) => showBadge(p ? p.id : "me", paused));

For long calls, refresh the token before it expires:

ts
room.on("token-expiring", async () => room.updateToken(await fetchNewToken()));

Big rooms: receive only what's on screen

ts
const room = await joinRoom({ url, token, autoSubscribe: { video: false } }); // audio stays automatic

room.on("participant-joined", (p) => {
  const tile = renderTile(p);
  room.observeVisibility(tile, p.id); // video only while the tile is visible
});

Background blur and noise suppression

ts
const room = await joinRoom({ url, token, backgroundEffect: true, noiseSuppression: true });
room.setBackgroundBlur(true, 12);
room.setBackgroundEffect({ backgroundImage: "/office.jpg" });
room.setNoiseSuppression(false);

These effects run in the sender's browser, so the other people in the call don't need anything. isBackgroundEffectSupported() and isNoiseSuppressionSupported() tell you up front whether a browser can run them.

Pre-call check

runPreflight() checks the camera, mic and connection before someone joins, so they can fix problems first:

ts
import { runPreflight } from "@appsocom/client";
const report = await runPreflight({ url, token, onStep: (s) => (status.textContent = `Testing ${s}…`) });

When someone leaves

ts
room.on("left", (reason) => showEnded(reason));
Reason
"left" you called room.leave()
"kicked" a moderator or your backend removed you
"room-closed" the room was ended
"room-full" the room was at its size limit
"room-locked" the room was locked and you aren't a moderator
"denied" a moderator turned you away from the waiting room
"unauthorized" the token was refused or expired
"connection-lost" the network was gone for longer than the reconnect window

All events

participant-joined, participant-updated, participant-left, track, track-removed, local-track, local-state, permissions, role, muted, hands, chat, chat-deleted, waiting, waiting-list, group-changed, room-state, self, message, connection, media-connection, video-paused, video-quality, connection-quality, active-speakers, token-expiring, recording, reaction, file-shared, file-removed, poll, question, captions, caption, summary, agent-state, transcript, effects, error, left.