Manage Resources

Client Preferences

View as MarkdownOpen in Claude

client.preferences holds per-client defaults the SDK reads when no per-call options override them: which mic / camera to use, whether to receive video by default, ICE / recovery tuning, codec ordering, and custom userVariables attached to every call. Preferences live in the browser, optionally persist to localStorage, and are distinct from per-User configuration (which lives on the platform — see Users).

This page covers how preferences fit into the SDK lifecycle. For the full property list, see ClientPreferences.

Defaults vs. per-call overrides

client.preferences ← defaults
client.dial(dest, options) ← per-call overrides win

Anything set on preferences applies to every subsequent dial() that doesn’t pass a competing field. Per-call options always win:

1client.preferences.receiveVideo = true; // receive remote video by default
2
3// This one call stays audio-only:
4await client.dial("/private/team", { receiveVideo: false });

Use preferences for app-wide defaults (codec ordering, a tier-wide userVariables payload). Use per-call options for situational values.

For example, codec ordering is an app-wide default — the array is a priority list of codec names, and it’s overridable per call:

1// Prefer Opus, fall back to G.711
2client.preferences.preferredAudioCodecs = ["opus", "PCMU"];
3
4// One call insists on G.711:
5await client.dial("/private/team", { preferredAudioCodecs: ["PCMU"] });

Common preferences

The full surface is documented in the ClientPreferences reference. These are the ones most apps touch, with their code defaults:

PreferenceDefaultControls
receiveVideofalsewhether to accept inbound video on a call
preferredAudioCodecs[]audio codec priority order
connectionTimeout10 (s)WebSocket connect timeout
degradationBitrateThreshold150 (kbps)bitrate below which video auto-disables
degradationRecoveryThreshold300 (kbps)bitrate above which video re-enables

Persistence

By default, preferences live in memory only. Set savePreferences: true to hydrate from localStorage on startup and write back on every setter:

1const client = new SignalWire(provider, { savePreferences: true });

The following details are persisted: timeouts, ICE / recovery tuning, codec preferences, stats and device-management flags, and userVariables.

Device selections persist separately, and are on by default. Independent of savePreferences, the device controller writes your mic / camera / speaker selections to localStorage (keyed by deviceId, keeping label / groupId to re-match when IDs rotate across sessions) and restores them next time. This is governed by the persistDeviceSelection preference (default true); set it to false to opt out.

For a different storage backend (IndexedDB, server-side per user), leave savePreferences off and mirror manually:

1function setReceiveVideo(value: boolean) {
2 client.preferences.receiveVideo = value;
3 myStore.set("receiveVideo", value);
4}

ClientPreferences is a synchronous object — there is no update$ observable. Preferences are read at dial time.

userVariables

userVariables is a free-form payload attached to every outbound call. The receiving side (an AI agent, a SWML script, a backend) reads it.

1client.preferences.userVariables = {
2 plan: user.plan,
3 locale: navigator.language,
4};

Set on preferences for app-wide values; pass to dial() for per-call attribution.

Time units

Timeouts on the preferences surface are exposed in seconds (stored as milliseconds internally):

1client.preferences.connectionTimeout = 30; // 30 seconds
2client.preferences.iceRestartTimeout = 10; // 10 seconds

Other fields use the unit of the underlying API (kbps, integer levels, etc.).

Keyframe recovery

A video stream consists of occasional keyframes — complete, self-contained frames — each followed by delta frames that encode only the change from the previous frame. A lost or corrupted delta frame corrupts every frame after it until the next keyframe arrives. The receiver can request one early via an RTCP feedback message:

Keyframes are large, so the SDK rate-limits these requests as a burst with cooldown:

PreferenceDefaultRole
keyframeMaxBurst3max requests per window
keyframeBurstWindow3000 mslength of the counting window
keyframeCooldown10000 mspause once the burst is spent

Up to keyframeMaxBurst requests are allowed per keyframeBurstWindow; once that limit is hit, requests pause for keyframeCooldown. Defaults: three requests per three-second window, then a ten-second cooldown.

Reference