@lumencast/runtime 0.11.0 → 0.12.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/README.md +39 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/{broadcast-DtHoU_fS.js → broadcast-ydSpPUje.js} +3 -3
- package/dist/{broadcast-DtHoU_fS.js.map → broadcast-ydSpPUje.js.map} +1 -1
- package/dist/{control-B9frEbNG.js → control-zTsF-bHP.js} +4 -4
- package/dist/{control-B9frEbNG.js.map → control-zTsF-bHP.js.map} +1 -1
- package/dist/{index-Dz27r92m.js → index-ClWi5UzJ.js} +361 -326
- package/dist/index-ClWi5UzJ.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lumencast.js +1 -1
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +52 -0
- package/dist/mount.js.map +1 -1
- package/dist/{status-pill-B2vBTwRC.js → status-pill-DkHIOL5V.js} +2 -2
- package/dist/{status-pill-B2vBTwRC.js.map → status-pill-DkHIOL5V.js.map} +1 -1
- package/dist/{test-DD2SBDku.js → test-COpMkyms.js} +4 -4
- package/dist/{test-DD2SBDku.js.map → test-COpMkyms.js.map} +1 -1
- package/dist/transport/ws.d.ts +4 -1
- package/dist/transport/ws.d.ts.map +1 -1
- package/dist/transport/ws.js +7 -0
- package/dist/transport/ws.js.map +1 -1
- package/dist/{tree-CgU_sUwI.js → tree-Cubmxeqo.js} +2 -2
- package/dist/{tree-CgU_sUwI.js.map → tree-Cubmxeqo.js.map} +1 -1
- package/dist/types.d.ts +18 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/webrtc/index.d.ts +1 -1
- package/dist/webrtc/index.d.ts.map +1 -1
- package/dist/webrtc/index.js.map +1 -1
- package/dist/webrtc/peer-stream-registry.d.ts +17 -0
- package/dist/webrtc/peer-stream-registry.d.ts.map +1 -1
- package/dist/webrtc/peer-stream-registry.js +22 -0
- package/dist/webrtc/peer-stream-registry.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1 -0
- package/src/mount.ts +54 -0
- package/src/transport/ws.ts +11 -0
- package/src/types.ts +18 -2
- package/src/webrtc/index.ts +1 -0
- package/src/webrtc/peer-stream-registry.ts +38 -0
- package/dist/index-Dz27r92m.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ErrorCode } from "@lumencast/protocol";
|
|
1
|
+
import type { ErrorCode, SceneRosterEntry } from "@lumencast/protocol";
|
|
2
2
|
import type { ResolveCaptureDevice } from "./render/primitives/capture";
|
|
3
3
|
import type { ResolvePeerStream, SubscribePeerStream } from "./render/primitives/media";
|
|
4
4
|
import type { ReservedCamLeaves } from "./state/reserved-leaves";
|
|
@@ -14,7 +14,12 @@ export interface LumencastError {
|
|
|
14
14
|
recoverable: boolean;
|
|
15
15
|
}
|
|
16
16
|
export interface LumencastMetric {
|
|
17
|
-
name: "delta_received" | "delta_applied" | "frame_dropped" | "reconnect" | "snapshot_received" | "scene_changed"
|
|
17
|
+
name: "delta_received" | "delta_applied" | "frame_dropped" | "reconnect" | "snapshot_received" | "scene_changed"
|
|
18
|
+
/** A render bundle was warmed ahead of time from a roster entry (either a
|
|
19
|
+
* `scene_roster` frame or the `preloadRoster` mount option). Emitted once
|
|
20
|
+
* the warm fetch resolves (or from cache). Carries `scene_id` +
|
|
21
|
+
* `scene_version` + `source` ("frame" | "option"). */
|
|
22
|
+
| "roster_preloaded";
|
|
18
23
|
[key: string]: unknown;
|
|
19
24
|
}
|
|
20
25
|
/** Anti-silent-drop render diagnostic (ADR 001 §3.4, issue #34).
|
|
@@ -88,6 +93,17 @@ export interface MountOptions {
|
|
|
88
93
|
* reads it. Omit it and the reserved leaves are simply not surfaced (the
|
|
89
94
|
* preview/headless paths are unaffected). */
|
|
90
95
|
onReservedLeaves?: (leaves: ReservedCamLeaves) => void;
|
|
96
|
+
/** Preload the render bundles of a known scene roster so the FIRST switch to
|
|
97
|
+
* each scene is instant (a warm cache hit instead of a blocking fetch).
|
|
98
|
+
* Each entry is `{ scene_id, scene_version }`. Warmed in the background right
|
|
99
|
+
* after mount — best-effort: a failed warm is swallowed (the scene still
|
|
100
|
+
* fetches on demand at switch time) and never blocks or errors the mount.
|
|
101
|
+
*
|
|
102
|
+
* This is the PUBLIC preload surface (lumencast-js #87b) for hosts that
|
|
103
|
+
* already know the roster at mount time. When the server also emits
|
|
104
|
+
* `scene_roster` frames the runtime warms from those too — both paths feed
|
|
105
|
+
* the same cache and are idempotent (a version is warmed at most once). */
|
|
106
|
+
preloadRoster?: readonly SceneRosterEntry[];
|
|
91
107
|
}
|
|
92
108
|
export interface LumencastHandle {
|
|
93
109
|
/** Tear down the WS, unmount the React tree, release timers. Idempotent. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,MAAM,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,sBAAsB,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EACA,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,WAAW,GACX,mBAAmB,GACnB,eAAe;IACjB;;;2DAGuD;OACrD,kBAAkB,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;+BAE+B;AAC/B,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IACrE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C;;;yDAGqD;IACrD,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACzD;;;;;;;yEAOqE;IACrE,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;gFAK4E;IAC5E,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;qEAGiE;IACjE,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;;;;;;;kDAS8C;IAC9C,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACvD;;;;;;;;;gFAS4E;IAC5E,aAAa,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC3C;AAED,YAAY,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/webrtc/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MeetViewer, type MeetViewerOptions } from "./meet-viewer.js";
|
|
2
2
|
import { type PeerStreamListener, type PeerStreamRegistry } from "./peer-stream-registry.js";
|
|
3
3
|
export { MeetViewer, type MeetViewerOptions, type MeetViewerDeps, type PeerInfo, type RemoteTrackEvent, } from "./meet-viewer.js";
|
|
4
|
-
export { createPeerStreamRegistry, type PeerStreamRegistry, type PeerStreamListener, } from "./peer-stream-registry.js";
|
|
4
|
+
export { createPeerStreamRegistry, type PeerStreamRegistry, type PeerStreamListener, type RosterListener, } from "./peer-stream-registry.js";
|
|
5
5
|
export interface PeerViewer {
|
|
6
6
|
/** Join the room(s) (viewer role, no capture). */
|
|
7
7
|
join: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webrtc/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAyB,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webrtc/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAyB,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,cAAc,GACpB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,yEAAyE;IACzE,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAC;IAC7D,8EAA8E;IAC9E,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,KAAK,MAAM,IAAI,CAAC;IACrF,mDAAmD;IACnD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;gDAC4C;IAC5C,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED;;;;mFAImF;AACnF;;;;;;;8BAO8B;AAC9B,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1C;AA+BD;8EAC8E;AAC9E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAevE;AAED,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG;IAC1D,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB;oFACgF;IAChF,IAAI,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,mBAAoB,SAAQ,UAAU;IACrD;;oEAEgE;IAChE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED;;;;;;;;;;2EAU2E;AAC3E,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CA8ErB;AAED;;oEAEoE;AACpE,MAAM,MAAM,mBAAmB,GAC3B,0BAA0B,GAC1B,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1D;;oCAEoC;AACpC,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,mBAAmB,GAAG,mBAAmB,CAYjG"}
|
package/dist/webrtc/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webrtc/index.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,mEAAmE;AACnE,mFAAmF;AACnF,8EAA8E;AAC9E,kEAAkE;AAClE,+EAA+E;AAC/E,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,6EAA6E;AAE7E,OAAO,EAAE,UAAU,EAAiD,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EACL,wBAAwB,GAGzB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,UAAU,GAKX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webrtc/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
export type PeerStreamListener = (stream: MediaStream | null) => void;
|
|
2
|
+
/** Notified whenever the live roster changes ORDER (a peer connects for the first
|
|
3
|
+
* time, or a connected peer drops). A pure re-`set` of an already-present label's
|
|
4
|
+
* stream is NOT a roster change (same arrival order) — it reaches per-label
|
|
5
|
+
* `subscribe` listeners only. Carries no payload : the listener re-reads
|
|
6
|
+
* `orderedLabels()`. */
|
|
7
|
+
export type RosterListener = () => void;
|
|
2
8
|
export interface PeerStreamRegistry {
|
|
3
9
|
/** #4 contract — the current stream for a label, or `null` if the peer is not
|
|
4
10
|
* connected (yet / any more). Synchronous, side-effect free. */
|
|
5
11
|
resolve(peerLabel: string): MediaStream | null;
|
|
12
|
+
/** The live `peer_label`s in ARRIVAL ORDER (insertion order of the backing Map,
|
|
13
|
+
* restricted to labels that currently hold a stream). Drives positional slot
|
|
14
|
+
* resolution (`@<n>` → `orderedLabels()[n]`, ADR Blue 009 axe 1 positional
|
|
15
|
+
* variant). Returns a fresh array — safe for the caller to keep / index. */
|
|
16
|
+
orderedLabels(): string[];
|
|
6
17
|
/** Push channel for the LIVE `media` primitive : invoked immediately with the
|
|
7
18
|
* current value, then on every change for `peerLabel`. Returns an
|
|
8
19
|
* unsubscribe. */
|
|
9
20
|
subscribe(peerLabel: string, listener: PeerStreamListener): () => void;
|
|
21
|
+
/** Roster-change channel : invoked whenever a peer connects (new label) or
|
|
22
|
+
* leaves (label dropped) — i.e. whenever `orderedLabels()` could shift. Lets a
|
|
23
|
+
* positional consumer re-resolve `@<n>` when arrivals/departures shuffle the
|
|
24
|
+
* order. NOT invoked on a pure stream replacement of an existing label. Returns
|
|
25
|
+
* an unsubscribe. */
|
|
26
|
+
subscribeRoster(listener: RosterListener): () => void;
|
|
10
27
|
/** Viewer-side : publish / replace a peer's stream (peer connected). */
|
|
11
28
|
set(peerLabel: string, stream: MediaStream): void;
|
|
12
29
|
/** Viewer-side : drop a peer's stream (peer left / connection failed). The
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer-stream-registry.d.ts","sourceRoot":"","sources":["../../src/webrtc/peer-stream-registry.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IACjC;qEACiE;IACjE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAC/C;;uBAEmB;IACnB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAAC;IACvE,wEAAwE;IACxE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAClD;;gDAE4C;IAC5C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;iBACa;IACb,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,
|
|
1
|
+
{"version":3,"file":"peer-stream-registry.d.ts","sourceRoot":"","sources":["../../src/webrtc/peer-stream-registry.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;AAEtE;;;;yBAIyB;AACzB,MAAM,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC;AAExC,MAAM,WAAW,kBAAkB;IACjC;qEACiE;IACjE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAC/C;;;iFAG6E;IAC7E,aAAa,IAAI,MAAM,EAAE,CAAC;IAC1B;;uBAEmB;IACnB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAAC;IACvE;;;;0BAIsB;IACtB,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;IACtD,wEAAwE;IACxE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAClD;;gDAE4C;IAC5C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;iBACa;IACb,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,CAoE7D"}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
export function createPeerStreamRegistry() {
|
|
24
24
|
const streams = new Map();
|
|
25
25
|
const listeners = new Map();
|
|
26
|
+
const rosterListeners = new Set();
|
|
26
27
|
function notify(peerLabel) {
|
|
27
28
|
const set = listeners.get(peerLabel);
|
|
28
29
|
if (set === undefined)
|
|
@@ -31,10 +32,25 @@ export function createPeerStreamRegistry() {
|
|
|
31
32
|
for (const listener of set)
|
|
32
33
|
listener(value);
|
|
33
34
|
}
|
|
35
|
+
function notifyRoster() {
|
|
36
|
+
for (const listener of [...rosterListeners])
|
|
37
|
+
listener();
|
|
38
|
+
}
|
|
34
39
|
return {
|
|
35
40
|
resolve(peerLabel) {
|
|
36
41
|
return streams.get(peerLabel) ?? null;
|
|
37
42
|
},
|
|
43
|
+
orderedLabels() {
|
|
44
|
+
// Map preserves insertion order = arrival order ; every key holds a stream
|
|
45
|
+
// (a dropped peer is `delete`d in remove()).
|
|
46
|
+
return [...streams.keys()];
|
|
47
|
+
},
|
|
48
|
+
subscribeRoster(listener) {
|
|
49
|
+
rosterListeners.add(listener);
|
|
50
|
+
return () => {
|
|
51
|
+
rosterListeners.delete(listener);
|
|
52
|
+
};
|
|
53
|
+
},
|
|
38
54
|
subscribe(peerLabel, listener) {
|
|
39
55
|
let set = listeners.get(peerLabel);
|
|
40
56
|
if (set === undefined) {
|
|
@@ -57,20 +73,26 @@ export function createPeerStreamRegistry() {
|
|
|
57
73
|
set(peerLabel, stream) {
|
|
58
74
|
if (streams.get(peerLabel) === stream)
|
|
59
75
|
return; // idempotent re-emit guard
|
|
76
|
+
const isNew = !streams.has(peerLabel); // a brand-new arrival shifts the roster
|
|
60
77
|
streams.set(peerLabel, stream);
|
|
61
78
|
notify(peerLabel);
|
|
79
|
+
if (isNew)
|
|
80
|
+
notifyRoster();
|
|
62
81
|
},
|
|
63
82
|
remove(peerLabel) {
|
|
64
83
|
if (!streams.has(peerLabel))
|
|
65
84
|
return;
|
|
66
85
|
streams.delete(peerLabel);
|
|
67
86
|
notify(peerLabel);
|
|
87
|
+
notifyRoster(); // a departure shifts every later position
|
|
68
88
|
},
|
|
69
89
|
clear() {
|
|
70
90
|
const labels = [...streams.keys()];
|
|
71
91
|
streams.clear();
|
|
72
92
|
for (const label of labels)
|
|
73
93
|
notify(label);
|
|
94
|
+
if (labels.length > 0)
|
|
95
|
+
notifyRoster();
|
|
74
96
|
},
|
|
75
97
|
};
|
|
76
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer-stream-registry.js","sourceRoot":"","sources":["../../src/webrtc/peer-stream-registry.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,sCAAsC;AACtC,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,uEAAuE;AACvE,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,wEAAwE;AACxE,oEAAoE;AACpE,2EAA2E;AAC3E,2EAA2E;AAC3E,+EAA+E;AAC/E,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,4DAA4D;
|
|
1
|
+
{"version":3,"file":"peer-stream-registry.js","sourceRoot":"","sources":["../../src/webrtc/peer-stream-registry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumencast/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Browser runtime for Lumencast — mount(), LSDP/1 transport, leaf-grain store, LSML render, animations, overlays.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"framer-motion": "^12.0.0",
|
|
37
37
|
"react": "^19.0.0",
|
|
38
38
|
"react-dom": "^19.0.0",
|
|
39
|
-
"@lumencast/protocol": "0.
|
|
39
|
+
"@lumencast/protocol": "0.12.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@playwright/test": "^1.49.1",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"vite-plugin-dts": "^4.5.0",
|
|
51
51
|
"vitest": "^4.1.5",
|
|
52
52
|
"ws": "^8.18.0",
|
|
53
|
-
"@lumencast/server": "0.
|
|
54
|
-
"@lumencast/
|
|
53
|
+
"@lumencast/dev-server": "0.12.0",
|
|
54
|
+
"@lumencast/server": "0.12.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"dev": "vite",
|
package/src/index.ts
CHANGED
package/src/mount.ts
CHANGED
|
@@ -46,6 +46,11 @@ export function mount(options: MountOptions): LumencastHandle {
|
|
|
46
46
|
|
|
47
47
|
let active = true;
|
|
48
48
|
|
|
49
|
+
// Render-bundle versions already warmed (or warming) by the preload path.
|
|
50
|
+
// Keeps both roster sources (the `scene_roster` frame and the `preloadRoster`
|
|
51
|
+
// mount option) idempotent — a version is fetched by the warmer at most once.
|
|
52
|
+
const warmedVersions = new Set<string>();
|
|
53
|
+
|
|
49
54
|
// ADR Blue 009 §3.2–3.3 — surface the reserved `__cam.*` LSDP leaves (the
|
|
50
55
|
// slot→peer assignments + the receive-only viewer creds) to the host so its
|
|
51
56
|
// WebRTC viewer (Solar) can drive room joins + `x-zab.meet-peer` slot re-keying.
|
|
@@ -107,6 +112,13 @@ export function mount(options: MountOptions): LumencastHandle {
|
|
|
107
112
|
to: frame.scene_version,
|
|
108
113
|
});
|
|
109
114
|
},
|
|
115
|
+
onSceneRoster: (frame) => {
|
|
116
|
+
if (!active) return;
|
|
117
|
+
// The server advertised the show roster (LSDP/1.1 `scene_roster`).
|
|
118
|
+
// Warm every scene's render bundle in the background so the first switch
|
|
119
|
+
// to each is a cache hit, not a blocking fetch.
|
|
120
|
+
warmRoster(frame.entries, "frame");
|
|
121
|
+
},
|
|
110
122
|
onServerError: (frame) => {
|
|
111
123
|
reportError({
|
|
112
124
|
code: frame.code,
|
|
@@ -121,6 +133,12 @@ export function mount(options: MountOptions): LumencastHandle {
|
|
|
121
133
|
|
|
122
134
|
ws.start();
|
|
123
135
|
|
|
136
|
+
// Public preload surface (#87b) — warm a host-supplied roster right after
|
|
137
|
+
// mount, before any switch. Same warmer + cache as the `scene_roster` frame.
|
|
138
|
+
if (options.preloadRoster !== undefined && options.preloadRoster.length > 0) {
|
|
139
|
+
warmRoster(options.preloadRoster, "option");
|
|
140
|
+
}
|
|
141
|
+
|
|
124
142
|
const root: Root = createRoot(options.target);
|
|
125
143
|
root.render(
|
|
126
144
|
createElement(LumencastApp, {
|
|
@@ -165,6 +183,42 @@ export function mount(options: MountOptions): LumencastHandle {
|
|
|
165
183
|
|
|
166
184
|
// --- helpers ----------------------------------------------------------
|
|
167
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Warm the render bundles for a set of roster entries in the background.
|
|
188
|
+
* Best-effort and non-blocking: each `get()` populates the fetcher's cache
|
|
189
|
+
* keyed by `scene_version`, so the eventual `onSnapshot` fetch for that scene
|
|
190
|
+
* is an instant cache hit. Idempotent via `warmedVersions`; the already-active
|
|
191
|
+
* scene is skipped (its bundle is already loaded or in flight). A failed warm
|
|
192
|
+
* is swallowed and its version released so a later roster can retry — the
|
|
193
|
+
* scene still fetches on demand at switch time.
|
|
194
|
+
*/
|
|
195
|
+
function warmRoster(
|
|
196
|
+
entries: readonly { scene_id: string; scene_version: string }[],
|
|
197
|
+
source: "frame" | "option",
|
|
198
|
+
): void {
|
|
199
|
+
const activeVersion = bundleSignal.value?.scene_version;
|
|
200
|
+
for (const { scene_id, scene_version } of entries) {
|
|
201
|
+
if (scene_version === activeVersion) continue;
|
|
202
|
+
if (warmedVersions.has(scene_version)) continue;
|
|
203
|
+
warmedVersions.add(scene_version);
|
|
204
|
+
void bundleFetcher
|
|
205
|
+
.get(scene_id, scene_version)
|
|
206
|
+
.then(() => {
|
|
207
|
+
if (!active) return;
|
|
208
|
+
options.onMetric?.({
|
|
209
|
+
name: "roster_preloaded",
|
|
210
|
+
scene_id,
|
|
211
|
+
scene_version,
|
|
212
|
+
source,
|
|
213
|
+
});
|
|
214
|
+
})
|
|
215
|
+
.catch(() => {
|
|
216
|
+
// Release so a subsequent roster advertisement can retry the warm.
|
|
217
|
+
warmedVersions.delete(scene_version);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
168
222
|
async function onSnapshot(
|
|
169
223
|
fetcher: BundleFetcher,
|
|
170
224
|
bSignal: typeof bundleSignal,
|
package/src/transport/ws.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type ErrorFrame,
|
|
26
26
|
type Patch,
|
|
27
27
|
type SceneChangedFrame,
|
|
28
|
+
type SceneRosterFrame,
|
|
28
29
|
type SnapshotFrame,
|
|
29
30
|
} from "@lumencast/protocol";
|
|
30
31
|
import type { LumencastToken } from "../types.js";
|
|
@@ -57,6 +58,9 @@ export interface WsClientOptions {
|
|
|
57
58
|
onSnapshot?: (frame: SnapshotFrame) => void;
|
|
58
59
|
onDelta?: (frame: DeltaFrame) => void;
|
|
59
60
|
onSceneChanged?: (frame: SceneChangedFrame) => void;
|
|
61
|
+
/** Out-of-band roster advertisement (LSDP/1.1 `scene_roster`, additive).
|
|
62
|
+
* Carries no sequence — dispatched without touching the sequence tracker. */
|
|
63
|
+
onSceneRoster?: (frame: SceneRosterFrame) => void;
|
|
60
64
|
onServerError?: (frame: ErrorFrame) => void;
|
|
61
65
|
/** Wire-level / codec / unrecoverable errors. */
|
|
62
66
|
onTransportError?: (err: TransportError) => void;
|
|
@@ -298,6 +302,13 @@ export class WsClient {
|
|
|
298
302
|
if (!frame.recoverable) this.close();
|
|
299
303
|
return;
|
|
300
304
|
}
|
|
305
|
+
case "scene_roster": {
|
|
306
|
+
// Out-of-band roster metadata: NOT part of the snapshot/delta stream.
|
|
307
|
+
// It carries no seq and MUST NOT touch the sequence tracker — forward
|
|
308
|
+
// it verbatim so the host can warm its render-bundle cache.
|
|
309
|
+
this.opts.onSceneRoster?.(frame);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
301
312
|
case "pong":
|
|
302
313
|
return;
|
|
303
314
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Public types of @lumencast/runtime — must align with RUNTIME-API.md.
|
|
2
2
|
|
|
3
|
-
import type { ErrorCode } from "@lumencast/protocol";
|
|
3
|
+
import type { ErrorCode, SceneRosterEntry } from "@lumencast/protocol";
|
|
4
4
|
import type { ResolveCaptureDevice } from "./render/primitives/capture";
|
|
5
5
|
import type { ResolvePeerStream, SubscribePeerStream } from "./render/primitives/media";
|
|
6
6
|
import type { ReservedCamLeaves } from "./state/reserved-leaves";
|
|
@@ -28,7 +28,12 @@ export interface LumencastMetric {
|
|
|
28
28
|
| "frame_dropped"
|
|
29
29
|
| "reconnect"
|
|
30
30
|
| "snapshot_received"
|
|
31
|
-
| "scene_changed"
|
|
31
|
+
| "scene_changed"
|
|
32
|
+
/** A render bundle was warmed ahead of time from a roster entry (either a
|
|
33
|
+
* `scene_roster` frame or the `preloadRoster` mount option). Emitted once
|
|
34
|
+
* the warm fetch resolves (or from cache). Carries `scene_id` +
|
|
35
|
+
* `scene_version` + `source` ("frame" | "option"). */
|
|
36
|
+
| "roster_preloaded";
|
|
32
37
|
[key: string]: unknown;
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -104,6 +109,17 @@ export interface MountOptions {
|
|
|
104
109
|
* reads it. Omit it and the reserved leaves are simply not surfaced (the
|
|
105
110
|
* preview/headless paths are unaffected). */
|
|
106
111
|
onReservedLeaves?: (leaves: ReservedCamLeaves) => void;
|
|
112
|
+
/** Preload the render bundles of a known scene roster so the FIRST switch to
|
|
113
|
+
* each scene is instant (a warm cache hit instead of a blocking fetch).
|
|
114
|
+
* Each entry is `{ scene_id, scene_version }`. Warmed in the background right
|
|
115
|
+
* after mount — best-effort: a failed warm is swallowed (the scene still
|
|
116
|
+
* fetches on demand at switch time) and never blocks or errors the mount.
|
|
117
|
+
*
|
|
118
|
+
* This is the PUBLIC preload surface (lumencast-js #87b) for hosts that
|
|
119
|
+
* already know the roster at mount time. When the server also emits
|
|
120
|
+
* `scene_roster` frames the runtime warms from those too — both paths feed
|
|
121
|
+
* the same cache and are idempotent (a version is warmed at most once). */
|
|
122
|
+
preloadRoster?: readonly SceneRosterEntry[];
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
export interface LumencastHandle {
|
package/src/webrtc/index.ts
CHANGED
|
@@ -23,14 +23,32 @@
|
|
|
23
23
|
|
|
24
24
|
export type PeerStreamListener = (stream: MediaStream | null) => void;
|
|
25
25
|
|
|
26
|
+
/** Notified whenever the live roster changes ORDER (a peer connects for the first
|
|
27
|
+
* time, or a connected peer drops). A pure re-`set` of an already-present label's
|
|
28
|
+
* stream is NOT a roster change (same arrival order) — it reaches per-label
|
|
29
|
+
* `subscribe` listeners only. Carries no payload : the listener re-reads
|
|
30
|
+
* `orderedLabels()`. */
|
|
31
|
+
export type RosterListener = () => void;
|
|
32
|
+
|
|
26
33
|
export interface PeerStreamRegistry {
|
|
27
34
|
/** #4 contract — the current stream for a label, or `null` if the peer is not
|
|
28
35
|
* connected (yet / any more). Synchronous, side-effect free. */
|
|
29
36
|
resolve(peerLabel: string): MediaStream | null;
|
|
37
|
+
/** The live `peer_label`s in ARRIVAL ORDER (insertion order of the backing Map,
|
|
38
|
+
* restricted to labels that currently hold a stream). Drives positional slot
|
|
39
|
+
* resolution (`@<n>` → `orderedLabels()[n]`, ADR Blue 009 axe 1 positional
|
|
40
|
+
* variant). Returns a fresh array — safe for the caller to keep / index. */
|
|
41
|
+
orderedLabels(): string[];
|
|
30
42
|
/** Push channel for the LIVE `media` primitive : invoked immediately with the
|
|
31
43
|
* current value, then on every change for `peerLabel`. Returns an
|
|
32
44
|
* unsubscribe. */
|
|
33
45
|
subscribe(peerLabel: string, listener: PeerStreamListener): () => void;
|
|
46
|
+
/** Roster-change channel : invoked whenever a peer connects (new label) or
|
|
47
|
+
* leaves (label dropped) — i.e. whenever `orderedLabels()` could shift. Lets a
|
|
48
|
+
* positional consumer re-resolve `@<n>` when arrivals/departures shuffle the
|
|
49
|
+
* order. NOT invoked on a pure stream replacement of an existing label. Returns
|
|
50
|
+
* an unsubscribe. */
|
|
51
|
+
subscribeRoster(listener: RosterListener): () => void;
|
|
34
52
|
/** Viewer-side : publish / replace a peer's stream (peer connected). */
|
|
35
53
|
set(peerLabel: string, stream: MediaStream): void;
|
|
36
54
|
/** Viewer-side : drop a peer's stream (peer left / connection failed). The
|
|
@@ -45,6 +63,7 @@ export interface PeerStreamRegistry {
|
|
|
45
63
|
export function createPeerStreamRegistry(): PeerStreamRegistry {
|
|
46
64
|
const streams = new Map<string, MediaStream>();
|
|
47
65
|
const listeners = new Map<string, Set<PeerStreamListener>>();
|
|
66
|
+
const rosterListeners = new Set<RosterListener>();
|
|
48
67
|
|
|
49
68
|
function notify(peerLabel: string): void {
|
|
50
69
|
const set = listeners.get(peerLabel);
|
|
@@ -53,10 +72,25 @@ export function createPeerStreamRegistry(): PeerStreamRegistry {
|
|
|
53
72
|
for (const listener of set) listener(value);
|
|
54
73
|
}
|
|
55
74
|
|
|
75
|
+
function notifyRoster(): void {
|
|
76
|
+
for (const listener of [...rosterListeners]) listener();
|
|
77
|
+
}
|
|
78
|
+
|
|
56
79
|
return {
|
|
57
80
|
resolve(peerLabel) {
|
|
58
81
|
return streams.get(peerLabel) ?? null;
|
|
59
82
|
},
|
|
83
|
+
orderedLabels() {
|
|
84
|
+
// Map preserves insertion order = arrival order ; every key holds a stream
|
|
85
|
+
// (a dropped peer is `delete`d in remove()).
|
|
86
|
+
return [...streams.keys()];
|
|
87
|
+
},
|
|
88
|
+
subscribeRoster(listener) {
|
|
89
|
+
rosterListeners.add(listener);
|
|
90
|
+
return () => {
|
|
91
|
+
rosterListeners.delete(listener);
|
|
92
|
+
};
|
|
93
|
+
},
|
|
60
94
|
subscribe(peerLabel, listener) {
|
|
61
95
|
let set = listeners.get(peerLabel);
|
|
62
96
|
if (set === undefined) {
|
|
@@ -76,18 +110,22 @@ export function createPeerStreamRegistry(): PeerStreamRegistry {
|
|
|
76
110
|
},
|
|
77
111
|
set(peerLabel, stream) {
|
|
78
112
|
if (streams.get(peerLabel) === stream) return; // idempotent re-emit guard
|
|
113
|
+
const isNew = !streams.has(peerLabel); // a brand-new arrival shifts the roster
|
|
79
114
|
streams.set(peerLabel, stream);
|
|
80
115
|
notify(peerLabel);
|
|
116
|
+
if (isNew) notifyRoster();
|
|
81
117
|
},
|
|
82
118
|
remove(peerLabel) {
|
|
83
119
|
if (!streams.has(peerLabel)) return;
|
|
84
120
|
streams.delete(peerLabel);
|
|
85
121
|
notify(peerLabel);
|
|
122
|
+
notifyRoster(); // a departure shifts every later position
|
|
86
123
|
},
|
|
87
124
|
clear() {
|
|
88
125
|
const labels = [...streams.keys()];
|
|
89
126
|
streams.clear();
|
|
90
127
|
for (const label of labels) notify(label);
|
|
128
|
+
if (labels.length > 0) notifyRoster();
|
|
91
129
|
},
|
|
92
130
|
};
|
|
93
131
|
}
|