@displayxr/inline3d 1.30.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/CHANGELOG.md +63 -0
- package/README.md +5 -5
- package/call.d.ts +53 -4
- package/dist/call.js +4 -4
- package/index.d.ts +56 -0
- package/js/call/signaling.js +66 -9
- package/js/inline3d-call.js +31 -1
- package/js/inline3d-model-playcanvas.js +1 -0
- package/js/inline3d-model.js +1 -0
- package/js/inline3d-splat-playcanvas.js +14 -3
- package/js/inline3d-splat.js +1 -0
- package/js/inline3d-viewer-ease.js +360 -0
- package/js/inline3d-viewer.js +15 -4
- package/js/inline3d.js +13 -0
- package/model.d.ts +8 -1
- package/package.json +4 -2
- package/splat.d.ts +8 -1
- package/viewer.d.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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
|
+
## 1.31.0 — 2026-10-05
|
|
9
|
+
|
|
10
|
+
The 1.29 `./call` option spellings stay accepted (with their warning) for one more release; they go in 1.32.
|
|
11
|
+
|
|
12
|
+
Touches the **preview tier** (`./viewer`, `./splat`, `./model`: the eye cameras they draw) and adds
|
|
13
|
+
one **core** option and export. Pixels move only around a tracking acquisition or loss.
|
|
14
|
+
|
|
15
|
+
### Added — the tracking ease (`viewerEase`, default on)
|
|
16
|
+
|
|
17
|
+
- When a viewer is acquired (the views jump from the untracked fallback viewpoint to the tracked
|
|
18
|
+
eyes) or lost, the SDK's renderers now glide the eye cameras from where they were drawn to the
|
|
19
|
+
live views over 300 ms (smoothstep) instead of snapping in one frame. Triggered by a jump of more
|
|
20
|
+
than 0.15 eye separations in one frame within 600 ms of a `trackingState` edge (the state and the
|
|
21
|
+
jump need not land on the same frame; an edge with no jump changes nothing), or, where the browser
|
|
22
|
+
reports no state, by a one-frame jump of more than half an eye separation. The eye positions for that test are read off the projections,
|
|
23
|
+
so rig changes, scene scale and canvas size do not trigger it.
|
|
24
|
+
- Exact, not approximate: every DisplayXR frustum is affine in the eye position, so offsetting the
|
|
25
|
+
pose (in the view's own frame) and the projection (element-wise) by the same weight gives the
|
|
26
|
+
frustum the runtime would report for the eased eye. The rig map and the layer rig see consistent
|
|
27
|
+
views throughout.
|
|
28
|
+
- Left alone: ordinary head motion, a vendor-animated ramp, view-count changes (2D<->3D mode switch),
|
|
29
|
+
buffer resizes, replayed frames, mono.
|
|
30
|
+
- `createInline3D({ viewerEase })` sets the session default; `viewerEase` on `SceneViewer`,
|
|
31
|
+
`addSplat` and `addModel` overrides it. `false` (or `{ durationMs: 0 }`) restores the snap;
|
|
32
|
+
`{ durationMs, easing }` tunes it. Core exports `ViewerEase`, `resolveViewerEaseOption`,
|
|
33
|
+
`frameTrackingState`, `VIEWER_EASE_DEFAULT_MS` for pages that render their own `addScene`.
|
|
34
|
+
`handle.viewer.viewerEase.last` reports the last frame's weight and trigger.
|
|
35
|
+
|
|
8
36
|
## 1.30.0 — 2026-10-05
|
|
9
37
|
|
|
10
38
|
Touches the **preview tier** only (`./call`, `./call/full`, the new `./camera` and `./splat/playcanvas`). RFC 0003
|
|
@@ -129,6 +157,41 @@ Internal moves (no API): `js/call/capture.js` → `js/camera/capture.js`, `js/ca
|
|
|
129
157
|
parses, a WebM whose tags parse). Panel runs still required: the mirrored self view on glass,
|
|
130
158
|
an SBS photo opened in `/player`.
|
|
131
159
|
|
|
160
|
+
### `./call` + the hosted service (RFC 0003 §5, phase C3)
|
|
161
|
+
|
|
162
|
+
Also in this release: the hosted signalling service productised, with the **reference server**
|
|
163
|
+
(`signaling/`). Additive for pages; `dxr-signal/1` gains optional fields and new error codes only.
|
|
164
|
+
|
|
165
|
+
- **Publishable keys.** `mountCall(el, { key: 'pk_…' })` / `<dxr-call key="pk_…">` now mean
|
|
166
|
+
something: the hosted server validates the key against its origin list on connect, applies the
|
|
167
|
+
keyed tier's limits (10× anonymous), and attributes every session to the key id (billing
|
|
168
|
+
identity from day one; billing is off). Issued by hand: `tools/signal-keys.mjs`. No key = the
|
|
169
|
+
anonymous tier, as before, with tighter limits.
|
|
170
|
+
- **Limits** (RFC §5b): joins per address per minute, concurrent rooms per address / per key, room
|
|
171
|
+
lifetime (2 h / 8 h → `expired`), TURN mints per address per hour. A refused join is an `error`
|
|
172
|
+
with `rate-limited` (+ `error.retryMs`) / `quota` / `bad-key` / `origin-not-allowed` / `blocked`
|
|
173
|
+
— never a silent hang; reconnects honour `retryMs`.
|
|
174
|
+
- **TURN metering and the monthly cap** (RFC §5c, Decision 2): the service meters minted
|
|
175
|
+
credentials (and, when configured, Cloudflare's real per-key relay bytes) against the free
|
|
176
|
+
1000 GB + a $100 hard cap. At 70 % anonymous credentials shorten, at 90 % anonymous sessions
|
|
177
|
+
get none (`warning` **`turn-shed`**), at 100 % nobody does (`error` **`turn-cap`**) — the call
|
|
178
|
+
still joins and direct P2P keeps working. `welcome.turn` / `SignalingSession.turn` carry the
|
|
179
|
+
decision (`SignalingTurnStatus`).
|
|
180
|
+
- **Domain move prepared, not cut over** (RFC §5g): the hosted server answers on every host of
|
|
181
|
+
`DXR_SIGNAL_ALIASES` (`dxr-signal.displayxr.workers.dev`, `signal.displayxr.org`) and
|
|
182
|
+
`dxrSignaling()` fails over between them on connect and reconnect. `DXR_SIGNAL_DEFAULT` is
|
|
183
|
+
unchanged; the alias list is internal (`js/call/signaling.js`, importable by path — the
|
|
184
|
+
`./call` entry keeps its 9 exports). `dxrSignaling(url, { aliases })` adds fallbacks for a
|
|
185
|
+
self-hosted URL. Options and the recommendation: `docs/signaling-domain.md`.
|
|
186
|
+
- **Docs:** `docs/privacy-call.md` (what the server sees, retention, the E2E caveat),
|
|
187
|
+
`signaling/README.md` (keys, limits, budget, self-host vars, a coturn recipe, staging),
|
|
188
|
+
`docs/call.md` troubleshooting rows for the new codes.
|
|
189
|
+
- **Server:** `signaling/gate.mjs` + `limits.mjs` + `keys.mjs` + `turn-budget.mjs`, shared by
|
|
190
|
+
the Worker (Durable Objects per subject + one budget, a KV namespace for keys, an hourly
|
|
191
|
+
analytics cron, `/admin/*` behind `ADMIN_TOKEN`) and the Node dev server (same admission in
|
|
192
|
+
memory). Staging twin `signaling/deploy/staging.toml`; the C3 gate
|
|
193
|
+
`test/e2e/signal-staging.e2e.mjs`.
|
|
194
|
+
|
|
132
195
|
## 1.29.0 — 2026-09-30
|
|
133
196
|
|
|
134
197
|
Touches the **core** (additive: `sharedInline3D()`, `handle.rewoven()`) and the **preview tier**
|
package/README.md
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
Inline-3D **web samples** and a small JS SDK for the
|
|
4
4
|
[DisplayXR Browser](https://github.com/DisplayXR/displayxr-browser) — the DisplayXR analog of
|
|
5
5
|
[`immersive-web/webxr-samples`](https://github.com/immersive-web/webxr-samples). This is the canonical
|
|
6
|
-
repo web developers clone to build
|
|
6
|
+
repo web developers clone to build 3D pages for spatial displays, and the site the browser navigates to for the
|
|
7
7
|
live demos.
|
|
8
8
|
|
|
9
9
|
**▶ See it live:** <https://displayxr.github.io/displayxr-web/> — open in the
|
|
10
|
-
[DisplayXR Browser](https://github.com/DisplayXR/displayxr-browser/releases) on
|
|
11
|
-
|
|
10
|
+
[DisplayXR Browser](https://github.com/DisplayXR/displayxr-browser/releases) on a spatial display to see
|
|
11
|
+
them in 3D; in any other browser the pages render as a normal 2D fallback, so they're safe to view
|
|
12
12
|
anywhere.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
@@ -107,7 +107,7 @@ that is intentionally **not** in 1.0): [`docs/sdk-stability.md`](docs/sdk-stabil
|
|
|
107
107
|
|
|
108
108
|
## Quick start
|
|
109
109
|
|
|
110
|
-
One SDK call turns a `<canvas>` into a
|
|
110
|
+
One SDK call turns a `<canvas>` into a 3D window on a spatial display. Everything degrades to plain 2D on a
|
|
111
111
|
non-DisplayXR browser, so a page is safe to ship anywhere.
|
|
112
112
|
|
|
113
113
|
```js
|
|
@@ -117,7 +117,7 @@ const wall = await createInline3D(); // opens an inline-3d session (detect
|
|
|
117
117
|
if (!wall.supported) {
|
|
118
118
|
// Not the DisplayXR Browser (or no 3D display) — your page's normal 2D content shows. Done.
|
|
119
119
|
} else {
|
|
120
|
-
// Woven
|
|
120
|
+
// Woven 3D. Add content — one call per element:
|
|
121
121
|
wall.addImage(canvas, 'photo-sbs.png'); // a still side-by-side 3D photo
|
|
122
122
|
wall.addVideo(canvas, videoEl); // an SBS 3D video
|
|
123
123
|
wall.addScene(canvas, (views, layer) => { /* render */ });// a live three.js / WebGL stereo scene
|
package/call.d.ts
CHANGED
|
@@ -60,19 +60,51 @@ export interface SignalingSession {
|
|
|
60
60
|
peers: string[];
|
|
61
61
|
/** Short-lived TURN credentials minted by the server, if it has any. */
|
|
62
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;
|
|
63
69
|
send(to: string, data: unknown): void;
|
|
64
70
|
leave(): void;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/** The hosted DisplayXR signalling server (`dxrSignaling()` with no URL). */
|
|
68
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
|
+
|
|
69
89
|
/**
|
|
70
90
|
* The `dxr-signal/1` JSON-over-WebSocket client (protocol: signaling/README.md). `url` is your
|
|
71
91
|
* server's base, e.g. `wss://signal.example.com` or `ws://localhost:8787` — the reference servers
|
|
72
92
|
* are `signaling/worker.mjs` (Cloudflare) and `node signaling/dev-server.mjs`. No URL = the hosted
|
|
73
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.
|
|
74
96
|
*/
|
|
75
|
-
export function dxrSignaling(
|
|
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;
|
|
76
108
|
|
|
77
109
|
/** The eight named accents `theme.accent` takes (the same names `/player` uses). */
|
|
78
110
|
export type CallAccent = 'azure' | 'violet' | 'magenta' | 'sunset' | 'amber' | 'lime' | 'mint' | 'ice';
|
|
@@ -248,7 +280,13 @@ export interface CallPeer {
|
|
|
248
280
|
* `error` codes — fatal for a feature, never for the widget. `'camera-busy'` (held by another
|
|
249
281
|
* app: audio-only until `retryCamera()`), `'no-camera'`, `'permission-denied'`, `'unreachable'`
|
|
250
282
|
* (`error.peer` = the tile), `'room-full'`, `'session-ended'`, `'signaling-closed'`,
|
|
251
|
-
* `'
|
|
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).
|
|
252
290
|
*/
|
|
253
291
|
export type CallErrorCode =
|
|
254
292
|
| 'camera-busy'
|
|
@@ -262,11 +300,19 @@ export type CallErrorCode =
|
|
|
262
300
|
| 'camera-failed'
|
|
263
301
|
| 'rectify-failed'
|
|
264
302
|
| 'mount-failed'
|
|
265
|
-
|
|
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'
|
|
266
312
|
| (string & {});
|
|
267
313
|
|
|
268
314
|
/** `warning` codes — degraded, not broken. */
|
|
269
|
-
export type CallWarningCode = 'lift-not-bundled' | (string & {});
|
|
315
|
+
export type CallWarningCode = 'lift-not-bundled' | 'turn-shed' | (string & {});
|
|
270
316
|
|
|
271
317
|
export interface CallEvents {
|
|
272
318
|
joined: { room: string; id: string };
|
|
@@ -289,6 +335,9 @@ export interface CallEvents {
|
|
|
289
335
|
* module could not be imported from this build — they stay flat. Fix: `mono3D: lift` or
|
|
290
336
|
* `./call/full`. Emitted once per call with one `console.warn`. A display that honestly cannot
|
|
291
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.
|
|
292
341
|
*/
|
|
293
342
|
warning: { code: CallWarningCode; message: string };
|
|
294
343
|
}
|