VideoSDK namespace

Static entry point for the SDK. Use it to join rooms and enumerate input/output devices. Everything else hangs off the Room object returned by join.

Blueprint

Full surface at a glance β€” every static method on this namespace, plus pre-call surface (permissions, devices, tests, preview streams).

namespace VideoSDK { // === Connect === join(opts: JoinOptions): Promise<Room>; // === Permissions (stateless) === checkPermissions(kinds: MediaKind[]): Promise<Map<MediaKind, boolean>>; requestPermissions(kinds: MediaKind[]): Promise<Map<MediaKind, boolean>>; // === Device enumeration (stateless) β€” returns typed device info === getCameras(): Promise<CameraDeviceInfo[]>; getMicrophones(): Promise<MicrophoneDeviceInfo[]>; getSpeakers(): Promise<SpeakerDeviceInfo[]>; // === Pre-call test β€” single method runs connectivity / media / quality === // Returns a StoppablePromise β€” apps can .stop() to skip remaining phases. runPreCallTest(opts: PreCallTestOpts): StoppablePromise<PreCallTestResult>; // === Local pre-call streams (singleton-tracked per kind) === // Acquires the device, returns a LocalXStream. The next VideoSDK.join() auto-promotes it. // Throws STREAM_ALREADY_EXISTS if one already exists for that kind. // ⚠ Under reconsideration β€” see Q18 β€” Pre-call preview lifecycle. createVideoStream(opts: PublishVideoOpts): Promise<LocalVideoStream>; createAudioStream(opts: PublishAudioOpts): Promise<LocalAudioStream>; // Read-only sync getters β€” the local stream that will be published on next join readonly videoStream: LocalVideoStream | null; readonly audioStream: LocalAudioStream | null; // === Processors (sticky, applies to all camera / mic streams of the local participant) === // Set once β€” automatically applied to pre-call streams, publish, unpublish/republish, setInputDevice swap. applyVideoProcessor(processor: VideoFrameProcessor): Promise<void>; removeVideoProcessor(): Promise<void>; applyAudioProcessor(processor: AudioFrameProcessor): Promise<void>; removeAudioProcessor(): Promise<void>; // Read-only sync getters β€” the currently active processors (or null) readonly videoProcessor: VideoFrameProcessor | null; readonly audioProcessor: AudioFrameProcessor | null; // === Events === on(event: "devices-changed", listener: () => void): void; off(event: "devices-changed", listener: () => void): void; // === Logging === setLogLevel(level: LogLevel): void; // global; default LogLevel.Info }

Methods

join async

Connects to a room and returns a fully-connected Room. The Promise resolves only after the join handshake is complete β€” there is no "before-join" window where listener registration could be silently dropped.

join(opts: JoinOptions): Promise<Room>
Parameters
  • opts β€” JoinOptions with token, roomId, optional name/metadata, optional pre-warmed event channels, and optional waiting-lobby hooks (onWaiting, signal) for tokens with joinPolicy.mode === 'ask'.
Returns

Promise that resolves with a connected Room. Rejects with an SDKError. See Errors β†’ VideoSDK.join() for the full inventory of 21 codes this method can reject with (including the four lobby codes: ENTRY_DENIED, ENTRY_TIMEOUT, ENTRY_RATE_LIMITED, INVALID_ENTRY_CLAIM).

Async publish failures (camera busy, mic in use, etc.) are not in this inventory. Publish runs async after join() resolves (decision 36) β€” failures fire on me.on('stream-publish-failed', failure) instead. See Errors β†’ stream-publish-failed.

Example β€” minimal join (no publish)
import { VideoSDK } from '@videosdk/js';

try {
  const room = await VideoSDK.join({
    token: getAuthToken(),
    roomId: 'team-standup',
    name: 'Alice',
    metadata: { role: 'host' },
    subscribeEvents: ['active-speaker-changed'],  // pre-warm lazy events
  });
  console.log('connected', room.localParticipant.id);
} catch (err) {
  if (err.kind === ErrorKind.Auth)        showLoginUI();             // any auth failure
  else if (err.code === 'TIMEOUT')        showNetworkError();
  else if (err.code === 'ROOM_FULL')      showRoomFullToast();
  else                                    showGenericError(err);
}
Example β€” one-shot join with publish (no pre-call)
const cameras = await VideoSDK.getCameras();
const mics    = await VideoSDK.getMicrophones();

const room = await VideoSDK.join({
  token: getAuthToken(),
  roomId: 'team-standup',
  name: 'Alice',
  publishVideo: { device: cameras[0], resolution: 'h720' },
  publishAudio: { device: mics[0], noiseSuppression: true },
});
// Connected AND publishing β€” single round-trip.
Example β€” pre-call β†’ join (auto-promote)
const cameras = await VideoSDK.getCameras();

// Pre-call screen
const previewVideo = await VideoSDK.createVideoStream({ device: cameras[0], resolution: 'h720' });
const previewAudio = await VideoSDK.createAudioStream({ device: (await VideoSDK.getMicrophones())[0] });
previewVideo.attach(previewEl);
await VideoSDK.testNetwork();

// Join β€” local streams auto-promote
const room = await VideoSDK.join({ token, roomId, name });
// previewVideo === room.localParticipant.video (no re-acquisition, no flicker)
Example β€” waiting lobby (token has joinPolicy.mode === 'ask')
// Same join() call. With an 'ask' token, the Promise stays pending while the
// joiner is in the lobby β€” onWaiting fires once so you can render the lobby UI.
const controller = new AbortController();
ui.onCancelClick = () => controller.abort();   // explicit "Cancel" button

try {
  const room = await VideoSDK.join({
    token,
    roomId,
    name: 'Patient 9',
    onWaiting: () => ui.showLobbyScreen('Waiting for the doctor…'),
    signal:    controller.signal,
  });
  ui.showCallScreen(room);
} catch (err) {
  if (err.code === 'ENTRY_DENIED')        ui.showDeniedScreen();
  else if (err.code === 'ENTRY_TIMEOUT')  ui.showTimeoutScreen();
  else if (err.name === 'AbortError')     ui.showHome();
  else                                    showGenericError(err);
}
// Tab close needs no code β€” the SDK's socket disconnect cancels the lobby entry.
// Full reference: waiting-lobby.html

Related: Q14 β€” One-step vs two-step join Q15 β€” Pre-warm signaling

getCameras async

Lists available cameras. Returns typed CameraDeviceInfo objects (not plain MediaDeviceInfo) so that camera references are nominally distinct from microphones / speakers β€” strongly-typed languages catch passing the wrong kind at compile time.

getCameras(): Promise<CameraDeviceInfo[]>
Example
const cameras = await VideoSDK.getCameras();
const front = cameras.find(c => c.label.includes('FaceTime'));
await me.video.setInputDevice(front);   // pass the typed object

getMicrophones async

Lists available microphones. Returns typed MicrophoneDeviceInfo objects.

getMicrophones(): Promise<MicrophoneDeviceInfo[]>
Example
const mics = await VideoSDK.getMicrophones();
console.log(mics.map(m => m.label));

getSpeakers async

Lists available output devices. Returns typed SpeakerDeviceInfo objects. Pass one to room.setOutputDevice() to route remote audio to a specific speaker / headset.

getSpeakers(): Promise<SpeakerDeviceInfo[]>
Example
const speakers = await VideoSDK.getSpeakers();
const headset = speakers.find(s => s.label.toLowerCase().includes('headset'));
await room.setOutputDevice(headset);

Permissions

checkPermissions async

Query the browser's current permission state for the listed kinds. Doesn't prompt the user β€” just reads cached permission state via navigator.permissions.query().

checkPermissions(kinds: MediaKind[]): Promise<Map<MediaKind, boolean>>
Returns

Map keyed by kind, value is true if previously granted, false if denied or not yet prompted.

Example
const perms = await VideoSDK.checkPermissions([MediaKind.Audio, MediaKind.Video]);
if (!perms.get(MediaKind.Video)) showRequestUI();

requestPermissions async

Prompts the user to grant access to the listed kinds. Acquires a temporary getUserMedia stream just to trigger the permission dialog, then releases it. Use this if you want explicit "allow access" UX before showing pre-call.

requestPermissions(kinds: MediaKind[]): Promise<Map<MediaKind, boolean>>
Returns

Map keyed by kind with the result of the prompt. Doesn't reject on denial β€” denied permissions return false.

Example
const granted = await VideoSDK.requestPermissions([MediaKind.Audio, MediaKind.Video]);
if (granted.get(MediaKind.Video)) {
  const preview = await VideoSDK.createVideoStream({ deviceId: 'default' });
  preview.attach(previewEl);
}

Pre-call test

runPreCallTest async

Comprehensive pre-call diagnostic β€” runs server reachability + SFU connection (connectivity), device validation (media), and uplink/downlink quality measurement (quality) in a single shared session. Returns a stoppable promise β€” call .stop() to skip remaining phases.

runPreCallTest(opts: PreCallTestOpts): StoppablePromise<PreCallTestResult>
Returns

A StoppablePromise β€” same shape as a Promise (you can await it) plus a .stop() method to abort early. Resolves with a PreCallTestResult containing per-phase results, errors, and metadata. Rejects only on fatal init failures (invalid token, browser unsupported, etc.).

Single shared connection. The SFU connection established for connectivity is reused for the quality test β€” no double handshake. If a local stream exists (from createVideoStream / createAudioStream), the test reuses it β€” no double device acquisition.
Example β€” simple full diagnostic
const result = await VideoSDK.runPreCallTest({ token });

if (result.quality?.audioOnly) {
  showWarning('Network is too slow for video β€” audio-only recommended');
}
if (result.connectivity?.turnUsed) {
  showWarning('Using TURN relay β€” may add latency');
}
Example β€” lobby UI with progress, stop on join
const test = VideoSDK.runPreCallTest({
  token,
  onProgress: ({ phase, data }) => {
    if (phase === 'media')        renderDeviceStatus(data);
    if (phase === 'connectivity') renderServerStatus(data);
  },
  onError: ({ code, message, phase }) => {
    showWarning(`[${phase}] ${message}`);
  },
});

joinButton.onclick = async () => {
  test.stop();                  // skip remaining quality test
  const result = await test;    // resolves with partial results, aborted: true
  await VideoSDK.join({ token, roomId, name });   // local streams auto-promote
};
Example β€” quick connectivity-only check
const result = await VideoSDK.runPreCallTest({
  token,
  tests: [PreCallPhase.Connectivity],
});

console.log('Region:', result.connectivity?.region);
console.log('Protocol:', result.connectivity?.protocol);
console.log('TURN used:', result.connectivity?.turnUsed);

Concurrency. Only one runPreCallTest can be in flight at a time. Calling it while one is running rejects with TEST_ALREADY_RUNNING. Call .stop() on the existing test first.

Error handling. Three layers: (1) Promise rejection for fatal init errors (invalid token, browser not supported); (2) onError callback for non-fatal phase errors as they happen; (3) result.errors[] array for post-test inspection. See PreCallTestResult.

Local pre-call streams (singleton-tracked)

Pre-call local streams. The SDK keeps an internal slot per kind; join auto-promotes whichever local streams exist into room.localParticipant.video / audio. No flicker β€” the same track that drove the pre-call preview becomes the published one.

One local stream per kind. Calling createVideoStream while one already exists throws STREAM_ALREADY_EXISTS. To swap configurations, call stop() on the existing one first. To swap devices, use the existing stream's setInputDevice() instead.
Under reconsideration. The singleton + auto-promote design is being weighed against a separate render-only precallPreview({ render }) method with auto-dispose on join. See Q18 β€” Pre-call preview lifecycle for the comparison and the use cases driving the discussion.

createVideoStream async

Acquires the camera and returns a LocalVideoStream. The next join auto-promotes this stream β€” same instance becomes room.localParticipant.video.

createVideoStream(opts: PublishVideoOpts): Promise<LocalVideoStream>
Returns

Resolves with the LocalVideoStream. Rejects on permission denied, device busy, no such device, or if a video stream already exists (STREAM_ALREADY_EXISTS).

Example β€” full pre-call flow
const cameras = await VideoSDK.getCameras();
const mics    = await VideoSDK.getMicrophones();

// Show pre-call UI with live local stream
const camStream = await VideoSDK.createVideoStream({ device: cameras[0], resolution: 'h720' });
const micStream = await VideoSDK.createAudioStream({ device: mics[0] });

camStream.attach(previewEl);
levelMeter.bind(() => micStream.audioLevel);
await camStream.setInputDevice(cameras[1]);   // hot-swap to another camera

await VideoSDK.runPreCallTest({ token });

// Join β€” local streams auto-promote, no flicker
const room = await VideoSDK.join({ token, roomId, name });
// camStream === room.localParticipant.video

Related open questions: Q18 β€” Pre-call preview lifecycle (singleton vs render-only)

createAudioStream async

Acquires the microphone and returns a LocalAudioStream. The next join auto-promotes this stream.

createAudioStream(opts: PublishAudioOpts): Promise<LocalAudioStream>

videoStream / audioStream readonly

Read-only sync getters that return the currently active local stream (or null). Useful for debugging, conditional UI ("show pre-call screen if a local stream exists"), and React component integration.

readonly videoStream: LocalVideoStream | null; readonly audioStream: LocalAudioStream | null;

After join, the same stream instance is also reachable via room.localParticipant.video / room.localParticipant.audio. While in pre-call (no join yet), use these getters.

Processors

Sticky, per-kind frame transform pipelines for camera and microphone. Set once β€” applies to every camera / mic stream of the local participant for as long as it's set. Survives publish, unpublish, re-publish, setInputDevice swaps, and reconnects.

No screen processor in v1. Screen pixels rarely benefit from per-frame transformation. Can be added in a future release if real demand emerges.
Pre-call applies automatically. If a processor is set before any stream exists (e.g. before join or createVideoStream), the SDK stores it and applies it lazily when the next camera / mic stream comes online. precallPreview picks it up automatically β€” no need to pass processor in opts.

The VideoFrameProcessor / AudioFrameProcessor interface is a single-method contract β€” the app implements process(frame). The SDK calls it once per captured frame; the returned frame is what gets encoded and published. App owns the lifecycle of its own resources (models, canvases, WASM, etc.) β€” the SDK never touches them.

applyVideoProcessor async

Sets the active video processor. Replaces any previously-set one. Applies immediately to the current me.video (if publishing) and to any future camera stream. Resolves once the processor is in place and the frame pipeline is wired through it.

applyVideoProcessor(processor: VideoFrameProcessor): Promise<void>
Example β€” toggle virtual background mid-call
// User clicks "Beach background"
const beachBg: VideoFrameProcessor = {
  async process(frame) {
    const mask = await segmentationModel.segment(frame);
    return composite(frame, mask, beachImage);
  },
};
await VideoSDK.applyVideoProcessor(beachBg);
// me.video now publishes with beach background β€” no flicker, no unpublish

applyAudioProcessor async

Sets the active audio processor. Replaces any previously-set one. Applies immediately to me.audio (if publishing) and to any future mic stream.

applyAudioProcessor(processor: AudioFrameProcessor): Promise<void>
Example β€” noise suppression for the whole call
const noiseSuppress: AudioFrameProcessor = {
  process(frame) { return rnnoise.run(frame); },
};
await VideoSDK.applyAudioProcessor(noiseSuppress);

removeVideoProcessor / removeAudioProcessor async

Removes the current processor for that kind. Streams revert to publishing raw camera / mic frames. Resolves once the SDK has unwired the processor β€” subsequent frames flow without per-frame app code.

removeVideoProcessor(): Promise<void> removeAudioProcessor(): Promise<void>

After this resolves, the SDK no longer calls processor.process(frame). App can dispose its own resources (models, WebGL contexts, WASM memory) whenever it wants β€” the SDK doesn't manage them.

videoProcessor / audioProcessor readonly

Read-only sync getters that return the currently active processor (or null). Useful for conditional UI ("show 'Effect: Beach' badge if a processor is active") and debugging.

readonly videoProcessor: VideoFrameProcessor | null; readonly audioProcessor: AudioFrameProcessor | null;

Events

devices-changed

Fires when the system's set of media devices changes β€” e.g. user plugs in a USB headset, an HDMI display is connected, or a Bluetooth device pairs. Apps that show device dropdowns should re-fetch via getCameras / getMicrophones / getSpeakers when this fires.

VideoSDK.on("devices-changed", listener: () => void): void
Example
VideoSDK.on('devices-changed', async () => {
  cameraSelect.options = await VideoSDK.getCameras();
  micSelect.options    = await VideoSDK.getMicrophones();
});

Pre-call lifecycle & auto-promote rules

Per kind (video / audio / screen), join resolves what to publish in this order:

  1. Local stream exists (from createVideoStream / createAudioStream) β†’ auto-promoted; same instance becomes me.video / me.audio.
  2. Else if JoinOptions.publishVideo / .publishAudio is set β†’ SDK acquires device and publishes during the join handshake (one server round-trip).
  3. Else β†’ no publish for that kind. App can call me.publishX(...) later.

Conflict: if both a local stream AND JoinOptions opts are provided for the same kind, join rejects with CONFLICTING_PUBLISH_CONFIG. To resolve, call VideoSDK.videoStream.stop() (or audioStream.stop()) first.

Logging

setLogLevel

Sets the SDK's logging verbosity globally. Affects all SDK logs (console + dashboard / telemetry). Apps usually call this once at startup; can also dial up to Debug when collecting bug reports.

setLogLevel(level: LogLevel): void
Parameters
  • level β€” see LogLevel for values. Default LogLevel.Info.
Example
import { VideoSDK, LogLevel } from '@videosdk/js';

// At app startup β€” silence SDK logs in production
VideoSDK.setLogLevel(LogLevel.Warn);

// Bump up when collecting a bug report
debugButton.onclick = () => VideoSDK.setLogLevel(LogLevel.Debug);

See also: Room JoinOptions LocalParticipant