Enums

String-backed enums used across the API. Both the enum constant and the underlying string value are accepted everywhere โ€” pick whichever style you prefer.

MediaKind

Identifies the kind of media. Used in subscribe / unsubscribe calls.

enum MediaKind { Audio = "audio", Video = "video", // camera Screen = "screen" // bundles screen video and screen audio }

Screen audio is bundled into Screen โ€” there is no separate ScreenAudio kind. Whether screen audio is included is controlled at publish time via PublishScreenOpts.audio.

Example
import { MediaKind } from '@videosdk/js';

await p.subscribe([MediaKind.Audio, MediaKind.Video]);
await p.subscribe(['audio', 'video']);  // strings also work

StreamState

Combined state of a remote stream. Read via stream.streamState; transitions are delivered via the stream's state-changed event. Two sources are merged into one value: server-driven routing state (always present) and client-observed decoder health (RemoteVideoStream / RemoteScreenStream).

Per-stream-type unions โ€” each subtype only ever produces values that make sense for its medium:

ValueSourceMeaningRecoverable
"active"routingBytes flowing, decoding normally.โ€”
"paused"routingServer stopped sending bytes (subscriber called pause(), publisher unpublished, etc.).Yes โ€” call resume() or wait for publisher.
"frozen"decoderDecoder stuck on a frame; user sees a freeze. Detected from WebRTC stats. Video / screen only.Yes โ€” clears when frames flow again.
"stuck"decoderBytes arriving but decoder isn't producing frames. Video / screen only.Yes โ€” clears when decoder recovers.
"ended"routingTrack permanently terminated (publisher unpublished, network died, device unplugged).No โ€” re-subscribe is required.

No separate frozen / unfrozen / stuck / ended events. All transitions flow through the single state-changed event with the corresponding state value.

Bandwidth-driven simulcast layer changes are a separate axis โ€” see MediaQuality and the quality-changed event on RemoteVideoStream / RemoteScreenStream. The stream stays "active" when the layer switches.

Routing vs decoder priority: when both axes have non-default values (e.g. routing is paused AND decoder was frozen at the time), the merged streamState reports the routing value โ€” routing is server-authoritative.

MediaQuality

Receive-side simulcast layer selection. Used in setPreferredQuality (pick preferred layer to receive) and reported back via currentQuality + the quality-changed event.

type MediaQuality = "high" | "medium" | "low" | "auto";
ValueReceiving meaning
"high"Receive the high simulcast layer.
"medium"Receive the middle layer.
"low"Receive the lowest layer.
"auto"SDK picks based on attached element size.

Receive-side only. Publish-side layer count is configured via PublishVideoOpts.maxLayer + multiStream; capture height via resolution; bitrate via bitrateMode / maxBitrate.

"auto" is only an input value (to setPreferredQuality). The reported currentQuality getter and quality-changed event payload always carry a concrete layer โ€” "high", "medium", or "low".

Requires server-side simulcast. If simulcast is disabled in your deployment, these calls are no-ops โ€” only the available layer is used.

VideoCodec

Preferred video codec for publishing. Used in PublishVideoOpts.codec. Best-effort โ€” if the chosen codec isn't supported by the browser or SFU, the SDK falls back silently. Apps that need a guarantee read stream.codec after publish.

enum VideoCodec { H264 = "h264", VP8 = "vp8", VP9 = "vp9", AV1 = "av1", }
ValueWhen to consider
"h264"Widest hardware-encode support; good for mobile battery life.
"vp8"Universal compatibility baseline.
"vp9"Better compression than VP8 at similar quality; good fit for screen share.
"av1"Best compression at the cost of CPU; limited browser support.

DegradationPreference

What to drop first under bandwidth pressure. Used in PublishVideoOpts.degradationPreference. Maps to the WebRTC native RTCDegradationPreference concept; values renamed for brevity.

enum DegradationPreference { Fps = "fps", Resolution = "resolution", Balanced = "balanced", Off = "off", }
ValueBehavior under bandwidth pressureGood fit for
"fps"Drop resolution to maintain framerate.Sports, dance, gaming, anything with motion.
"resolution"Drop framerate to maintain resolution.Slides, screen share, document review.
"balanced"SDK adapts both axes (default).General video calls.
"off"No automatic degradation.Recording / streaming where you want consistent output.

ContentHint

Encoder optimization hint. Used in PublishVideoOpts.contentHint. Mirrors the WebRTC MediaStreamTrack.contentHint property.

enum ContentHint { Motion = "motion", Detail = "detail", Text = "text", }
ValueWhen to use
"motion"High-motion content where smoothness matters more than per-frame sharpness (sports, dance, video calls).
"detail"General sharpness โ€” balanced default for camera content.
"text"High-detail static content (slides, code, shared documents). Encoder optimizes for sharp edges and lower frame rate.

PreCallPhase

Phases of the pre-call diagnostic. Used in PreCallTestOpts.tests to select which to run, and on onProgress events to identify which phase fired.

enum PreCallPhase { Connectivity = "connectivity", // server ping + SFU connect (TURN/protocol detection) Media = "media", // device validation (camera/mic ready, frames flowing) Quality = "quality", // uplink/downlink quality measurement (WHIP/WHEP) }

NetworkQuality

Quality score for a network direction (uplink or downlink) and per-kind (audio / video). Returned in PreCallTestResult.quality.

enum NetworkQuality { Down = "down", // connection lost or no data Bad = "bad", // unusable Poor = "poor", // noticeable issues Fair = "fair", // usable but degraded Good = "good", // good quality Excellent = "excellent", // best quality }

QualityFactor

Reasons a quality score is degraded. Populated in quality.uplink.factors / quality.downlink.factors when the score is Fair or below.

enum QualityFactor { Rtt = "rtt", // round-trip time too high (โ‰ฅ 350ms) PacketLoss = "packet-loss", // packet loss too high (โ‰ฅ 5%) Jitter = "jitter", // jitter too high (โ‰ฅ 50ms) Bitrate = "bitrate", // bitrate too low (< 500 Kbps for video) }

Protocol

Network transport preference. Set via JoinOptions.preferredProtocol. Useful for restrictive networks (corporate firewalls that block UDP).

enum Protocol { UdpOnly = "udp-only", // reject if UDP unavailable; lowest latency UdpOverTcp = "udp-over-tcp", // default โ€” try UDP, fall back to TCP if blocked TcpOnly = "tcp-only", // force TCP from the start (slower but reliable) }

LogLevel

SDK logging verbosity. Set via VideoSDK.setLogLevel(). Affects console / dashboard logging only โ€” does not affect data sent to apps.

enum LogLevel { Silent = "silent", // no SDK logs Error = "error", // errors only Warn = "warn", // errors + warnings Info = "info", // default โ€” high-level lifecycle (join, publish, leave, etc.) Debug = "debug", // verbose โ€” every internal event (use for bug reports) }

ConnectionState

Current connection state of a Room. Read sync via room.state; observe transitions via connection-state-changed. Only three values โ€” idle / connecting are unobservable in our one-step model since VideoSDK.join() returns the Room only after it's connected.

enum ConnectionState { Connected = "connected", // joined and operational; media flowing Reconnecting = "reconnecting", // lost connection, retrying โ€” recovers to Connected or moves to Disconnected Disconnected = "disconnected", // terminal โ€” left, kicked, room closed, or retries exhausted }

DisconnectReason

Why the room transitioned to ConnectionState.Disconnected. Delivered as reason on the connection-state-changed event when the new state is Disconnected (and on the disconnected alias event). Mirrors v0's reason codes 1001โ€“1104, converted from numeric to string for refactor-safety.

enum DisconnectReason { // โ”€โ”€ Server-pushed โ”€โ”€ WebsocketDisconnected = "websocket-disconnected", // v0: 1001 RemovePeer = "remove-peer", // v0: 1002 โ€” peer removed by another participant RemovePeerViewerModeChanged = "remove-peer-viewer-mode-changed", // v0: 1003 RemovePeerMediaRelayStop = "remove-peer-media-relay-stop", // v0: 1004 SwitchRoom = "switch-room", // v0: 1005 RoomClose = "room-close", // v0: 1006 โ€” room ended by host Unknown = "unknown", // v0: 1007 โ€” catch-all (avoid; prefer a specific reason) RemoveAll = "remove-all", // v0: 1008 โ€” bulk remove (e.g. host ended for everyone) MeetingEndApi = "meeting-end-api", // v0: 1009 โ€” server REST API ended the meeting RemovePeerApi = "remove-peer-api", // v0: 1010 โ€” server REST API removed this peer DuplicateParticipant = "duplicate-participant", // v0: 1011 โ€” same participantId joined elsewhere // โ”€โ”€ Local (client-initiated or client-detected) โ”€โ”€ ManualLeaveCalled = "manual-leave-called", // v0: 1101 โ€” app called room.leave() WebsocketConnectionAttemptsExhausted = "websocket-connection-attempts-exhausted", // v0: 1102 โ€” reconnect retries exhausted JoinRoomFailed = "join-room-failed", // v0: 1103 โ€” initial join failed (also surfaces as VideoSDK.join() Promise rejection) SwitchRoomFailed = "switch-room-failed", // v0: 1104 }
Migration note. v0 delivers reasons as { code: 1102, message: '...' } on onMeetingLeft. v1 delivers them as { reason: DisconnectReason.WebsocketConnectionAttemptsExhausted } on connection-state-changed. The set of reasons is unchanged from v0; only the format and delivery channel differ. Numeric-to-string mapping is preserved as comments above for migration.

ServiceState

Run state of a room server-side service โ€” recording, HLS, livestream, transcription. Read via Room.recordingState / hlsState / livestreamState / transcriptionState; transitions are delivered via the matching *-state-changed event.

enum ServiceState { Stopped = "stopped", // not running โ€” ended cleanly, or never started Starting = "starting", // start requested; server is spinning the service up Started = "started", // running โ€” pipeline up; URLs minted Playable = "playable", // HLS only โ€” buffer ready, URLs safe to hand to a player Stopping = "stopping", // stop requested; server is finalizing Failed = "failed", // not running โ€” ended because of an error }
ValueRunningUsed byMeaning
"stopped"NoallNot running โ€” ended cleanly, or never started.
"starting"NoallStart requested; the server is spinning the service up.
"started"YesallRunning. For HLS, pipeline is up and URLs are minted (populated on room.hlsUrls) but not yet playable โ€” the HLS segment buffer is still filling (~6 s).
"playable"YesHLS onlyHLS-specific sub-state. Reached ~6 s after started, when the buffer has filled and the URLs are safe to hand to hls.js. Recording / livestream / transcription never reach this state โ€” their started is already the usable state.
"stopping"YesallStop requested; the server is finalizing.
"failed"NoallNot running โ€” ended because of an error.

Generic lifecycle. A normal run progresses stopped โ†’ starting โ†’ started, then started โ†’ stopping โ†’ stopped. Any active state can transition to failed on error. A fresh start call from failed (or stopped) goes back to starting.

HLS lifecycle extends this with one extra step: stopped โ†’ starting โ†’ started โ†’ playable โ†’ stopping โ†’ stopped. The buffer-fill phase between started and playable is intrinsic to HLS segment delivery and isn't a transition apps can shorten โ€” they listen for playable to know when to call hls.loadSource(room.hlsUrls.playbackUrl).

Failure carries an error. When the state becomes Failed, the corresponding *-state-changed event carries an error describing why the service stopped.
Why Playable is in the shared enum (even though only HLS uses it). Putting it on ServiceState keeps every service on the same single-event channel (*-state-changed) โ€” no separate hls-playable event to learn. Apps that don't care about HLS never see Playable in their recordingState / livestreamState / transcriptionState; apps that do care just add one more branch to their hlsState switch.

ErrorKind

Category of an SDKError. Lets apps write a single handler for an entire class of errors without listing every code. Read via err.kind on every error the SDK surfaces (Promise rejections, event payloads).

enum ErrorKind { Auth = "auth", // token / API key / permissions / role / account-level Media = "media", // camera / mic / screen / encoder / device-level Network = "network", // signaling / ICE / DTLS / connectivity Server = "server", // server-side rejects / room-level limits / unexpected server error Config = "config", // bad JoinOptions / unsupported parameter combination Permission = "permission", // browser / OS permission denials (camera, mic, screen-share) }
Boundary calls. A few errors sit on category fences (e.g. MEDIA_PERMISSION_DENIED โ€” is it media or permission?). The SDK picks one and documents the choice with the error code definition. The boundary, once set, is stable across releases.

LocalEvent

Event names fired on LocalParticipant. Both the enum value and the underlying string work in .on() / .off(). Three categories: publish lifecycle (your own publishes), incoming requests from other participants, and pin events.

enum LocalEvent { // Publish lifecycle โ€” fires for explicit publishX(), JoinOptions async publish, // pre-call auto-promote, and mid-call runtime failures (camera unplugged, encoder died). // Stream payload (LocalPublication) for success; PublishFailure for failure (initial OR mid-call). StreamPublished = "stream-published", StreamUnpublished = "stream-unpublished", StreamPublishFailed = "stream-publish-failed", // Incoming publish/unpublish requests from other participants. Payload includes // `kind` discriminator + `accept` / `reject` callbacks. accept() publishes/unpublishes; // reject() silently declines. // stream-publish-requested โ†’ PublishRequest { kind, from, accept, reject } // stream-unpublish-requested โ†’ UnpublishRequest { kind, from, accept, reject } StreamPublishRequested = "stream-publish-requested", StreamUnpublishRequested = "stream-unpublish-requested", // You were pinned/unpinned (self-pin or someone pinned you). // Payload: { by: LocalParticipant | RemoteParticipant, kinds: MediaKind[] } Pinned = "pinned", Unpinned = "unpinned", }
Publish events fire for every path โ€” explicit me.publishVideo(), async JoinOptions.publishVideo, and pre-call auto-promote. stream-published delivers a LocalPublication with kind + the matching video? / audio? / screen? stream slot. stream-unpublished delivers { kind: MediaKind }. stream-publish-failed delivers a PublishFailure with { kind, error } โ€” covers both initial publish failures and mid-call runtime failures (camera unplug, encoder die). For explicit calls, the Promise also resolves/rejects โ€” both channels work; app picks.

Operational events (frozen, unfrozen, stuck, ended, silent-detected) fire on the stream object itself โ€” see LocalVideoStream / LocalAudioStream / LocalScreenStream.

Example
import { LocalEvent, MediaKind } from '@videosdk/js';

me.on(LocalEvent.StreamPublishRequested, async (req) => {
  // req: PublishRequest โ€” { kind, from, accept, reject }
  const label = req.kind === MediaKind.Video ? 'camera' : 'microphone';
  if (confirm(`${req.from.displayName} asks you to enable ${label}`)) {
    const pub = await req.accept();   // resolves with LocalPublication
    if (pub.video) pub.video.attach(localCamEl);
  } else {
    req.reject();
  }
});
me.on('stream-publish-requested', async (req) => await req.accept());  // identical

RemoteEvent

Event names fired on RemoteParticipant. Both the enum value and the underlying string work in .on() / .off(). Three categories: publish notifications (remote published a kind), subscribe-completion events (your subscribe got the stream), pin events.

enum RemoteEvent { // Publish notifications โ€” fire when remote publishes/unpublishes (regardless of subscribe state). // Fire retroactively for already-published kinds. Payload: { kind: MediaKind }. StreamPublished = "stream-published", StreamUnpublished = "stream-unpublished", // Subscribe-completion โ€” fires as each kind's pipeline becomes ready. // Payload: Subscription with kind + the matching video?/audio?/screen? stream. StreamSubscribed = "stream-subscribed", // Unsubscribe โ€” fires when subscribe ends (you unsubscribed OR publisher unpublished). // Payload: { kind: MediaKind }. StreamUnsubscribed = "stream-unsubscribed", // This participant was pinned/unpinned by anyone (you or another participant). // Fires retroactively on join. Payload: { by, kinds }. Pinned = "pinned", Unpinned = "unpinned", }
Example โ€” both forms work
import { RemoteEvent, MediaKind } from '@videosdk/js';

p.on(RemoteEvent.StreamPublished, ({ kind }) => p.subscribe(kind));
p.on('stream-published',          ({ kind }) => p.subscribe(kind));   // identical

p.on(RemoteEvent.StreamSubscribed, sub => sub.video?.attach(el));
p.on('stream-subscribed',          sub => sub.video?.attach(el));     // identical

See also: Types LocalParticipant RemoteParticipant