@displayxr/inline3d 1.29.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/call.d.ts CHANGED
@@ -7,73 +7,30 @@
7
7
  // RFC 0003 C1: `mountCall(el, opts?)` (the one-line path, the wall from `sharedInline3D()`), the
8
8
  // `warning` event (`lift-not-bundled`), `key`; `<dxr-call>` is `./call/element`, and
9
9
  // `./call/full` is this entry with lift statically imported.
10
+ // RFC 0003 C2 (§2): THIS is the public surface — what a page needs to run, observe and dress a
11
+ // call. The test helpers that used to be exported here are internal (importable by file path
12
+ // from `js/call/*.js`); 1.30 keeps them as `@deprecated` wrappers (the block at the end), 1.31
13
+ // removes them. Options are grouped (`theme`, `invite`, `landing`, `liftOptions.max`), the camera
14
+ // is `@displayxr/inline3d/camera` (`camera: cam`), and `diagnostics()` holds the debug data.
15
+ //
16
+ // The public surface of this file is pinned by test/api-snapshot: a change here fails `npm test`
17
+ // until the snapshot is updated in the same change (`node tools/api-surface.mjs --update`).
10
18
 
11
- import type { Inline3D, Inline3DUnsupported, CreateInline3DOptions } from './index.js';
12
-
13
- /**
14
- * The `lift()` of `@displayxr/inline3d/lift`, as the call uses it: `lift(video, { mode: 'live',
15
- * wall, ui: 'none', convergence, priority, quality })` → a handle. Only the members below are
16
- * used; `setPriority` / `setConvergence` / `on` / `native` / `canvas` / `state` are optional.
17
- */
18
- export interface CallLiftHandle {
19
- remove(): void;
20
- setPriority?(p: LiftStreamPriority): boolean;
21
- setConvergence?(x: 'auto' | number): void;
22
- on?(type: string, cb: (d: any) => void): () => void;
23
- readonly native?: boolean;
24
- readonly woven?: boolean;
25
- readonly state?: string;
26
- readonly canvas?: HTMLCanvasElement;
27
- readonly stats?: { provider?: string | null } & Record<string, unknown>;
28
- }
29
- export type CallLiftFunction = ((element: HTMLVideoElement, opts: Record<string, unknown> & {
30
- mode: 'live';
31
- wall: unknown;
32
- ui: 'none';
33
- convergence: 'auto' | number;
34
- priority: LiftStreamPriority;
35
- }) => Promise<CallLiftHandle>) & { liftCapabilities?: (o?: { webFallback?: boolean }) => Promise<any> };
36
-
37
- /** A lifted stream's scheduling (the native provider's per-stream priority). */
38
- export type LiftStreamPriority = 'high' | 'normal' | 'low' | 'paused';
39
-
40
- /** Why a mono peer is not lifted (or `'lifted'`). */
41
- export type Mono3DReason = 'unavailable' | 'off' | 'lifted' | 'budget' | 'failed';
19
+ import type { Inline3D, Inline3DUnsupported } from './index.js';
20
+ import type { StereoCamera } from './camera.js';
42
21
 
43
22
  /** What a participant sends: a side-by-side pair (left eye left) or one flat picture. */
44
23
  export type CallFormat = 'sbs' | 'mono';
45
24
 
46
- /** How this page draws a remote participant (RFC §3). */
47
- export type CallRoute = 'woven-sbs' | 'flat-left' | 'flat' | 'lifted';
48
-
49
- /** A remote participant's connection state as the tile shows it. */
50
25
  /**
51
- * `'unreachable'`: known to exist but no connection for ~10 s — usually a network that needs a
52
- * relay (TURN). Sticky until it connects; retries continue in the background.
26
+ * A remote participant's connection state as the tile shows it. `'unreachable'`: known to exist
27
+ * but no connection for ~10 s — usually a network that needs a relay (TURN). Sticky until it
28
+ * connects; retries continue in the background.
53
29
  */
54
30
  export type PeerState = 'new' | 'connecting' | 'connected' | 'reconnecting' | 'unreachable' | 'left';
55
31
 
56
- /** The out-of-band 3D flag, sent on the data channel when it opens. A peer that sends none is mono. */
57
- export interface CallHello {
58
- type: 'hello';
59
- v: number;
60
- format: CallFormat;
61
- width: number | null;
62
- height: number | null;
63
- /** Camera baseline, mm. Needed (with `hfovDeg`) for automatic convergence. */
64
- baselineMm: number | null;
65
- /** Horizontal field of view of ONE eye, degrees. */
66
- hfovDeg: number | null;
67
- /** False for a raw stereo camera (the P1 default); true only after a calibrated rectify step. */
68
- rectified: boolean;
69
- sdk: string | null;
70
- }
71
-
72
- export interface CallCalibration {
73
- baselineMm?: number;
74
- hfovDeg?: number;
75
- rectified?: boolean;
76
- }
32
+ /** How THIS side shows a participant: a woven stereo pair, a mono peer lifted to 3D, or flat. */
33
+ export type CallDisplay = '3D' | '2D→3D' | '2D';
77
34
 
78
35
  /**
79
36
  * The signalling seam. Any object with `join()` works — bring your own accounts / push service.
@@ -103,233 +60,345 @@ export interface SignalingSession {
103
60
  peers: string[];
104
61
  /** Short-lived TURN credentials minted by the server, if it has any. */
105
62
  iceServers?: RTCIceServer[];
63
+ /** Hosted service (RFC 0003 §5): the tier this session was admitted as. */
64
+ tier?: 'anon' | 'key';
65
+ /** Hosted service: the publishable key id this session is attributed to. */
66
+ key?: string;
67
+ /** Hosted service: why `iceServers` is (or is not) there — see {@link SignalingTurnStatus}. */
68
+ turn?: SignalingTurnStatus;
106
69
  send(to: string, data: unknown): void;
107
70
  leave(): void;
108
71
  }
109
72
 
73
+ /** The hosted DisplayXR signalling server (`dxrSignaling()` with no URL). */
74
+ export const DXR_SIGNAL_DEFAULT: string;
75
+ /**
76
+ * The server's TURN decision for one session (`welcome.turn`, `dxr-signal/1`). `status` is the
77
+ * service-wide state of the monthly relay budget (`ok` / `degraded` / `off`); `reason` says why
78
+ * THIS session got no credentials: `unconfigured` (the server has no TURN — self-hosted), `budget`
79
+ * (anonymous session shed near the budget → `warning` `turn-shed`), `rate` (too many mints from
80
+ * this address → `turn-shed`), `cap` (the monthly cap → `error` `turn-cap`), `mint-failed`.
81
+ */
82
+ export interface SignalingTurnStatus {
83
+ status: 'ok' | 'degraded' | 'off';
84
+ reason?: 'unconfigured' | 'budget' | 'cap' | 'rate' | 'mint-failed';
85
+ /** Credential lifetime, seconds, when minted. */
86
+ ttl?: number;
87
+ }
88
+
110
89
  /**
111
90
  * The `dxr-signal/1` JSON-over-WebSocket client (protocol: signaling/README.md). `url` is your
112
91
  * server's base, e.g. `wss://signal.example.com` or `ws://localhost:8787` — the reference servers
113
- * are `signaling/worker.mjs` (Cloudflare) and `node signaling/dev-server.mjs`. There is no hosted
114
- * default yet, so the URL is required.
92
+ * are `signaling/worker.mjs` (Cloudflare) and `node signaling/dev-server.mjs`. No URL = the hosted
93
+ * server. `key`: a publishable key for the hosted service (public by design; sent on connect).
94
+ * The hosted server answers on more than one host (RFC 0003 §5g) and the adapter fails over
95
+ * between them on connect and reconnect by itself; `aliases` adds fallbacks for a self-hosted URL.
115
96
  */
116
- /** The hosted DisplayXR signalling server (`dxrSignaling()` with no URL). */
117
- export const DXR_SIGNAL_DEFAULT: string;
118
- export function dxrSignaling(url?: string, opts?: { WebSocket?: any; pingMs?: number; /** A publishable key for the hosted service (public by design; sent on connect). */ key?: string }): SignalingAdapter;
97
+ export function dxrSignaling(
98
+ url?: string,
99
+ opts?: {
100
+ WebSocket?: any;
101
+ pingMs?: number;
102
+ /** A publishable key for the hosted service (public by design; sent on connect). */
103
+ key?: string;
104
+ /** Extra base URLs to fail over to when `url` is unreachable (the hosted default has its own). */
105
+ aliases?: string[];
106
+ }
107
+ ): SignalingAdapter;
119
108
 
109
+ /** The eight named accents `theme.accent` takes (the same names `/player` uses). */
120
110
  export type CallAccent = 'azure' | 'violet' | 'magenta' | 'sunset' | 'amber' | 'lime' | 'mint' | 'ice';
121
111
 
112
+ /**
113
+ * Every string the chrome shows, by key (docs/call.md lists them with their defaults). A partial
114
+ * map overrides any subset — which is also how the chrome is localised. `{var}` placeholders are
115
+ * filled by the module.
116
+ */
117
+ export type CallStrings = Partial<Record<
118
+ | 'lobbyStartTitle' | 'lobbyJoinTitle' | 'lobbyFullTitle' | 'lobbyLeftTitle' | 'lobbyFullText' | 'lobbyText'
119
+ | 'cameraDefault' | 'kindBusy' | 'kindNone' | 'kindSbs' | 'kindMono'
120
+ | 'liftOff' | 'liftChecking' | 'liftMissing' | 'liftNoProvider' | 'liftNative' | 'liftWeb' | 'liftProven' | 'liftUnproven'
121
+ | 'start' | 'join' | 'rejoin' | 'retryCamera' | 'cameraSelect' | 'joining'
122
+ | 'waitingTitle' | 'waitingText' | 'inviteTitle' | 'inviteText' | 'inviteLink' | 'copyLink' | 'copied' | 'pressCopy' | 'qrLabel'
123
+ | 'banner2D' | 'bannerLink'
124
+ | 'mute' | 'unmute' | 'cameraOn' | 'cameraOff' | 'cameraRetry' | 'cameraBusyRetry' | 'depth' | 'invite' | 'leave'
125
+ | 'connecting' | 'reconnecting' | 'leftCall' | 'cameraOffPlate' | 'noCamera' | 'unreachable' | 'cameraBusy'
126
+ | 'you' | 'badge3D' | 'badge2D3D' | 'badge2D' | 'badgePending',
127
+ string
128
+ >>;
129
+
130
+ /**
131
+ * Theming (RFC 0003 §2). The colour / radius / font entries are written as CSS custom properties
132
+ * on the host (`--dxr-accent`, `--dxr-ink`, `--dxr-shell`, `--dxr-danger`, `--dxr-radius`,
133
+ * `--dxr-font`), which a stylesheet can set instead; the chrome also exposes `part` names
134
+ * (`bar`, `invite`, `badge`, `plate`, `lobby`, `banner`, `self`, `tile`, `grid`) — reached as
135
+ * `dxr-call [part="bar"]`, since the chrome is light DOM. Every one of these styles chrome only:
136
+ * a woven tile and its ancestors stay visually bare (woven-canvas rule 7), and no theme can reach
137
+ * them.
138
+ */
139
+ export interface CallTheme {
140
+ /** A named accent or any CSS colour → `--dxr-accent`. Default `'azure'`. */
141
+ accent?: CallAccent | (string & {});
142
+ /** Text on chrome → `--dxr-ink`. */
143
+ ink?: string;
144
+ /** Chrome background → `--dxr-shell` (near-solid on purpose: no blur over the weave). */
145
+ shell?: string;
146
+ /** Leave / muted → `--dxr-danger`. */
147
+ danger?: string;
148
+ /** Chrome corner radius (px, or any CSS length) → `--dxr-radius`. Never applied to a tile. */
149
+ radius?: number | string;
150
+ /** Chrome font family → `--dxr-font`. */
151
+ font?: string;
152
+ /** Tile aspect (w/h) = the woven buffer's per-eye aspect. Default 16/9. */
153
+ tileAspect?: number;
154
+ /** Overrides for the chrome's text (also localisation). */
155
+ strings?: CallStrings;
156
+ }
157
+
122
158
  export interface CallOptions {
123
159
  /**
124
160
  * The inline-3D manager to put the tiles on. `mountCall` defaults it to the document's shared
125
161
  * one (`sharedInline3D()`); `addCall` takes it as its first argument instead.
126
162
  */
127
163
  wall?: Inline3D | Inline3DUnsupported | null;
128
- /** Default `dxrSignaling()` (the hosted DisplayXR server). Or `dxrSignaling(url)` to self-host,
129
- * or your own adapter. */
164
+ /**
165
+ * `'auto'` (default): the room in this page's `#room=` fragment, else a new one on join. Or a
166
+ * room id / invite link. Room ids are >= 96 random bits (generated ones: 128), base64url.
167
+ */
168
+ room?: 'auto' | (string & {});
169
+ /** Default `dxrSignaling()` (the hosted DisplayXR server). Or `dxrSignaling(url)` to self-host, or your own adapter. */
130
170
  signaling?: SignalingAdapter;
131
171
  /**
132
172
  * A publishable key for the hosted signalling service (`pk_…`, RFC 0003 §5a). Public by design
133
173
  * — it sits in page source — and only used when `signaling` is left at the default.
134
174
  */
135
175
  key?: string;
136
- /**
137
- * `'auto'` (default): the room in this page's `#room=` fragment, else a new one on join. Or a
138
- * room id / invite link. Room ids are >= 96 random bits (generated ones: 128), base64url.
139
- */
140
- room?: 'auto' | string;
141
176
  /** Overrides the server's STUN/TURN list. Default: public STUN + the server's TURN, if any. */
142
177
  iceServers?: RTCIceServer[];
143
178
  /**
144
- * `'auto'` (default): a device delivering > 2.5:1 frames is a stereo pair, sent as `'sbs'`;
145
- * otherwise the default webcam, `'mono'`. A camera held by another process (e.g. an eye
146
- * tracker: NotReadableError) is skipped, never fatal. `'stereo'` prefers the pair (falls back
147
- * to mono), `'mono'` never probes, a string is a deviceId, a MediaStream is used as given with
148
- * `format` declaring what it is.
179
+ * `'auto'` (default): the call opens the best camera through `@displayxr/inline3d/camera`
180
+ * (`openCamera({ prefer: 'auto' })`): a stereo device when one is present, else the default
181
+ * webcam; a camera held by another process (an eye tracker) is skipped, never fatal. `'stereo'`
182
+ * prefers the pair (falls back to mono), `'mono'` never probes, a string is a `deviceId`, a
183
+ * `MediaStream` is used as given with `format` declaring what it is, and a `StereoCamera` you
184
+ * opened yourself is used as is (and left open when the call ends).
149
185
  */
150
- camera?: 'auto' | 'stereo' | 'mono' | string | MediaStream;
151
- /** The format of a page-supplied `camera` stream. Default `'mono'` — 3D-ness is never guessed. */
186
+ camera?: 'auto' | 'stereo' | 'mono' | (string & {}) | MediaStream | StereoCamera;
187
+ /** The format of a page-supplied `MediaStream`. Default `'mono'` — 3D-ness is never guessed. */
152
188
  format?: CallFormat;
153
- /** Sent in `hello`; enables automatic convergence on the receivers. */
154
- calibration?: CallCalibration;
155
- /**
156
- * P2 seam: a calibrated rectification step for a raw stereo stream (needs the camera's
157
- * intrinsics/extrinsics, which come from a plug-in or the runtime). Return a rectified SBS
158
- * stream; the hello then says `rectified: true`. P1 ships none: the raw pair is sent.
159
- */
160
- rectify?: (stream: MediaStream, info: { width: number; height: number; deviceId: string | null; label: string }) => MediaStream | Promise<MediaStream>;
161
189
  /** Microphone (echo cancellation + noise suppression). Default true. */
162
190
  audio?: boolean;
163
- /**
164
- * Auto-convergence (default true): measure the disparity of the point between each SBS peer's
165
- * eyes on the received frames and shift the two eyes by half of it each, so the remote person sits
166
- * at the display plane. No calibration needed. The depth slider stays an offset on top; a peer's
167
- * `hint` is used only while no measurement has succeeded. Lifted (mono) tiles are unaffected.
168
- */
169
- autoConverge?: boolean;
170
191
  /**
171
192
  * Lift mono peers to 3D on a 3D display (RFC §5). `'auto'` (default): import
172
193
  * `@displayxr/inline3d/lift` lazily from this copy of the SDK, and use it when it reports a
173
194
  * native provider or a WebGPU web fallback — otherwise mono peers stay flat (one log line, no
174
195
  * error). `'off'` / `false`: flat. A function: your own `lift` (a bundled app imports
175
- * `lift` from `@displayxr/inline3d/lift` and passes it here). Switch at runtime with
176
- * `setMono3D()`.
196
+ * `lift` from `@displayxr/inline3d/lift` and passes it here, or uses `./call/full`). Switch at
197
+ * runtime with `setMono3D()`.
177
198
  */
178
- mono3D?: 'auto' | 'off' | false | CallLiftFunction;
199
+ mono3D?: 'auto' | 'off' | false | LiftFunction;
179
200
  /**
180
201
  * Extra `lift()` options for lifted tiles — e.g. `models` (where the depth model is served from),
181
- * `ort`, `quality`, `providers`. The call's own keys (`mode`, `wall`, `ui`, `convergence`,
182
- * `priority`) always win.
202
+ * `ort`, `quality`, `providers` — plus `max`: concurrent lifted tiles (default 4 = the mesh
203
+ * maximum; further mono peers stay flat). The call's own keys (`mode`, `wall`, `ui`,
204
+ * `convergence`, `priority`) always win.
183
205
  */
184
- liftOptions?: Record<string, unknown>;
185
- /** Concurrent lifted tiles. Default 4 (= the mesh maximum); further mono peers stay flat. */
186
- maxLifted?: number;
206
+ liftOptions?: Record<string, unknown> & { max?: number };
207
+ /**
208
+ * Auto-convergence (default true): measure the disparity of the point between each SBS peer's
209
+ * eyes on the received frames and shift the two eyes by half of it each, so the remote person sits
210
+ * at the display plane. No calibration needed. The depth slider stays an offset on top.
211
+ */
212
+ autoConverge?: boolean;
187
213
  /** Participants INCLUDING you. Default 4, clamped to 2..4 (full mesh). */
188
214
  maxPeers?: number;
189
- /** `'grid'` (default) or `'speaker'` (the active speaker spans the row). */
190
- layout?: 'grid' | 'speaker';
191
- /** SDK chrome: lobby, invite (link + QR), bottom bar, badges, banner. Default true. */
192
- ui?: boolean;
193
- /** Join without the lobby. Default: `!ui`. */
215
+ /**
216
+ * `'grid'` (default), `'speaker'` (the active speaker spans the row), or `'none'`: the module
217
+ * creates the tiles but does not position them — each carries `data-dxr-peer="<id>"` and
218
+ * `tile(id)` returns it, for the page's own CSS grid on the host.
219
+ */
220
+ layout?: 'grid' | 'speaker' | 'none';
221
+ /**
222
+ * SDK chrome. `true` (default): lobby, invite (link + QR), bottom bar, badges, plates, banner.
223
+ * `'tiles'`: badges and state plates only — your bar, the module's correct per-tile chrome.
224
+ * `false`: nothing. Tiles are module-owned in every mode (they are woven canvases).
225
+ */
226
+ ui?: boolean | 'tiles';
227
+ /** Join without the lobby. Default: true unless `ui` is `true`. */
194
228
  autoJoin?: boolean;
195
- /** The small self view (mirrored; a stereo self view is mirrored AND eye-swapped). Default true. */
229
+ /** The small self view (mirrored; a stereo self view is mirrored AND eye-swapped — an `addCameraView`). Default true. */
196
230
  selfView?: boolean;
197
- /** Tile aspect (w/h) = the woven buffer's per-eye aspect. Default 16/9. */
198
- tileAspect?: number;
199
- /** A named accent or any CSS colour, written to `--dxr-accent`. */
200
- accent?: CallAccent | string;
201
- /** Base URL of invite links (default: this page's URL, query kept, fragment replaced). */
202
- inviteBase?: string;
203
- /** Write `#room=` into this page's URL on join (so a reload rejoins). Default: `ui`. */
204
- updateUrl?: boolean;
205
- /** Where the "View in 3D with DisplayXR Browser" banner links. */
206
- browserUrl?: string;
207
- /** Re-open the inline-3D session if it ends without the page closing it. Default true. */
208
- recoverSession?: boolean;
209
- /** Scroll the call block to the top of the viewport once, on join, so every tile (and the self
210
- * view) is on screen and woven. Default true (only with `ui`). */
211
- scrollIntoView?: boolean;
212
- /** `createInline3D()` options for the wall `mountCall` creates, and for a session re-open. */
213
- wallOptions?: CreateInline3DOptions;
231
+ /** Colours, radius, font, tile aspect and strings — see {@link CallTheme}. */
232
+ theme?: CallTheme;
233
+ /** Invite links: `base` (default this page's URL, query kept, fragment replaced); `updateUrl` writes `#room=` into the page URL on join (default: `ui === true`). */
234
+ invite?: { base?: string; updateUrl?: boolean };
235
+ /** The "see it in 3D" offer for recipients on ordinary browsers: `browserUrl` (where it links), `allow2D` (false = 3D-only kiosk pages). */
236
+ landing?: { browserUrl?: string; allow2D?: boolean };
237
+ /** Verbose console logging. `diagnostics()` has the rest. */
214
238
  debug?: boolean;
215
- log?: (tag: string, detail: object) => void;
216
239
  }
217
240
 
241
+ /**
242
+ * The `lift()` of `@displayxr/inline3d/lift`, as the call uses it: `lift(video, { mode: 'live',
243
+ * wall, ui: 'none', convergence, priority, quality })` → a handle it can `remove()`. Only the
244
+ * members below are used; everything else is optional.
245
+ */
246
+ export type LiftFunction = ((element: HTMLVideoElement, opts: Record<string, unknown> & {
247
+ mode: 'live';
248
+ wall: unknown;
249
+ ui: 'none';
250
+ convergence: 'auto' | number;
251
+ priority: 'high' | 'normal' | 'low' | 'paused';
252
+ }) => Promise<{
253
+ remove(): void;
254
+ setPriority?(p: 'high' | 'normal' | 'low' | 'paused'): boolean;
255
+ setConvergence?(x: 'auto' | number): void;
256
+ on?(type: string, cb: (d: any) => void): () => void;
257
+ readonly native?: boolean;
258
+ readonly state?: string;
259
+ readonly canvas?: HTMLCanvasElement;
260
+ }>) & { liftCapabilities?: (o?: { webFallback?: boolean }) => Promise<any> };
261
+
262
+ /** The documented shape of the `quality` event's `in` / `out` (values informative, shape stable). */
218
263
  export interface CallQuality {
219
264
  in: { width: number; height: number; fps: number; codec: string | null; kbps: number | null; dropped: number; decoder: string | null } | null;
220
265
  out: { width: number; height: number; fps: number; codec: string | null; kbps: number | null; limitation: string | null; encoder: string | null } | null;
221
266
  }
222
267
 
268
+ /** A remote participant, as a page needs it. Debug data (hello, route, convergence, lift) is in `diagnostics()`. */
223
269
  export interface CallPeer {
224
270
  readonly id: string;
225
271
  readonly format: CallFormat;
226
- readonly route: CallRoute | null;
272
+ readonly display: CallDisplay;
227
273
  readonly state: PeerState;
228
274
  readonly muted: boolean;
229
275
  readonly cameraOff: boolean;
230
276
  readonly speaking: boolean;
231
- readonly rectified: boolean;
232
- readonly hello: CallHello | null;
233
- readonly quality: CallQuality | null;
234
- /** The per-eye convergence shift currently painted, source px. */
235
- readonly convergencePx: number;
236
- /** Auto-convergence: measured disparity (source px; null until a lock), mean ms per measurement, last method. */
237
- readonly autoConverge: { disparityPx: number | null; ms: number | null; readMs: number | null; method: 'mode' | 'focus' | 'track' | null; via: 'videoframe' | 'canvas' | null };
238
- /** A lifted tile: whether lift is showing yet, native or web, and its current priority. */
239
- readonly lift: { readonly live: boolean; readonly native: boolean; readonly priority: LiftStreamPriority | null; readonly state: string | null } | null;
240
277
  }
241
278
 
242
- export interface CallMono3DInfo {
243
- readonly on: boolean;
244
- readonly state: 'off' | 'idle' | 'loading' | 'ready' | 'unavailable';
245
- /** Why it is unavailable: `'import-failed'`, `'no-lift-export'`, `'no-provider'`, `'no-webgpu'`. */
246
- readonly reason: string | null;
247
- /** From `liftCapabilities()` when the lift module has it; null when unknown. */
248
- readonly native: boolean | null;
249
- readonly provider: string | null;
250
- readonly lifted: number;
251
- readonly max: number;
252
- /** A lift has gone live in this call. */
253
- readonly proven: boolean;
254
- }
279
+ /**
280
+ * `error` codes — fatal for a feature, never for the widget. `'camera-busy'` (held by another
281
+ * app: audio-only until `retryCamera()`), `'no-camera'`, `'permission-denied'`, `'unreachable'`
282
+ * (`error.peer` = the tile), `'room-full'`, `'session-ended'`, `'signaling-closed'`,
283
+ * `'signaling-unreachable'`, `'join-failed'`, `'camera-failed'`, `'rectify-failed'`,
284
+ * `'mount-failed'` (the element). From the hosted signalling service (RFC 0003 §5, C3):
285
+ * `'rate-limited'` / `'quota'` (a limit of the anonymous or keyed tier — the join is refused;
286
+ * `error.retryMs` says when a retry may work), `'bad-key'` / `'origin-not-allowed'` (the
287
+ * publishable key is unknown, revoked, or not allowed from this page's origin), `'blocked'`,
288
+ * `'expired'` (the room's lifetime is up), and `'turn-cap'` (the service's monthly relay budget
289
+ * is spent: the call still joins and direct connections work, but nobody gets TURN — not fatal).
290
+ */
291
+ export type CallErrorCode =
292
+ | 'camera-busy'
293
+ | 'no-camera'
294
+ | 'permission-denied'
295
+ | 'unreachable'
296
+ | 'room-full'
297
+ | 'session-ended'
298
+ | 'signaling-closed'
299
+ | 'join-failed'
300
+ | 'camera-failed'
301
+ | 'rectify-failed'
302
+ | 'mount-failed'
303
+ | 'signaling-unreachable'
304
+ // Hosted signalling service (RFC 0003 §5, C3).
305
+ | 'rate-limited'
306
+ | 'quota'
307
+ | 'bad-key'
308
+ | 'origin-not-allowed'
309
+ | 'blocked'
310
+ | 'expired'
311
+ | 'turn-cap'
312
+ | (string & {});
313
+
314
+ /** `warning` codes — degraded, not broken. */
315
+ export type CallWarningCode = 'lift-not-bundled' | 'turn-shed' | (string & {});
255
316
 
256
317
  export interface CallEvents {
318
+ joined: { room: string; id: string };
319
+ /** `reason`: `'left'` (you called `leave()` / the element was removed) or `'pagehide'`. */
320
+ left: { room: string | null; reason: string };
257
321
  peer: { id: string };
258
322
  peerleft: { id: string; reason: string };
259
- format: { id: string; format: CallFormat; route: CallRoute; mono3d: Mono3DReason | null; hello: CallHello | null };
323
+ state: { id: string; state: PeerState };
324
+ /** How this side shows a participant changed (a hello arrived, lift went live, the wall came back). */
325
+ display: { id: string; display: CallDisplay };
326
+ speaker: { id: string | null };
260
327
  /**
261
328
  * Per peer (`id`) every 2 s; or, with `id: null` and `lift`, when lifted tiles on the web
262
329
  * provider degrade the page's frame rate (~< 20 fps for 3 s) and when it recovers.
263
330
  */
264
331
  quality: ({ id: string } & CallQuality) | ({ id: null; in: null; out: null; lift: { degraded: boolean; frameMs: number; tiles: number } });
265
- speaker: { id: string | null };
266
- /** Codes include `'camera-busy'`, `'no-camera'`, `'unreachable'` (error.peer = the tile id), `'room-full'`, `'session-ended'`. */
267
- error: { code: string; message: string; error: Error | null };
332
+ error: { code: CallErrorCode; message: string; error: Error | null };
268
333
  /**
269
- * Degraded, not broken. `'lift-not-bundled'`: a 2D participant is on a 3D display, `mono3D`
270
- * is `'auto'`, and the lift module could not be imported from this build (a bundler that could
271
- * not follow the computed import, or a copy of the SDK without `./lift`) — they stay flat. Fix:
272
- * `import { lift } from '@displayxr/inline3d/lift'` and pass `mono3D: lift`, or import
273
- * `@displayxr/inline3d/call/full`. Emitted once per call, the first time such a peer routes,
274
- * with one `console.warn`. A display that honestly cannot lift is NOT this: that is
275
- * `handle.mono3D.reason` (`'no-provider'` / `'no-webgpu'`) and an info line.
334
+ * `'lift-not-bundled'`: a 2D participant is on a 3D display, `mono3D` is `'auto'`, and the lift
335
+ * module could not be imported from this build — they stay flat. Fix: `mono3D: lift` or
336
+ * `./call/full`. Emitted once per call with one `console.warn`. A display that honestly cannot
337
+ * lift is NOT this: that is `handle.mono3D.reason` (`'no-provider'` / `'no-webgpu'`).
338
+ * `'turn-shed'`: only THIS session got no TURN from the hosted service (an anonymous session
339
+ * while the monthly relay budget is nearly spent, or too many credential requests from this
340
+ * address this hour); the call runs STUN-only. A publishable key keeps TURN.
276
341
  */
277
- warning: { code: 'lift-not-bundled' | string; message: string };
278
- state: { id: string; state: PeerState };
279
- joined: { room: string; id: string };
280
- left: { room: string };
281
- session: { wall: unknown };
342
+ warning: { code: CallWarningCode; message: string };
282
343
  }
283
344
 
284
345
  export interface CallHandle {
285
346
  readonly room: string | null;
286
347
  readonly id: string;
287
- readonly state: 'idle' | 'lobby' | 'joining' | 'in-call' | 'full' | 'left';
288
- readonly wall: unknown;
289
- readonly format: CallFormat | null;
348
+ readonly state: 'lobby' | 'joining' | 'in-call' | 'full' | 'left';
349
+ /** `'busy'` = held by another app (e.g. eye tracking): the call runs audio-only until `retryCamera()`. */
350
+ readonly camera: 'ok' | 'busy' | 'none' | 'pending';
351
+ /** What YOU send; null before a camera opened. */
352
+ readonly localFormat: CallFormat | null;
290
353
  readonly muted: boolean;
354
+ readonly cameraOff: boolean;
291
355
  readonly depth: number;
292
356
  readonly speaker: string | null;
293
- /** `'busy'` = held by another app (e.g. eye tracking): the call runs audio-only until `retryCamera()`. */
294
- readonly camera: 'ok' | 'busy' | 'none' | 'pending';
295
- /** mono→3D status (see {@link CallMono3DInfo}). */
296
- readonly mono3D: CallMono3DInfo;
297
357
  /** Read-only snapshot of the remote participants. */
298
358
  readonly peers: ReadonlyArray<CallPeer>;
299
- join(): Promise<CallHandle>;
359
+ /** mono→3D status. `reason` when unavailable: `'import-failed'`, `'no-lift-export'`, `'no-provider'`, `'no-webgpu'`. */
360
+ readonly mono3D: { readonly on: boolean; readonly state: 'off' | 'idle' | 'loading' | 'ready' | 'unavailable'; readonly reason: string | null; readonly provider: string | null; readonly lifted: number; readonly max: number };
361
+ join(): Promise<void>;
362
+ leave(): void;
300
363
  /** `…#room=<id>` — the room rides in the fragment, which never reaches a server. Null before a room exists. */
301
364
  inviteLink(): string | null;
302
365
  /** Mute (true), unmute (false) or toggle (no argument). Returns the new state. */
303
366
  mute(on?: boolean): boolean;
304
- cameraOff(on?: boolean): boolean;
305
- setCamera(idOrStream: string | MediaStream, opts?: { format?: CallFormat }): Promise<{ format: CallFormat; width: number; height: number; label: string }>;
367
+ /** Camera off (true), on (false) or toggle. Returns the new state (`cameraOff` reads it). */
368
+ setCameraOff(on?: boolean): boolean;
369
+ /** Switch cameras: a `deviceId`, a `MediaStream` (+ `format`), or a `StereoCamera`. Keeps the current one on failure. */
370
+ setCamera(src: string | MediaStream | StereoCamera, opts?: { format?: CallFormat }): Promise<{ format: CallFormat; width: number; height: number; label: string }>;
371
+ /** Try the configured camera again (e.g. after the eye tracker released it). */
372
+ retryCamera(): Promise<{ format: CallFormat; width: number; height: number; label: string }>;
306
373
  /**
307
374
  * The ONE depth control, [-1, 1] (+ = push back). Stereo tiles: ± 5% of the eye width added to
308
375
  * the automatic convergence. Lifted tiles: lift's convergence (0 → `'auto'`, else 0.5 + 0.5·v).
309
376
  */
310
377
  setDepth(v: number | null): number;
311
- /** Try the configured camera again (e.g. after the eye tracker released it). Keeps the current one on failure. */
312
- retryCamera(): Promise<{ format: CallFormat; width: number; height: number; label: string }>;
313
- /** Send `hint {subjectZmm}` (rate-limited to 5 Hz) if the page can estimate the face distance. */
314
- sendHint(subjectZmm: number): boolean;
315
378
  /** Turn mono→3D on/off at runtime (no argument toggles). Off releases every lift; tiles go flat. */
316
379
  setMono3D(on?: boolean): boolean;
317
- leave(): void;
380
+ /** A participant's tile element (for `layout: 'none'`), or null. Style it in place; never move it. */
381
+ tile(peerId: string): HTMLElement | null;
318
382
  on<K extends keyof CallEvents>(type: K, cb: (e: CallEvents[K]) => void): () => void;
319
383
  off<K extends keyof CallEvents>(type: K, cb: (e: CallEvents[K]) => void): void;
384
+ /** Explicitly UNSTABLE: the wall, the transport, per-peer hello / route / convergence / lift / quality, the lift pool. For devtools. */
385
+ diagnostics(): Record<string, unknown>;
320
386
  }
321
387
 
322
- export function addCall(wall: unknown, container: HTMLElement, opts?: CallOptions): Promise<CallHandle>;
388
+ export function addCall(wall: Inline3D | Inline3DUnsupported | null, container: HTMLElement, opts?: CallOptions): Promise<CallHandle>;
323
389
 
324
390
  /**
325
391
  * The one-line path (RFC 0003 §1): `addCall` with every argument optional. The wall is
326
- * `opts.wall` if given, else the document's shared manager (`sharedInline3D(opts.wallOptions)` —
327
- * the page's existing session, never a second one). Hosted signalling, `camera: 'auto'` and the
328
- * SDK chrome by default; resolves once the lobby shows (or, with `autoJoin` / `ui: false`, once
329
- * joined). `<dxr-call>` (`@displayxr/inline3d/call/element`) is this as markup.
392
+ * `opts.wall` if given, else the document's shared manager (`sharedInline3D()` — the page's
393
+ * existing session, never a second one). Hosted signalling, `camera: 'auto'` and the SDK chrome
394
+ * by default; resolves once the lobby shows (or, with `autoJoin` / `ui: false`, once joined).
395
+ * `<dxr-call>` is this as markup.
330
396
  */
331
397
  export function mountCall(el: HTMLElement, opts?: CallOptions): Promise<CallHandle>;
332
398
 
399
+ /** The room in an invite link (or a `location`), or null — for an SPA router to read the room without mounting a call. */
400
+ export function parseInviteLink(link: string | { hash?: string; href?: string } | null | undefined): string | null;
401
+
333
402
  // ── <dxr-call> — the one-line path as markup ───────────────────────────────────────────────
334
403
  //
335
404
  // Importing this entry (or `./call/full`, or the CDN bundle `dist/call.js`) registers the element.
@@ -337,14 +406,14 @@ export function mountCall(el: HTMLElement, opts?: CallOptions): Promise<CallHand
337
406
  // disconnect (a DOM move IS a teardown — woven-canvas rule 2), re-dispatches every call event as
338
407
  // a bubbling, composed `CustomEvent` named `dxr-call:<event>` with the payload in `detail`, and
339
408
  // exposes the handle as `el.call`. Attributes (read once, at connect): `room`, `signaling` (a
340
- // URL), `key`, `camera`, `layout`, `accent`, `max-peers`, `no-ui`, `auto-join`, `mono3d="off"`,
341
- // `no-audio`, `no-self-view`, `no-auto-converge`, `tile-aspect`, `invite-base`, `browser-url`,
342
- // `debug`. Everything else is `el.options`, set before connect; options win over attributes.
409
+ // URL), `key`, `camera`, `layout`, `accent`, `max-peers`, `no-ui`, `ui="tiles"`, `auto-join`,
410
+ // `mono3d="off"`, `no-audio`, `no-self-view`, `no-auto-converge`, `tile-aspect`, `invite-base`,
411
+ // `browser-url`, `debug`. Everything else is `el.options`, set before connect; options win.
343
412
 
344
413
  export class DxrCallElement extends HTMLElement {
345
414
  /** The mount seam: null = `mountCall` of `./call`; `./call/full` sets its own. */
346
415
  static mount: ((el: HTMLElement, opts?: CallOptions) => Promise<CallHandle>) | null;
347
- /** Non-string options (a `MediaStream`, an adapter, `lift`, a `wall`) — set BEFORE connecting. */
416
+ /** Non-string options (a `MediaStream`, an adapter, `lift`, a `wall`, a `theme`) — set BEFORE connecting. */
348
417
  options: CallOptions | null;
349
418
  /** The handle once mounted; null before, and again after disconnect. */
350
419
  readonly call: CallHandle | null;
@@ -378,77 +447,88 @@ declare global {
378
447
  interface HTMLElementEventMap extends DxrCallEventMap {}
379
448
  }
380
449
 
381
- // ── pure helpers (exported for tests and advanced pages) ──────────────────────────────────
450
+ // ── DEPRECATED (1.30 only) — the 1.29 exports that left the public surface in C2 ─────────
451
+ //
452
+ // Each is INTERNAL (RFC 0003 §2): still importable by file path from `js/call/*.js` /
453
+ // `js/camera/*.js`, kept on this entry for one release as a wrapper that `console.warn`s once,
454
+ // removed in 1.31. Do not write new code against them.
455
+
456
+ /** @deprecated internal — js/call/options.js; removed from this entry in 1.31 */
382
457
  export function normalizeCallOptions(opts?: Partial<CallOptions>): Record<string, unknown>;
458
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
383
459
  export function newRoomId(getRandomValues?: (a: Uint8Array) => Uint8Array): string;
460
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
384
461
  export function isValidRoomId(room: unknown): boolean;
385
- export function parseInviteLink(link: string | { hash?: string; href?: string } | null | undefined): string | null;
462
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 (`handle.inviteLink()` is the API) */
386
463
  export function buildInviteLink(base: string, room: string): string;
387
- export function normalizeHello(msg: unknown): CallHello | null;
464
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 (the wire is a protocol: docs/call-wire.md) */
465
+ export function normalizeHello(msg: unknown): { type: 'hello'; v: number; format: CallFormat; width: number | null; height: number | null; baselineMm: number | null; hfovDeg: number | null; rectified: boolean; sdk: string | null } | null;
466
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
388
467
  export function makeHello(o?: { format?: CallFormat; width?: number; height?: number; baselineMm?: number; hfovDeg?: number; rectified?: boolean }): object;
389
- export function routeFor(p: { format?: string | null; woven: boolean; mono3D?: 'auto' | 'off' | false | CallLiftFunction; lift?: boolean; overBudget?: boolean; failed?: boolean }): { route: CallRoute; mono3d?: Mono3DReason };
390
- export function badgeFor(route: CallRoute | null): '3D' | '2D→3D' | '2D';
391
- export function normalizeMono3D(v: unknown): 'auto' | 'off' | CallLiftFunction;
392
- export function resolveLift(
393
- mono3D: 'auto' | 'off' | false | CallLiftFunction,
394
- o?: { importer?: () => Promise<any>; nav?: any; log?: (tag: string, o: object) => void; capsOpts?: object },
395
- ): Promise<{ lift: CallLiftFunction | null; capabilities: ((o?: object) => Promise<any>) | null; caps: any; source: 'injected' | 'module' | 'none'; reason: string | null }>;
396
- export function liftConvergenceFor(depth: number | null | undefined): 'auto' | number;
397
- export function liftPriorityFor(p: { id: string | null; speakerId: string | null; visible?: boolean }): LiftStreamPriority;
398
- /** The hook for the native provider's per-stream priority: forwards to `handle.setPriority` (no-op without one). */
399
- export function setLiftPriority(handle: CallLiftHandle | null | undefined, level: LiftStreamPriority): boolean;
400
- export function defaultLiftSpecifier(base?: string): string;
401
- export const LIFT_PRIORITY: Readonly<{ speaker: 'high'; other: 'normal'; hidden: 'paused' }>;
402
- export function createFrameWatch(o?: { degradedMs?: number; recoveredMs?: number; holdMs?: number; alpha?: number }): {
403
- feed(dtMs: number): 'degraded' | 'recovered' | null;
404
- reset(): void;
405
- readonly frameMs: number | null;
406
- readonly degraded: boolean;
407
- };
408
- export interface CallLiftPool {
409
- readonly size: number;
410
- max: number;
411
- has(id: string): boolean;
412
- handle(id: string): CallLiftHandle | null;
413
- priority(id: string): LiftStreamPriority | null;
414
- canAcquire(id: string): boolean;
415
- acquire(id: string, element: HTMLVideoElement, o?: { wall?: unknown; visible?: boolean; onError?: (e: unknown) => void }): Promise<CallLiftHandle | null>;
416
- release(id: string): void;
417
- releaseAll(): void;
418
- setSpeaker(id: string | null): void;
419
- setVisible(id: string, visible: boolean): void;
420
- setDepth(v: number): 'auto' | number;
421
- readonly anyWeb: boolean;
422
- list(): Array<{ id: string; priority: LiftStreamPriority | null; pending: boolean; native: boolean }>;
423
- }
424
- export function createLiftPool(o: { lift: CallLiftFunction; max?: number; log?: (tag: string, o: object) => void; options?: Record<string, unknown> }): CallLiftPool;
468
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 (`CallPeer.display` is the API) */
469
+ export function routeFor(p: { format?: string | null; woven: boolean; mono3D?: 'auto' | 'off' | false | LiftFunction; lift?: boolean; overBudget?: boolean; failed?: boolean }): { route: 'woven-sbs' | 'flat-left' | 'flat' | 'lifted'; mono3d?: string };
470
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
471
+ export function badgeFor(route: 'woven-sbs' | 'flat-left' | 'flat' | 'lifted' | null): CallDisplay;
472
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
473
+ export function createLiveGate(o?: { need?: number; needNoPose?: number }): { feed(viewCount: number | null): boolean; readonly live: boolean };
474
+ /** @deprecated internal — js/camera/converge.js; removed in 1.31 */
425
475
  export function convergenceShiftPx(p: { eyeWidthPx: number; hfovDeg?: number | null; baselineMm?: number | null; subjectZmm?: number | null }): number;
476
+ /** @deprecated internal — js/camera/converge.js; removed in 1.31 */
426
477
  export function lowPass(prev: number, target: number, alpha?: number): number;
427
- /** The disparity (left x − right x, input px) of the point between the eyes in a grayscale SBS frame. */
428
- export function measureFocusDisparity(
429
- img: ArrayLike<number>,
430
- W: number,
431
- H: number,
432
- o?: { focus?: { x: number; y: number }; block?: number; dMin?: number; dMax?: number; dyMax?: number; minNcc?: number },
433
- ): { d: number; x: number; y: number; c: number; method: 'focus' | 'mode'; blocks: number } | null;
478
+ /** @deprecated internal — js/camera/converge.js; removed in 1.31 */
434
479
  export function clampShift(px: number, eyeWidthPx: number, maxFraction?: number): number;
480
+ /** @deprecated internal — js/camera/geometry.js; removed in 1.31 */
435
481
  export function eyeCropRect(eyeW: number, eyeH: number, aspect: number, shift: number, eye: 0 | 1): { sx: number; sy: number; sw: number; sh: number };
482
+ /** @deprecated internal — js/camera/geometry.js; removed in 1.31 (`addCameraView` of `/camera` mirrors a self view) */
436
483
  export function mirrorSwapOps(W: number, H: number): Array<{ src: 'L' | 'R'; sx: number; sw: number; sy: number; sh: number; dx: number; dw: number; mirror: true }>;
484
+ /** @deprecated internal — js/camera/geometry.js; removed in 1.31 */
437
485
  export function mirrorSwapPixels<T extends ArrayLike<number>>(px: T, W: number, H: number): T;
486
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
438
487
  export function maxBitrateKbps(format: CallFormat, remotePeers: number): number;
488
+ /** @deprecated internal — js/camera/disparity.js; removed in 1.31 (`autoConverge` is the behaviour) */
489
+ export function measureFocusDisparity(img: ArrayLike<number>, W: number, H: number, o?: { focus?: { x: number; y: number }; block?: number; dMin?: number; dMax?: number; dyMax?: number; minNcc?: number }): { d: number; x: number; y: number; c: number; method: 'focus' | 'mode'; blocks: number } | null;
490
+ /** @deprecated internal — js/call/sdp.js; removed in 1.31 */
439
491
  export function preferVideoCodecs(sdp: string, order?: readonly string[]): string;
492
+ /** @deprecated internal — js/call/sdp.js; removed in 1.31 */
440
493
  export function sortCodecCapabilities<T extends { mimeType?: string }>(codecs: T[], order?: readonly string[]): T[];
494
+ /** @deprecated internal — js/call/sdp.js; removed in 1.31 */
441
495
  export const VIDEO_CODEC_ORDER: readonly string[];
496
+ /** @deprecated internal — js/call/transport.js; removed in 1.31 (an SFU adapter will get a designed Transport seam) */
497
+ export class MeshTransport {
498
+ constructor(o: { signaling: SignalingAdapter; id: string; maxPeers?: number; iceServers?: RTCIceServer[]; RTCPeerConnection?: any; log?: (tag: string, obj: object) => void });
499
+ readonly size: number;
500
+ }
501
+ /** @deprecated internal — js/call/transport.js; removed in 1.31 */
442
502
  export function clampMaxPeers(n?: number): number;
503
+ /** @deprecated internal — js/call/qr.js; removed in 1.31 */
443
504
  export function qrEncode(text: string, opts?: { ecl?: 'L' | 'M'; mask?: number }): { version: number; size: number; mask: number; modules: boolean[][] };
505
+ /** @deprecated internal — js/call/signaling.js; removed in 1.31 (documented in signaling/README.md) */
444
506
  export function roomKey(room: string): Promise<string>;
507
+ /** @deprecated internal — js/call/signaling.js; removed in 1.31 */
445
508
  export const SIGNAL_PROTOCOL: string;
509
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
446
510
  export const WIRE_VERSION: number;
511
+ /** @deprecated internal — js/call/wire.js; removed in 1.31 */
447
512
  export const CALL_SDK: string;
513
+ /** @deprecated replaced by `theme.strings`; removed in 1.31 */
448
514
  export const PLATE_TEXT: Readonly<{ unreachable: string; cameraBusy: string }>;
449
- export function createLiveGate(o?: { need?: number; needNoPose?: number }): { feed(viewCount: number | null): boolean; readonly live: boolean };
515
+ /** @deprecated folded into `theme.accent` (which takes the names); removed in 1.31 */
450
516
  export const CALL_ACCENTS: Readonly<Record<CallAccent, string>>;
451
- export class MeshTransport {
452
- constructor(o: { signaling: SignalingAdapter; id: string; maxPeers?: number; iceServers?: RTCIceServer[]; RTCPeerConnection?: any; log?: (tag: string, obj: object) => void });
453
- readonly size: number;
454
- }
517
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
518
+ export function normalizeMono3D(v: unknown): 'auto' | 'off' | LiftFunction;
519
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
520
+ export function resolveLift(mono3D: 'auto' | 'off' | false | LiftFunction, o?: { importer?: () => Promise<any>; nav?: any; log?: (tag: string, o: object) => void; capsOpts?: object }): Promise<{ lift: LiftFunction | null; capabilities: ((o?: object) => Promise<any>) | null; caps: any; source: 'injected' | 'module' | 'none'; reason: string | null }>;
521
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
522
+ export function createLiftPool(o: { lift: LiftFunction; max?: number; log?: (tag: string, o: object) => void; options?: Record<string, unknown> }): { readonly size: number; max: number; has(id: string): boolean; release(id: string): void; releaseAll(): void; [k: string]: unknown };
523
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
524
+ export function createFrameWatch(o?: { degradedMs?: number; recoveredMs?: number; holdMs?: number; alpha?: number }): { feed(dtMs: number): 'degraded' | 'recovered' | null; reset(): void; readonly frameMs: number | null; readonly degraded: boolean };
525
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
526
+ export function liftConvergenceFor(depth: number | null | undefined): 'auto' | number;
527
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
528
+ export function liftPriorityFor(p: { id: string | null; speakerId: string | null; visible?: boolean }): 'high' | 'normal' | 'low' | 'paused';
529
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
530
+ export function setLiftPriority(handle: { setPriority?(p: 'high' | 'normal' | 'low' | 'paused'): boolean } | null | undefined, level: 'high' | 'normal' | 'low' | 'paused'): boolean;
531
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
532
+ export function defaultLiftSpecifier(base?: string): string;
533
+ /** @deprecated internal — js/call/lift.js; removed in 1.31 */
534
+ export const LIFT_PRIORITY: Readonly<{ speaker: 'high'; other: 'normal'; hidden: 'paused' }>;