@displayxr/inline3d 1.47.5 → 1.48.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/CHANGELOG.md +143 -1
- package/README.md +7 -1
- package/call.d.ts +98 -8
- package/camera.d.ts +34 -5
- package/dist/call.js +8 -6
- package/docs/content-lifecycle-rules.md +270 -0
- package/docs/woven-canvas-rules.md +25 -13
- package/js/call/element.js +7 -3
- package/js/call/options.js +20 -3
- package/js/call/transport.js +11 -1
- package/js/call/ui.js +8 -1
- package/js/call/wire.js +163 -6
- package/js/camera/capture.js +107 -20
- package/js/camera/conv-meter.js +152 -0
- package/js/inline3d-call.js +392 -71
- package/js/inline3d-camera.js +29 -6
- package/js/inline3d-model-entry.js +3 -0
- package/js/inline3d-model-playcanvas.js +36 -2
- package/js/inline3d-pc-auto-release.js +191 -0
- package/js/inline3d-pc-release.js +107 -0
- package/js/inline3d-pc-shader-cache.js +172 -0
- package/js/inline3d-playcanvas-engine.js +2 -1
- package/js/inline3d-player.js +8 -4
- package/js/inline3d-splat-deferred.js +2 -0
- package/js/inline3d-splat-diag.js +125 -4
- package/js/inline3d-splat-playcanvas.js +40 -3
- package/js/inline3d-splat.js +2 -0
- package/llms.txt +1 -0
- package/model.d.ts +12 -0
- package/package.json +12 -1
- package/signaling/README.md +289 -0
- package/signaling/dev-server.mjs +320 -0
- package/signaling/gate.mjs +112 -0
- package/signaling/keys.mjs +123 -0
- package/signaling/limits.mjs +221 -0
- package/signaling/room.mjs +322 -0
- package/signaling/turn-budget.mjs +254 -0
- package/splat.d.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,149 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
|
|
|
5
5
|
`./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
|
|
6
6
|
which tier they touch, because that is what tells you whether an upgrade can move your pixels.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 1.48.0 — 2026-11-03
|
|
9
|
+
|
|
10
|
+
### Changed — auto-convergence is measured on the sender's raw frames (`./call`)
|
|
11
|
+
|
|
12
|
+
Preview tier. A stereo participant was converged by each RECEIVER, which measured the disparity of
|
|
13
|
+
the point between the person's eyes on the frame it decoded: a compressed copy, often downscaled
|
|
14
|
+
by WebRTC, measured again by every participant. The sender now measures it on its own raw camera
|
|
15
|
+
frames, before encoding, and sends the result on the data channel (`conv { d, c }`, `d` a
|
|
16
|
+
fraction of the per-eye width; at most 5 a second while the person moves, one every 400 ms while
|
|
17
|
+
they hold still). Each receiver still applies it as a per-eye crop, so the stream keeps the full
|
|
18
|
+
picture and every receiver keeps its own depth slider and display. The sender measures whatever
|
|
19
|
+
its self view does, including `selfView: false`, and sends nothing while its camera is off or from
|
|
20
|
+
a mono camera.
|
|
21
|
+
|
|
22
|
+
- New option **`sendConvergence`** (default `true`; `<dxr-call no-send-convergence>`). `false`
|
|
23
|
+
sends nothing and leaves the measuring to the receivers.
|
|
24
|
+
- Additive on the wire, so the wire version stays 1. A sender advertises it with `hello.conv:
|
|
25
|
+
true`. A sender without it (1.47 and earlier), or one silent for 1.2 s, is measured by the
|
|
26
|
+
receiver exactly as before, and an older receiver ignores `conv`. The depth cursor still copies
|
|
27
|
+
the received frames while it hovers a tile.
|
|
28
|
+
- `diagnostics().peers[i].autoConverge` gains `source` (`'sender'` / `'local'` / `null`),
|
|
29
|
+
`senderFrac` and `samples`. A failed local measurement no longer drops a value held from the
|
|
30
|
+
sender.
|
|
31
|
+
- `docs/call.md` § *Auto-convergence is measured by the sender*.
|
|
32
|
+
|
|
33
|
+
### Added — headless-mode hooks for an app that owns its layout (`./call`)
|
|
34
|
+
|
|
35
|
+
Preview tier. Everything a chat app that lays out the call itself (`ui: false`, `layout: 'none'`)
|
|
36
|
+
needed and had to work around; the tiles stay module-owned. Docs: `docs/call.md` § *Hooks for an
|
|
37
|
+
app that owns its layout*.
|
|
38
|
+
|
|
39
|
+
- **Names.** Option `name` (≤ 64 characters, control and bidi characters stripped), sent in
|
|
40
|
+
`hello`; `CallPeer.name`; `handle.setName()` re-sends it; event `name { id, name }`. Additive on
|
|
41
|
+
the wire (wire version stays 1): an older peer ignores it.
|
|
42
|
+
- **App messages.** `handle.send(peerId, data)` / `handle.broadcast(data)` on the call's data
|
|
43
|
+
channel (`{ type: 'app', d }`): JSON, ≤ 16 KiB serialised, ≤ 20 a second per participant (over a
|
|
44
|
+
cap: `false`, nothing sent); event `message { from, data }`. Only app messages reach the page.
|
|
45
|
+
An older peer drops them silently.
|
|
46
|
+
- **Events.** `signal { state, code? }` (+ `handle.signal`): connected / reconnecting / closed.
|
|
47
|
+
`weave { id, state, reason?, ms }`: a tile's first join and its re-join after a resize, per
|
|
48
|
+
participant and `'self'`. `speaking { id, on }`, for participants and `'self'`.
|
|
49
|
+
- **Layout.** `tileCover` (default `'auto'` with `layout: 'none'`): the core's `rectCover` on every
|
|
50
|
+
woven tile and a stereo self view. `theme.tileAspect: 'fill'`: no aspect imposed, each tile's
|
|
51
|
+
crop follows its own box (the numeric default is unchanged). `handle.tile('self')`
|
|
52
|
+
(`data-dxr-peer="self"`); under `layout: 'none'` the footer and self slot are
|
|
53
|
+
`display: contents` so the page places the self tile. `handle.setSelfView(on)` removes / restores
|
|
54
|
+
its layer. A hidden (0×0) tile is no longer painted or measured.
|
|
55
|
+
- **Devices.** `handle.setMic(deviceId)`, `handle.setAudioOutput(sinkId)` (tiles that join later
|
|
56
|
+
too), `handle.localStream`.
|
|
57
|
+
- **Quality.** The `quality` event gains `path` (`'host'` / `'srflx'` / `'prflx'` / `'relay'`, from
|
|
58
|
+
the selected ICE candidate pair) and `rttMs`.
|
|
59
|
+
- `<dxr-call>`: `name` attribute, `tile-aspect="fill"`, and the five new events as `dxr-call:*`.
|
|
60
|
+
|
|
61
|
+
### Fixed — the camera probe opened a hand-tracking camera (`./camera`, `./call`)
|
|
62
|
+
|
|
63
|
+
Preview tier. Found on hardware (Windows, DisplayXR Browser 1.9.1): with no stereo pair open,
|
|
64
|
+
`camera: 'auto'`'s last-resort "try every device" loop opened a hand-tracking camera — an ordinary
|
|
65
|
+
video input, wide enough to pass for a side-by-side pair — and the call sent its picture as 3D.
|
|
66
|
+
Every probe path (the named-pair fast path, the default camera, the last resort) now skips devices
|
|
67
|
+
whose label marks a sensor (hand tracking, eye tracking, a "tracking camera", a depth camera), plus
|
|
68
|
+
the new `openCamera({ exclude })` / call `cameraExclude` list; `false` turns the built-in list off.
|
|
69
|
+
Left-out devices appear in `skipped` with `excluded: true`; an explicit `deviceId` is opened
|
|
70
|
+
regardless. Also: the browser's "3D Camera (DisplayXR)" is listed LAST on Windows, so the probe no
|
|
71
|
+
longer assumes it is the default — a default that is a pair only by its shape no longer wins over a
|
|
72
|
+
camera that names itself a pair, and with an excluded default the first plain camera is the
|
|
73
|
+
fallback. `addCameraView` gains `aspect: 'fill'` and `rectCover`.
|
|
74
|
+
|
|
75
|
+
### Added — the dev signalling server ships in the package
|
|
76
|
+
|
|
77
|
+
`signaling/dev-server.mjs` and the five modules it imports are in the published files (not an
|
|
78
|
+
`exports` entry): run it by path, or import `startDevServer` from the file. Copy it unbundled
|
|
79
|
+
(`signaling/README.md`).
|
|
80
|
+
|
|
81
|
+
### Docs — frosted chrome over a tile works on DisplayXR Browser 0.1.16+
|
|
82
|
+
|
|
83
|
+
`docs/woven-canvas-rules.md` rule 10 and §5, and `docs/call.md`: since DisplayXR Browser 0.1.16 the
|
|
84
|
+
compositor draws a tile mono inside each `backdrop-filter` rect (displayxr-browser#120;
|
|
85
|
+
`samples/composition` is the validated case), so partial frosted chrome over a tile is fine. Never
|
|
86
|
+
frost a whole visible tile; effects on a tile or its ancestors (rule 7) are unchanged. The SDK's
|
|
87
|
+
own chrome keeps its near-solid tint. `docs/call.md` also notes that changing only the `#room=`
|
|
88
|
+
fragment of a page with a live call does not re-join (reload, or navigate with a changed query).
|
|
89
|
+
|
|
90
|
+
## 1.47.6 — 2026-11-03
|
|
91
|
+
|
|
92
|
+
### Fixed — a page that destroys PlayCanvas materials no longer keeps their programs (`./splat`, `./model`)
|
|
93
|
+
|
|
94
|
+
Preview tier. When a page destroys a material on a PlayCanvas tile the SDK created, either
|
|
95
|
+
directly or through a glTF container's `asset.unload()`, the SDK now destroys the programs that
|
|
96
|
+
material linked, one frame later, when no mesh instance in the app, no material asset in the
|
|
97
|
+
registry and no still-alive material holds them. A page that follows the engine's own teardown
|
|
98
|
+
(`entity.destroy()` plus destroying or unloading what it drew with) is bounded without calling
|
|
99
|
+
`handle.releaseEntity`. Measured on macOS, 24 swaps of distinct products under one tile: 2 live
|
|
100
|
+
programs (25 created, 23 deleted), against 25 (none deleted) before. A kiosk app with one
|
|
101
|
+
persistent stage had reached 170 live programs over 28 screen changes.
|
|
102
|
+
|
|
103
|
+
Content shown again after its material was destroyed recompiles on its first draw
|
|
104
|
+
(`handle.warmEntity` hides that). A material the page keeps alive keeps its programs.
|
|
105
|
+
`?dxrdiag=keepprograms` turns the release off for an A/B, and the overlay shows
|
|
106
|
+
`release patched (−N)`. It installs only on PlayCanvas 2.22.x's `Material` shape and otherwise
|
|
107
|
+
reports `release skipped: …`. `docs/content-lifecycle-rules.md` rule 1 and §6 are updated.
|
|
108
|
+
|
|
109
|
+
### Added — `handle.releaseEntity`: swap content in a PlayCanvas tile without leaking its programs (`./splat`, `./model`)
|
|
110
|
+
|
|
111
|
+
Preview tier. PlayCanvas never frees a shader program by itself: its `ProgramLibrary` caches every
|
|
112
|
+
variant for the device's life, and `Material.destroy()` (which `entity.destroy()` and a container's
|
|
113
|
+
`asset.unload()` reach) only empties `material.variants`. A page that swaps glTFs under
|
|
114
|
+
`handle.engine.root` (a carousel, a catalogue) therefore kept every program it ever showed, and a
|
|
115
|
+
desktop driver keeps each linked program's compiled form (about 5 MB per program, measured on one
|
|
116
|
+
Linux driver): a production page's GPU process grew about 8 MB/min for as long as it was open.
|
|
117
|
+
|
|
118
|
+
- `handle.releaseEntity(entity, { asset })` on `addSplat(…, { engine: 'playcanvas' })` and
|
|
119
|
+
PlayCanvas `addModel` handles: collects the programs the entity's materials linked, destroys the
|
|
120
|
+
entity, unloads its glTF container `asset` unless something else in the tile still draws from it,
|
|
121
|
+
then destroys each collected program nothing left uses (`Shader.destroy()`, the engine's own
|
|
122
|
+
release). Returns `{ programs, container }`. A no-op on Spark and three.
|
|
123
|
+
Measured over 24 distinct models: live programs 5 → 47 without it, bounded at 5–16 (the model on
|
|
124
|
+
screen) with it.
|
|
125
|
+
|
|
126
|
+
### Fixed — the engine kept every compiled shader until the device died (`./splat`, `./model`)
|
|
127
|
+
|
|
128
|
+
Preview tier. PlayCanvas 2.22.3 keeps every compiled vertex and fragment shader in a
|
|
129
|
+
module-private cache keyed by source, and `WebglShader.destroy()` deletes only the program. So
|
|
130
|
+
even with the programs destroyed, two compiled shaders per program stayed (10 → 91 over the same 24
|
|
131
|
+
swaps). The SDK now counts, per cached shader, the programs that use it and deletes it with the last
|
|
132
|
+
one, on every PlayCanvas device it creates (`js/inline3d-pc-shader-cache.js`: a run-time patch of the
|
|
133
|
+
engine's `WebglShader` prototype, since the engine is the page's own dependency; it installs only on
|
|
134
|
+
the 2.22.x shape and otherwise steps aside). With `releaseEntity`, shaders stay at exactly twice the
|
|
135
|
+
live programs. `?dxrdiag=keepshaders` turns it off for an A/B.
|
|
136
|
+
|
|
137
|
+
### Added — live GL object counts and a leak warning in `?dxrdiag` (`./splat`, `./model`)
|
|
138
|
+
|
|
139
|
+
The diagnostics overlay shows live programs and shaders on the tile's context (created and deleted
|
|
140
|
+
since diag attached) and the shader-cache fix's state; the dump carries them under `glObjects`. At
|
|
141
|
+
each content swap the SDK sees (`setSource`, a glTF container loaded into the tile's registry,
|
|
142
|
+
`releaseEntity`) it samples the live program count, and warns once when it has not dropped across
|
|
143
|
+
10 swaps of one kind and rose at more than 60% of them. `addModel`'s PlayCanvas tile now honours
|
|
144
|
+
`?dxrdiag` too. Nothing runs when diag is off.
|
|
145
|
+
|
|
146
|
+
### Docs
|
|
147
|
+
|
|
148
|
+
- New [`docs/content-lifecycle-rules.md`](docs/content-lifecycle-rules.md): what a swap must release,
|
|
149
|
+
PlayCanvas and three.js engine facts, the hook-and-plateau pass test, the SDK's own swap paths
|
|
150
|
+
measured, and what the SDK does vs what the page owns.
|
|
9
151
|
|
|
10
152
|
## 1.47.5 — 2026-11-01
|
|
11
153
|
|
package/README.md
CHANGED
|
@@ -166,7 +166,8 @@ No projection math lands in your page or in the SDK — the off-axis frustum sta
|
|
|
166
166
|
Full API + authoring guidance: [`docs/authoring-inline-3d.md`](docs/authoring-inline-3d.md).
|
|
167
167
|
Before you ship a page that navigates or remounts, read
|
|
168
168
|
[`docs/woven-canvas-rules.md`](docs/woven-canvas-rules.md): how to avoid a raw side-by-side
|
|
169
|
-
flash, and releasing a poster on `handle.firstWoven`.
|
|
169
|
+
flash, and releasing a poster on `handle.firstWoven`. A page that swaps content in a window (a
|
|
170
|
+
carousel, a catalogue): [`docs/content-lifecycle-rules.md`](docs/content-lifecycle-rules.md).
|
|
170
171
|
Three.js glue (an off-axis `EyeCamera`) in [`js/inline3d-three.js`](js/inline3d-three.js).
|
|
171
172
|
|
|
172
173
|
## What's here
|
|
@@ -198,6 +199,8 @@ js/
|
|
|
198
199
|
inline3d-model-entry.js experimental: addModel() — picks the engine (PlayCanvas default)
|
|
199
200
|
inline3d-model-playcanvas.js the PlayCanvas model backend (Sample-Viewer lighting, engine decoders)
|
|
200
201
|
inline3d-pc-look.js PlayCanvas look helpers for both adapters (environments, transmission)
|
|
202
|
+
inline3d-pc-release.js handle.releaseEntity: content under engine.root out, its programs + container released
|
|
203
|
+
inline3d-pc-shader-cache.js engine fix: a compiled shader is deleted with the last program using it
|
|
201
204
|
inline3d-model.js the three.js model backend (engine:'three'; ./model/three)
|
|
202
205
|
inline3d-playcanvas-engine.js named playcanvas imports for both PlayCanvas adapters (tree-shaking)
|
|
203
206
|
inline3d-splat-effects.js splat shader effects: the runner, the effect registry (PlayCanvas)
|
|
@@ -215,6 +218,9 @@ docs/
|
|
|
215
218
|
the WebXR→inline-3d mapping table and the whole render loop
|
|
216
219
|
woven-canvas-rules.md never show a raw side-by-side frame: the join, the rules,
|
|
217
220
|
handle.firstWoven / rewoven(), reading the browser's `withheld` log line
|
|
221
|
+
content-lifecycle-rules.md
|
|
222
|
+
release what a content swap replaces (programs included):
|
|
223
|
+
handle.releaseEntity, engine facts, the hook-and-plateau pass test
|
|
218
224
|
camera.md The 3D camera primitive (/camera): openCamera, the mirrored self view,
|
|
219
225
|
capturePhoto / record with the stereo record inside the file.
|
|
220
226
|
call.md 3D video calls: the <dxr-call> / mountCall() one-line embed, options,
|
package/call.d.ts
CHANGED
|
@@ -154,8 +154,12 @@ export interface CallTheme {
|
|
|
154
154
|
radius?: number | string;
|
|
155
155
|
/** Chrome font family → `--dxr-font`. */
|
|
156
156
|
font?: string;
|
|
157
|
-
/**
|
|
158
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Tile aspect (w/h) = the woven buffer's per-eye aspect, the same for every tile. Default 16/9.
|
|
159
|
+
* `'fill'`: no aspect is imposed — the page sizes each tile's box (layout: 'none') and each
|
|
160
|
+
* tile's crop follows its own stage box (clamped to 0.3–4), the self view's too.
|
|
161
|
+
*/
|
|
162
|
+
tileAspect?: number | 'fill';
|
|
159
163
|
/** Overrides for the chrome's text (also localisation). */
|
|
160
164
|
strings?: CallStrings;
|
|
161
165
|
}
|
|
@@ -193,6 +197,24 @@ export interface CallOptions {
|
|
|
193
197
|
format?: CallFormat;
|
|
194
198
|
/** Microphone (echo cancellation + noise suppression). Default true. */
|
|
195
199
|
audio?: boolean;
|
|
200
|
+
/**
|
|
201
|
+
* Your display name, sent to every participant in `hello` (their `CallPeer.name`). At most 64
|
|
202
|
+
* characters; control characters are stripped. Change it with `setName()`. Default none.
|
|
203
|
+
*/
|
|
204
|
+
name?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Devices `camera: 'auto' | 'stereo' | 'mono'` never opens — `openCamera`'s `exclude`: strings
|
|
207
|
+
* match a label (case-insensitive substring) or a deviceId, RegExps the label. Labels of
|
|
208
|
+
* sensors (hand tracking, eye tracking, a "tracking camera", a depth camera) are always left out
|
|
209
|
+
* too; `false` turns that built-in list off. An explicit deviceId is opened regardless.
|
|
210
|
+
*/
|
|
211
|
+
cameraExclude?: ReadonlyArray<string | RegExp> | false;
|
|
212
|
+
/**
|
|
213
|
+
* Cover each woven tile (and a stereo self view) across a resize or move until it is woven
|
|
214
|
+
* again — the core's `rectCover: 'auto'` on its registration. Default `'auto'` with
|
|
215
|
+
* `layout: 'none'` (your layout moves the tiles), `'off'` otherwise.
|
|
216
|
+
*/
|
|
217
|
+
tileCover?: 'auto' | 'off';
|
|
196
218
|
/**
|
|
197
219
|
* Lift mono peers to 3D on a 3D display (RFC §5). `'auto'` (default): import
|
|
198
220
|
* `@displayxr/inline3d/lift` lazily from this copy of the SDK, and use it when it reports a
|
|
@@ -210,11 +232,20 @@ export interface CallOptions {
|
|
|
210
232
|
*/
|
|
211
233
|
liftOptions?: Record<string, unknown> & { max?: number };
|
|
212
234
|
/**
|
|
213
|
-
* Auto-convergence (default true):
|
|
214
|
-
*
|
|
215
|
-
*
|
|
235
|
+
* Auto-convergence (default true): shift each SBS peer's two eyes by half the disparity of the
|
|
236
|
+
* point between that person's eyes, so the remote person sits at the display plane. No
|
|
237
|
+
* calibration needed. The depth slider stays an offset on top. The disparity comes from the
|
|
238
|
+
* sender when it measures it (`sendConvergence`, 1.48+); from an older sender, this side measures
|
|
239
|
+
* the received frames itself.
|
|
216
240
|
*/
|
|
217
241
|
autoConverge?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* With a stereo camera (default true): measure the disparity of the point between your eyes on
|
|
244
|
+
* your own raw camera frames, before compression, and send it to every peer a few times a second
|
|
245
|
+
* (a few bytes on the data channel). Their `autoConverge` applies it, so nobody measures the
|
|
246
|
+
* decoded frames. `false`: send nothing; receivers measure, as before 1.48.
|
|
247
|
+
*/
|
|
248
|
+
sendConvergence?: boolean;
|
|
218
249
|
/**
|
|
219
250
|
* `'depth'`: over a 3D (stereo) tile — a peer's or your self view — the cursor is never
|
|
220
251
|
* overlapped by what comes out of the screen: where the person under the pointer pops out, the
|
|
@@ -277,6 +308,14 @@ export type LiftFunction = ((element: HTMLVideoElement, opts: Record<string, unk
|
|
|
277
308
|
export interface CallQuality {
|
|
278
309
|
in: { width: number; height: number; fps: number; codec: string | null; kbps: number | null; dropped: number; decoder: string | null } | null;
|
|
279
310
|
out: { width: number; height: number; fps: number; codec: string | null; kbps: number | null; limitation: string | null; encoder: string | null } | null;
|
|
311
|
+
/**
|
|
312
|
+
* The network path of the selected ICE candidate pair: `'relay'` (through TURN, either end),
|
|
313
|
+
* else the local candidate's type — `'host'` (direct, same network), `'srflx'` / `'prflx'`
|
|
314
|
+
* (direct through NAT). Null before a pair is selected.
|
|
315
|
+
*/
|
|
316
|
+
path?: 'host' | 'srflx' | 'prflx' | 'relay' | null;
|
|
317
|
+
/** The selected pair's current round-trip time, ms; null when unknown. */
|
|
318
|
+
rttMs?: number | null;
|
|
280
319
|
}
|
|
281
320
|
|
|
282
321
|
/** A remote participant, as a page needs it. Debug data (hello, route, convergence, lift) is in `diagnostics()`. */
|
|
@@ -288,6 +327,8 @@ export interface CallPeer {
|
|
|
288
327
|
readonly muted: boolean;
|
|
289
328
|
readonly cameraOff: boolean;
|
|
290
329
|
readonly speaking: boolean;
|
|
330
|
+
/** Their display name (`name` option / `setName()`), sanitised; null when they sent none (or an older SDK). */
|
|
331
|
+
readonly name: string | null;
|
|
291
332
|
}
|
|
292
333
|
|
|
293
334
|
/**
|
|
@@ -354,6 +395,26 @@ export interface CallEvents {
|
|
|
354
395
|
* address this hour); the call runs STUN-only. A publishable key keeps TURN.
|
|
355
396
|
*/
|
|
356
397
|
warning: { code: CallWarningCode; message: string };
|
|
398
|
+
/** A participant's display name arrived or changed (`name: null` = they cleared it). */
|
|
399
|
+
name: { id: string; name: string | null };
|
|
400
|
+
/**
|
|
401
|
+
* An app message from a participant (`send` / `broadcast` on their side): `data` as they sent it
|
|
402
|
+
* (JSON). Only app messages surface here — the call's own wire messages never do.
|
|
403
|
+
*/
|
|
404
|
+
message: { from: string; data: unknown };
|
|
405
|
+
/**
|
|
406
|
+
* The signalling connection: `'connected'` on join and after a reconnect, `'reconnecting'` while
|
|
407
|
+
* it retries (media keeps flowing), `'closed'` when it gave up — `code` says why (e.g. `'expired'`).
|
|
408
|
+
*/
|
|
409
|
+
signal: { state: 'connected' | 'reconnecting' | 'closed'; code?: string };
|
|
410
|
+
/**
|
|
411
|
+
* A tile's weave state settled: its first join (`firstWoven`), or its re-join after a resize
|
|
412
|
+
* (`rewoven()`). `id` is a participant's, or `'self'` for your self view. `'flat'`: the wall
|
|
413
|
+
* says the tile will not weave (`reason`, e.g. `'layer-failed'`) — it is shown flat.
|
|
414
|
+
*/
|
|
415
|
+
weave: { id: string; state: 'woven' | 'flat'; reason?: string; ms: number };
|
|
416
|
+
/** A participant (or `'self'`) started / stopped speaking. */
|
|
417
|
+
speaking: { id: string; on: boolean };
|
|
357
418
|
}
|
|
358
419
|
|
|
359
420
|
export interface CallHandle {
|
|
@@ -391,8 +452,37 @@ export interface CallHandle {
|
|
|
391
452
|
setDepth(v: number | null): number;
|
|
392
453
|
/** Turn mono→3D on/off at runtime (no argument toggles). Off releases every lift; tiles go flat. */
|
|
393
454
|
setMono3D(on?: boolean): boolean;
|
|
394
|
-
/**
|
|
455
|
+
/**
|
|
456
|
+
* A participant's tile element (for `layout: 'none'`), or null; `'self'` is your self view
|
|
457
|
+
* (`data-dxr-peer="self"`). Style it in place; never move it.
|
|
458
|
+
*/
|
|
395
459
|
tile(peerId: string): HTMLElement | null;
|
|
460
|
+
/** Your display name, or null. */
|
|
461
|
+
readonly name: string | null;
|
|
462
|
+
/** Change your display name (re-sent to everyone in `hello`). Returns it sanitised (null = none). */
|
|
463
|
+
setName(name: string | null): string | null;
|
|
464
|
+
/**
|
|
465
|
+
* An app message to one participant over the call's data channel (arrives as their `message`
|
|
466
|
+
* event). `data` must be JSON-serialisable and at most 16 KiB serialised; at most 20 messages a
|
|
467
|
+
* second per participant. Returns false (nothing sent) when over a cap, not serialisable, or the
|
|
468
|
+
* participant is not connected. A participant on an SDK before 1.48 drops it silently.
|
|
469
|
+
*/
|
|
470
|
+
send(peerId: string, data: unknown): boolean;
|
|
471
|
+
/** {@link CallHandle.send} to everyone. True only if every connected participant got it. */
|
|
472
|
+
broadcast(data: unknown): boolean;
|
|
473
|
+
/** The signalling connection — see the `signal` event. Null before join and after leave. */
|
|
474
|
+
readonly signal: 'connected' | 'reconnecting' | 'closed' | null;
|
|
475
|
+
/**
|
|
476
|
+
* Show (true) or hide (false) your self view; no argument toggles. Hiding removes its layer
|
|
477
|
+
* from the wall (never a hidden live registration). Returns the new state.
|
|
478
|
+
*/
|
|
479
|
+
setSelfView(on?: boolean): boolean;
|
|
480
|
+
/** Switch the microphone (a `deviceId`; null = the default). Resolves false, keeping the old one, if it cannot open. */
|
|
481
|
+
setMic(deviceId: string | null): Promise<boolean>;
|
|
482
|
+
/** Play every participant's audio (also later ones) to `sinkId` (`HTMLMediaElement.setSinkId`). Resolves false if any tile refused. */
|
|
483
|
+
setAudioOutput(sinkId: string): Promise<boolean>;
|
|
484
|
+
/** What you send (camera + microphone). Read-only — use `setCamera` / `setMic` to change it. */
|
|
485
|
+
readonly localStream: MediaStream | null;
|
|
396
486
|
on<K extends keyof CallEvents>(type: K, cb: (e: CallEvents[K]) => void): () => void;
|
|
397
487
|
off<K extends keyof CallEvents>(type: K, cb: (e: CallEvents[K]) => void): void;
|
|
398
488
|
/** Explicitly UNSTABLE: the wall, the transport, per-peer hello / route / convergence / lift / quality, the lift pool. For devtools. */
|
|
@@ -421,8 +511,8 @@ export function parseInviteLink(link: string | { hash?: string; href?: string }
|
|
|
421
511
|
// a bubbling, composed `CustomEvent` named `dxr-call:<event>` with the payload in `detail`, and
|
|
422
512
|
// exposes the handle as `el.call`. Attributes (read once, at connect): `room`, `signaling` (a
|
|
423
513
|
// URL), `key`, `camera`, `layout`, `accent`, `max-peers`, `no-ui`, `ui="tiles"`, `auto-join`,
|
|
424
|
-
// `mono3d="off"`, `no-audio`, `no-self-view`, `no-auto-converge`, `
|
|
425
|
-
// `browser-url`, `debug`. Everything else is `el.options`, set before connect; options win.
|
|
514
|
+
// `mono3d="off"`, `no-audio`, `no-self-view`, `no-auto-converge`, `no-send-convergence`,
|
|
515
|
+
// `tile-aspect`, `invite-base`, `browser-url`, `debug`. Everything else is `el.options`, set before connect; options win.
|
|
426
516
|
|
|
427
517
|
export class DxrCallElement extends HTMLElement {
|
|
428
518
|
/** The mount seam: null = `mountCall` of `./call`; `./call/full` sets its own. */
|
package/camera.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// The public surface of this file is pinned by test/api-snapshot: a change here fails `npm test`
|
|
12
12
|
// until the snapshot is updated in the same change (`node tools/api-surface.mjs --update`).
|
|
13
13
|
|
|
14
|
-
import type { Inline3D, Inline3DUnsupported, TileHandle } from './index.js';
|
|
14
|
+
import type { Inline3D, Inline3DUnsupported, TileHandle, RectCoverOption } from './index.js';
|
|
15
15
|
import type { CursorShape, CursorShapeHit, CursorShapeOption, CursorSoftenOption } from './cursor-depth.js';
|
|
16
16
|
|
|
17
17
|
/** What the camera delivers: a side-by-side pair (left eye left) or one flat picture. */
|
|
@@ -56,6 +56,17 @@ export interface CameraOptions {
|
|
|
56
56
|
* is then true. A throwing hook keeps the raw pair (`cam.rectifyError`).
|
|
57
57
|
*/
|
|
58
58
|
rectify?: (stream: MediaStream, info: { width: number; height: number; deviceId: string | null; label: string }) => MediaStream | Promise<MediaStream>;
|
|
59
|
+
/**
|
|
60
|
+
* Devices no probe may open. Built in, always (unless `false`): video inputs whose label marks a
|
|
61
|
+
* sensor rather than a camera — hand tracking, an eye tracker's or a "tracking camera"'s stream,
|
|
62
|
+
* a depth camera (`/\b(hand[- ]?track|tracking camera|eye[- ]?track|depth camera)/i`). Add your
|
|
63
|
+
* own: a string matches a label (case-insensitive substring) or a deviceId exactly; a RegExp
|
|
64
|
+
* tests the label. Applied on every probe path, the last-resort "try every device" one
|
|
65
|
+
* included; a default camera that turns out to be excluded is closed again. Left-out devices
|
|
66
|
+
* are listed in `skipped` with `excluded: true`. `false` turns the built-in list off. An explicit
|
|
67
|
+
* `deviceId` (`prefer`) is your choice and is opened regardless.
|
|
68
|
+
*/
|
|
69
|
+
exclude?: ReadonlyArray<string | RegExp> | false;
|
|
59
70
|
/** Verbose console logging. */
|
|
60
71
|
debug?: boolean;
|
|
61
72
|
}
|
|
@@ -64,7 +75,16 @@ export interface CameraOptions {
|
|
|
64
75
|
export type CameraErrorCode = 'camera-busy' | 'permission-denied' | 'no-camera';
|
|
65
76
|
export interface CameraError extends Error {
|
|
66
77
|
code: CameraErrorCode;
|
|
67
|
-
skipped: ReadonlyArray<
|
|
78
|
+
skipped: ReadonlyArray<CameraSkip>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A device {@link openCamera} tried and did not use: busy, refused, or left out (`excluded`, `error: 'excluded'`). */
|
|
82
|
+
export interface CameraSkip {
|
|
83
|
+
label: string;
|
|
84
|
+
error: string;
|
|
85
|
+
busy?: boolean;
|
|
86
|
+
denied?: boolean;
|
|
87
|
+
excluded?: boolean;
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
export interface CameraEvents {
|
|
@@ -162,7 +182,7 @@ export interface StereoCamera {
|
|
|
162
182
|
/** True when this module opened the device (and `close()` stops it); false for a page-supplied stream. */
|
|
163
183
|
readonly owned: boolean;
|
|
164
184
|
/** Devices tried and not used (busy, denied, not a pair) — the clue a `'camera-busy'` report carries. */
|
|
165
|
-
readonly skipped: ReadonlyArray<
|
|
185
|
+
readonly skipped: ReadonlyArray<CameraSkip>;
|
|
166
186
|
readonly state: 'live' | 'ended' | 'closed';
|
|
167
187
|
/** A detached, playing `<video>` on the stream (never in the DOM); null without a document. */
|
|
168
188
|
readonly video: HTMLVideoElement | null;
|
|
@@ -214,8 +234,17 @@ export interface CameraViewOptions {
|
|
|
214
234
|
autoConverge?: boolean;
|
|
215
235
|
/** [-1, 1], + = push back: an offset on top of the convergence. Default 0. */
|
|
216
236
|
depth?: number;
|
|
217
|
-
/**
|
|
218
|
-
|
|
237
|
+
/**
|
|
238
|
+
* The tile's aspect (w/h) = the woven buffer's per-eye aspect. Default 16/9. `'fill'`: the
|
|
239
|
+
* canvas's own CSS box, read every frame (clamped to 0.3–4), for a page that sizes the tile.
|
|
240
|
+
*/
|
|
241
|
+
aspect?: number | 'fill';
|
|
242
|
+
/**
|
|
243
|
+
* The core's rect cover (`addImage`'s `rectCover`) on the woven registration: `'auto'`
|
|
244
|
+
* covers the tile across a later resize or move until it is woven again. Default: the core's
|
|
245
|
+
* (`'off'`).
|
|
246
|
+
*/
|
|
247
|
+
rectCover?: RectCoverOption;
|
|
219
248
|
/**
|
|
220
249
|
* `'depth'`: while the pointer is over the woven view and what is under it comes out of the
|
|
221
250
|
* screen, the normal arrow is drawn just in front of it (runtime ADR-046, measured off the pair's
|