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.
npm install @appsocom/clientJoin a room
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
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
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 itChat, messages and hands
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 EngagementConnection 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").
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:
room.on("token-expiring", async () => room.updateToken(await fetchNewToken()));Big rooms: receive only what's on screen
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
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:
import { runPreflight } from "@appsocom/client";
const report = await runPreflight({ url, token, onStep: (s) => (status.textContent = `Testing ${s}…`) });When someone leaves
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.