@lumencast/runtime 0.9.0 → 0.10.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.
Files changed (90) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/app.d.ts +6 -1
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +3 -1
  5. package/dist/app.js.map +1 -1
  6. package/dist/{broadcast-ryjLRD5q.js → broadcast-L5wm2I6J.js} +3 -3
  7. package/dist/{broadcast-ryjLRD5q.js.map → broadcast-L5wm2I6J.js.map} +1 -1
  8. package/dist/{control-AgxbXOVS.js → control-eEUG7unp.js} +4 -4
  9. package/dist/{control-AgxbXOVS.js.map → control-eEUG7unp.js.map} +1 -1
  10. package/dist/index-Clrya_9l.js +1281 -0
  11. package/dist/index-Clrya_9l.js.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.html +1 -1
  15. package/dist/index.js +11 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/lumencast.js +18 -13
  18. package/dist/mount.d.ts.map +1 -1
  19. package/dist/mount.js +11 -0
  20. package/dist/mount.js.map +1 -1
  21. package/dist/overlay/runtime-context.d.ts +10 -0
  22. package/dist/overlay/runtime-context.d.ts.map +1 -1
  23. package/dist/overlay/runtime-context.js.map +1 -1
  24. package/dist/render/bundle.d.ts +1 -1
  25. package/dist/render/bundle.d.ts.map +1 -1
  26. package/dist/render/bundle.js.map +1 -1
  27. package/dist/render/primitives/capture.d.ts +13 -4
  28. package/dist/render/primitives/capture.d.ts.map +1 -1
  29. package/dist/render/primitives/capture.js +54 -22
  30. package/dist/render/primitives/capture.js.map +1 -1
  31. package/dist/render/primitives/index.d.ts.map +1 -1
  32. package/dist/render/primitives/index.js +4 -0
  33. package/dist/render/primitives/index.js.map +1 -1
  34. package/dist/render/primitives/live-peer-video.d.ts +27 -0
  35. package/dist/render/primitives/live-peer-video.d.ts.map +1 -0
  36. package/dist/render/primitives/live-peer-video.js +64 -0
  37. package/dist/render/primitives/live-peer-video.js.map +1 -0
  38. package/dist/render/primitives/media.d.ts +37 -12
  39. package/dist/render/primitives/media.d.ts.map +1 -1
  40. package/dist/render/primitives/media.js +43 -17
  41. package/dist/render/primitives/media.js.map +1 -1
  42. package/dist/render/primitives/meet-peer.d.ts +31 -0
  43. package/dist/render/primitives/meet-peer.d.ts.map +1 -0
  44. package/dist/render/primitives/meet-peer.js +46 -0
  45. package/dist/render/primitives/meet-peer.js.map +1 -0
  46. package/dist/render/prop-allowlist.d.ts.map +1 -1
  47. package/dist/render/prop-allowlist.js +27 -1
  48. package/dist/render/prop-allowlist.js.map +1 -1
  49. package/dist/render/tree.js +42 -8
  50. package/dist/render/tree.js.map +1 -1
  51. package/dist/{status-pill-BxCdj-KZ.js → status-pill-elORkMrh.js} +2 -2
  52. package/dist/{status-pill-BxCdj-KZ.js.map → status-pill-elORkMrh.js.map} +1 -1
  53. package/dist/{test-CaRHj_J6.js → test-7q_KJkdX.js} +4 -4
  54. package/dist/{test-CaRHj_J6.js.map → test-7q_KJkdX.js.map} +1 -1
  55. package/dist/{tree-BLIxJbD3.js → tree-BMxx5170.js} +522 -436
  56. package/dist/tree-BMxx5170.js.map +1 -0
  57. package/dist/types.d.ts +13 -0
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/webrtc/index.d.ts +76 -0
  60. package/dist/webrtc/index.d.ts.map +1 -0
  61. package/dist/webrtc/index.js +180 -0
  62. package/dist/webrtc/index.js.map +1 -0
  63. package/dist/webrtc/meet-viewer.d.ts +139 -0
  64. package/dist/webrtc/meet-viewer.d.ts.map +1 -0
  65. package/dist/webrtc/meet-viewer.js +379 -0
  66. package/dist/webrtc/meet-viewer.js.map +1 -0
  67. package/dist/webrtc/peer-stream-registry.d.ts +21 -0
  68. package/dist/webrtc/peer-stream-registry.d.ts.map +1 -0
  69. package/dist/webrtc/peer-stream-registry.js +77 -0
  70. package/dist/webrtc/peer-stream-registry.js.map +1 -0
  71. package/package.json +4 -4
  72. package/src/app.tsx +9 -0
  73. package/src/index.ts +35 -0
  74. package/src/mount.ts +11 -0
  75. package/src/overlay/runtime-context.tsx +10 -0
  76. package/src/render/bundle.ts +11 -1
  77. package/src/render/primitives/capture.tsx +73 -28
  78. package/src/render/primitives/index.ts +4 -0
  79. package/src/render/primitives/live-peer-video.tsx +90 -0
  80. package/src/render/primitives/media.tsx +66 -17
  81. package/src/render/primitives/meet-peer.tsx +57 -0
  82. package/src/render/prop-allowlist.ts +27 -1
  83. package/src/render/tree.tsx +44 -8
  84. package/src/types.ts +13 -0
  85. package/src/webrtc/index.ts +252 -0
  86. package/src/webrtc/meet-viewer.ts +497 -0
  87. package/src/webrtc/peer-stream-registry.ts +93 -0
  88. package/dist/index-DrXsLYhe.js +0 -903
  89. package/dist/index-DrXsLYhe.js.map +0 -1
  90. package/dist/tree-BLIxJbD3.js.map +0 -1
@@ -0,0 +1,379 @@
1
+ // WebRTC viewer (mesh, VIEWER role) — ADR 006 §3.3 (C1), issue #3.
2
+ //
3
+ // A viewer-only port of `Prism/src/renderer/src/lib/meet-client.ts` (§1.4) : it
4
+ // joins a Meet room, receives N peers and exposes one `MediaStream` per peer.
5
+ // It NEVER publishes : no `getUserMedia`, no local stream, every transceiver is
6
+ // `recvonly`. Recovering a WebRTC flow needs no capture permission (ADR §2),
7
+ // which is the whole point of the pivot — Solar-CEF (on-air) and the Prism
8
+ // return webview can both be viewers without a capture grant.
9
+ //
10
+ // It reuses the reference's hard-won mesh logic verbatim in spirit :
11
+ // - perfect-negotiation (polite/impolite glare handling) ;
12
+ // - symmetric m-line ordering (audio transceiver first, video second) ;
13
+ // - STUN-first + per-URL TURN iceServers ;
14
+ // - one aggregated `MediaStream` per peer (`track` → addTrack, `ended` →
15
+ // removeTrack), handed to the consumer as `RemoteTrackEvent`.
16
+ //
17
+ // OWNERSHIP : the viewer owns each peer's `RTCPeerConnection` AND the
18
+ // `MediaStream` it aggregates. It is the SOLE authority on the track lifecycle —
19
+ // created on `track`, removed on `ended` / `peer-left` / `connectionstatechange
20
+ // failed|closed`, all torn down on `leave()`. A downstream `<video srcObject>`
21
+ // (the `media` primitive #4) is a pure consumer : unmounting it clears its own
22
+ // `srcObject` and stops nothing. A returning mirror can never kill a peer for
23
+ // the on-air composite.
24
+ //
25
+ // PEER LABEL : the transverse invariant gravé by Conduit (ZabCam contract #6) is
26
+ // a strict STRING equality — `peer_label == MeetClientOptions.name ==
27
+ // RemoteTrackEvent.peerName`, all matching `^[a-z][a-z0-9_-]{0,63}$`. Publishers
28
+ // (#5 Prism / #2 Pulsar) join with `name = peer_label` ; it comes back on the
29
+ // remote side as `PeerInfo.name`, surfaced verbatim as `RemoteTrackEvent.peerName`.
30
+ // The viewer therefore resolves `peer_label → MediaStream` by indexing peers by
31
+ // `peerName` (== label), NEVER by the opaque `peerId`. No separate label channel
32
+ // is needed : the join announce already carries it.
33
+ export class MeetViewer {
34
+ options;
35
+ ws = null;
36
+ remotes = new Map();
37
+ iceServers = [];
38
+ selfId = null;
39
+ listeners = new Map();
40
+ deps;
41
+ constructor(options) {
42
+ this.options = options;
43
+ this.deps = {
44
+ WebSocket: options.deps?.WebSocket ?? globalThis.WebSocket,
45
+ RTCPeerConnection: options.deps?.RTCPeerConnection ?? globalThis.RTCPeerConnection,
46
+ MediaStream: options.deps?.MediaStream ?? globalThis.MediaStream,
47
+ };
48
+ }
49
+ on(type, listener) {
50
+ const set = this.listeners.get(type) ?? new Set();
51
+ this.listeners.set(type, set);
52
+ set.add(listener);
53
+ return () => set.delete(listener);
54
+ }
55
+ /** Join the room as a VIEWER (recvonly). No capture, no publish. */
56
+ join() {
57
+ return this.openSocket();
58
+ }
59
+ /** Leave and tear down every peer connection + aggregated stream. As the
60
+ * track owner, this is where the streams (and the device-side tracks) end. */
61
+ leave() {
62
+ this.send({ type: "leave" });
63
+ this.ws?.close(1000, "viewer-leave");
64
+ }
65
+ /* ---- Socket ------------------------------------------------------- */
66
+ openSocket() {
67
+ const url = new URL(this.options.signalingUrl);
68
+ url.searchParams.set("room", this.options.roomId);
69
+ url.searchParams.set("token", this.options.token);
70
+ return new Promise((resolve, reject) => {
71
+ const ws = new this.deps.WebSocket(url.toString());
72
+ this.ws = ws;
73
+ const onOpen = () => {
74
+ ws.removeEventListener("error", onError);
75
+ // role:"viewer" — the signaling server allocates no publish slot.
76
+ this.send({ type: "join", name: this.options.name, role: "viewer" });
77
+ resolve();
78
+ };
79
+ const onError = (event) => {
80
+ ws.removeEventListener("open", onOpen);
81
+ reject(event);
82
+ };
83
+ ws.addEventListener("open", onOpen, { once: true });
84
+ ws.addEventListener("error", onError, { once: true });
85
+ ws.addEventListener("message", (ev) => void this.onMessage(ev.data));
86
+ ws.addEventListener("close", (ev) => {
87
+ this.tearDown();
88
+ this.emit("close", { code: ev.code, reason: ev.reason });
89
+ });
90
+ });
91
+ }
92
+ send(msg) {
93
+ if (this.ws && this.ws.readyState === this.deps.WebSocket.OPEN) {
94
+ this.ws.send(JSON.stringify(msg));
95
+ }
96
+ }
97
+ tearDown() {
98
+ for (const r of this.remotes.values())
99
+ r.pc.close();
100
+ this.remotes.clear();
101
+ }
102
+ /* ---- Protocol ----------------------------------------------------- */
103
+ async onMessage(raw) {
104
+ let msg;
105
+ try {
106
+ msg = JSON.parse(String(raw));
107
+ }
108
+ catch {
109
+ return;
110
+ }
111
+ switch (msg.type) {
112
+ case "joined": {
113
+ this.selfId = msg.peerId;
114
+ // TURN ONLY (no STUN). This viewer runs in an Electron <webview> whose
115
+ // P2P stack has NO mDNS resolver, so host `.local` candidates never
116
+ // resolve (-105), and it can't resolve STUN *hostnames* either
117
+ // (stun.l.google.com / stun.cloudflare.com → -105). The only viable
118
+ // path is the relay, and the server now advertises TURN by IP
119
+ // (turn:51.91.126.43:3478), which needs no DNS. Dropping the unusable
120
+ // STUN hostnames removes the -105 noise and the dead srflx gathering.
121
+ this.iceServers = msg.turn.urls.map((url) => ({
122
+ urls: url,
123
+ username: msg.turn.username,
124
+ credential: msg.turn.credential,
125
+ }));
126
+ this.emit("joined", { peerId: msg.peerId, peers: msg.peers });
127
+ for (const peer of msg.peers)
128
+ this.ensureRemote(peer);
129
+ break;
130
+ }
131
+ case "peer-joined": {
132
+ this.emit("peer-joined", msg.peer);
133
+ this.ensureRemote(msg.peer);
134
+ break;
135
+ }
136
+ case "peer-left": {
137
+ const remote = this.remotes.get(msg.peerId);
138
+ if (remote) {
139
+ // The pc owns the tracks — closing it ends them. The registry/consumer
140
+ // are notified via the peer-left event (label-keyed).
141
+ remote.pc.close();
142
+ this.remotes.delete(msg.peerId);
143
+ this.emit("peer-left", { peerId: msg.peerId, peerName: remote.info.name });
144
+ }
145
+ break;
146
+ }
147
+ case "signal": {
148
+ await this.handleSignal(msg.from, msg.payload);
149
+ break;
150
+ }
151
+ case "error": {
152
+ this.emit("error", { code: msg.code, message: msg.message });
153
+ break;
154
+ }
155
+ }
156
+ }
157
+ async handleSignal(from, payload) {
158
+ let remote = this.remotes.get(from);
159
+ if (!remote) {
160
+ remote = this.ensureRemote({ id: from, name: from.slice(0, 8), role: "publisher" });
161
+ }
162
+ const { pc } = remote;
163
+ if (payload.kind === "sdp") {
164
+ const desc = payload.description;
165
+ const offerCollision = desc.type === "offer" && (remote.makingOffer || pc.signalingState !== "stable");
166
+ remote.ignoreOffer = !this.isPolite(from) && offerCollision;
167
+ if (remote.ignoreOffer)
168
+ return;
169
+ await pc.setRemoteDescription(desc);
170
+ for (const c of remote.pendingCandidates) {
171
+ try {
172
+ await pc.addIceCandidate(c);
173
+ }
174
+ catch {
175
+ /* ignore late/stale candidates */
176
+ }
177
+ }
178
+ remote.pendingCandidates = [];
179
+ if (desc.type === "offer") {
180
+ await pc.setLocalDescription();
181
+ if (pc.localDescription) {
182
+ this.sendSignal(from, {
183
+ kind: "sdp",
184
+ description: {
185
+ type: pc.localDescription.type,
186
+ sdp: pc.localDescription.sdp,
187
+ },
188
+ });
189
+ }
190
+ }
191
+ return;
192
+ }
193
+ if (payload.kind === "ice") {
194
+ const init = payload.candidate;
195
+ if (pc.remoteDescription) {
196
+ try {
197
+ await pc.addIceCandidate(init);
198
+ }
199
+ catch (err) {
200
+ if (!remote.ignoreOffer)
201
+ throw err;
202
+ }
203
+ }
204
+ else {
205
+ remote.pendingCandidates.push(init);
206
+ }
207
+ return;
208
+ }
209
+ // control payloads ignored — the viewer does not act on screen/mute hints.
210
+ }
211
+ /* ---- Peer setup --------------------------------------------------- */
212
+ ensureRemote(peer) {
213
+ const existing = this.remotes.get(peer.id);
214
+ if (existing)
215
+ return existing;
216
+ // relay-only: in the Electron <webview> host (.local) and srflx candidates
217
+ // are unusable (no mDNS resolver, STUN hostnames unresolvable), so force the
218
+ // ICE agent to gather/use ONLY relay candidates via the by-IP TURN server.
219
+ // The publisher (browser) gathers all transports incl. relay, so the
220
+ // relay↔relay pair connects. Avoids ICE stalling on dead host/srflx checks.
221
+ const pc = new this.deps.RTCPeerConnection({
222
+ iceServers: this.iceServers,
223
+ iceTransportPolicy: "relay",
224
+ });
225
+ const stream = new this.deps.MediaStream();
226
+ // VIEWER : both transceivers are recvonly and carry NO local track. Order
227
+ // (audio first, video second) must match the publisher's so the m-lines
228
+ // line up — the same invariant as the reference.
229
+ const audioTx = pc.addTransceiver("audio", { direction: "recvonly" });
230
+ const videoTx = pc.addTransceiver("video", { direction: "recvonly" });
231
+ // BUNDLE codec-collision fix (ADR 006 #3). A recvonly viewer offering the
232
+ // FULL Chromium codec catalogue (VP8/VP9×profiles/H264×profiles/AV1/H265 +
233
+ // rtx/red/ulpfec/flexfec, audio opus + telephone-event) maximises payload-
234
+ // type pressure. Under BUNDLE all m-lines share ONE PT namespace, so when
235
+ // the publisher (answerer, with pre-allocated sendrecv transceivers — see
236
+ // `meet-client.ts`) reconciles that dense offer, PTs collide ACROSS m-lines
237
+ // (the observed `126` audio telephone-event vs `39` video H264). A pure
238
+ // viewer needs ONE coherent codec per kind, not the whole catalogue : we
239
+ // pin a deduplicated, minimal preference set so the offered PT space is
240
+ // small and collision-free by construction. This is NOT SDP munging — it is
241
+ // the spec'd `setCodecPreferences` API ; Chromium still owns PT assignment.
242
+ pinViewerCodecs(audioTx, videoTx);
243
+ const state = {
244
+ info: peer,
245
+ pc,
246
+ stream,
247
+ makingOffer: false,
248
+ ignoreOffer: false,
249
+ pendingCandidates: [],
250
+ };
251
+ pc.addEventListener("negotiationneeded", () => {
252
+ void (async () => {
253
+ try {
254
+ state.makingOffer = true;
255
+ await pc.setLocalDescription();
256
+ if (pc.localDescription) {
257
+ this.sendSignal(peer.id, {
258
+ kind: "sdp",
259
+ description: {
260
+ type: pc.localDescription.type,
261
+ sdp: pc.localDescription.sdp,
262
+ },
263
+ });
264
+ }
265
+ }
266
+ catch {
267
+ /* transient — glare or mid-close */
268
+ }
269
+ finally {
270
+ state.makingOffer = false;
271
+ }
272
+ })();
273
+ });
274
+ pc.addEventListener("icecandidate", (ev) => {
275
+ const candidate = ev.candidate;
276
+ if (!candidate)
277
+ return;
278
+ this.sendSignal(peer.id, {
279
+ kind: "ice",
280
+ candidate: {
281
+ candidate: candidate.candidate,
282
+ sdpMid: candidate.sdpMid,
283
+ sdpMLineIndex: candidate.sdpMLineIndex,
284
+ usernameFragment: candidate.usernameFragment,
285
+ },
286
+ });
287
+ });
288
+ pc.addEventListener("track", (ev) => {
289
+ const track = ev.track;
290
+ // Aggregate every incoming track into the peer's single MediaStream so
291
+ // the consumer gets one coherent source for `<video srcObject>`.
292
+ if (!state.stream.getTracks().includes(track)) {
293
+ state.stream.addTrack(track);
294
+ }
295
+ track.addEventListener("ended", () => {
296
+ state.stream.removeTrack(track);
297
+ });
298
+ this.emit("remote-track", {
299
+ peerId: peer.id,
300
+ peerName: peer.name,
301
+ stream: state.stream,
302
+ });
303
+ });
304
+ pc.addEventListener("connectionstatechange", () => {
305
+ this.emit("connection-state", { peerId: peer.id, state: pc.connectionState });
306
+ if (pc.connectionState === "failed" || pc.connectionState === "closed") {
307
+ this.remotes.delete(peer.id);
308
+ this.emit("peer-left", { peerId: peer.id, peerName: peer.name });
309
+ }
310
+ });
311
+ this.remotes.set(peer.id, state);
312
+ return state;
313
+ }
314
+ /* ---- Helpers ------------------------------------------------------ */
315
+ isPolite(otherId) {
316
+ if (!this.selfId)
317
+ return false;
318
+ return this.selfId > otherId;
319
+ }
320
+ sendSignal(to, payload) {
321
+ this.send({ type: "signal", to, payload });
322
+ }
323
+ emit(type, event) {
324
+ const set = this.listeners.get(type);
325
+ if (!set)
326
+ return;
327
+ for (const listener of set)
328
+ listener(event);
329
+ }
330
+ }
331
+ /* ---- Codec preference (BUNDLE collision fix) ----------------------- */
332
+ /** Pin a minimal, deduplicated codec preference on a viewer's recvonly
333
+ * transceivers so the offered payload-type space stays small and BUNDLE-safe.
334
+ *
335
+ * AUDIO : opus (+ keep telephone-event for spec completeness, it is harmless).
336
+ * VIDEO : H264 + its rtx ONLY (drop VP9/AV1/H265 multi-profile clutter). H264
337
+ * is what the publisher (Prism cam / Pulsar NVENC) emits ; a viewer
338
+ * that only ever RECEIVES needs no broader set. rtx is kept so NACK
339
+ * retransmission still works.
340
+ *
341
+ * Feature-detected end-to-end : if `setCodecPreferences` or
342
+ * `RTCRtpReceiver.getCapabilities` is unavailable (older engines, jsdom/test
343
+ * fakes), this is a silent no-op and the transceiver keeps Chromium's default
344
+ * full list — behaviour is never WORSE than before the fix. */
345
+ function pinViewerCodecs(audioTx, videoTx) {
346
+ const getCaps = globalThis.RTCRtpReceiver?.getCapabilities;
347
+ if (typeof getCaps !== "function")
348
+ return;
349
+ pinKind(videoTx, getCaps("video"), (mime) => {
350
+ const m = mime.toLowerCase();
351
+ // Keep H264 and the generic rtx (retransmission) codec only.
352
+ return m === "video/h264" || m === "video/rtx";
353
+ });
354
+ pinKind(audioTx, getCaps("audio"), (mime) => {
355
+ const m = mime.toLowerCase();
356
+ return m === "audio/opus" || m === "audio/telephone-event";
357
+ });
358
+ }
359
+ /** Apply `setCodecPreferences` with the subset of `caps` whose mimeType passes
360
+ * `keep`, preserving the platform's preferred order. Guarded : no transceiver,
361
+ * no `setCodecPreferences`, no caps, or an empty filtered set → no-op. */
362
+ function pinKind(tx, caps, keep) {
363
+ const setPrefs = tx
364
+ ?.setCodecPreferences;
365
+ if (typeof setPrefs !== "function" || !caps)
366
+ return;
367
+ const codecs = caps.codecs.filter((c) => keep(c.mimeType));
368
+ if (codecs.length === 0)
369
+ return; // never offer an empty m-line
370
+ try {
371
+ setPrefs.call(tx, codecs);
372
+ }
373
+ catch {
374
+ // Some engines reject a preference list (e.g. rtx without its apt primary in
375
+ // the same call) — fall back to the default full list rather than break the
376
+ // transceiver. The fix is best-effort hardening, never a hard dependency.
377
+ }
378
+ }
379
+ //# sourceMappingURL=meet-viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meet-viewer.js","sourceRoot":"","sources":["../../src/webrtc/meet-viewer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export type PeerStreamListener = (stream: MediaStream | null) => void;
2
+ export interface PeerStreamRegistry {
3
+ /** #4 contract — the current stream for a label, or `null` if the peer is not
4
+ * connected (yet / any more). Synchronous, side-effect free. */
5
+ resolve(peerLabel: string): MediaStream | null;
6
+ /** Push channel for the LIVE `media` primitive : invoked immediately with the
7
+ * current value, then on every change for `peerLabel`. Returns an
8
+ * unsubscribe. */
9
+ subscribe(peerLabel: string, listener: PeerStreamListener): () => void;
10
+ /** Viewer-side : publish / replace a peer's stream (peer connected). */
11
+ set(peerLabel: string, stream: MediaStream): void;
12
+ /** Viewer-side : drop a peer's stream (peer left / connection failed). The
13
+ * registry forgets the reference ; it does NOT stop the tracks (the viewer's
14
+ * pc.close() does, as the track owner). */
15
+ remove(peerLabel: string): void;
16
+ /** Viewer-side : forget every entry (room teardown). Reference-only, no track
17
+ * stops. */
18
+ clear(): void;
19
+ }
20
+ export declare function createPeerStreamRegistry(): PeerStreamRegistry;
21
+ //# sourceMappingURL=peer-stream-registry.d.ts.map
@@ -0,0 +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,CAgD7D"}
@@ -0,0 +1,77 @@
1
+ // Peer-stream registry — the bridge between the WebRTC viewer (#3) and the
2
+ // `media` primitive's LIVE mode (#4).
3
+ //
4
+ // The viewer feeds this registry one entry per CONNECTED peer, keyed by the
5
+ // peer's STABLE `peer_label` (the ZabCam contract label #6, announced on the
6
+ // mesh as the peer's join `name` — see `PeerInfo.name` / `RemoteTrackEvent`).
7
+ // The `media` primitive resolves `peerLabel → MediaStream` through it.
8
+ //
9
+ // Reactivity : a peer's stream becomes available ASYNCHRONOUSLY (after the room
10
+ // join + SDP/ICE), so a `media` node that mounted before the peer connected
11
+ // must be notified when its stream arrives. The registry exposes both a
12
+ // one-shot `resolvePeerStream` (the #4 contract, synchronous) AND a
13
+ // `subscribePeerStream(label, cb)` push channel the LIVE primitive uses to
14
+ // re-render on connect / disconnect. The store is a plain Map guarded by a
15
+ // listener set — no signals dependency, so it adds nothing to the render path.
16
+ //
17
+ // OWNERSHIP (the #3↔#4 lifecycle decision) : the registry NEVER creates or
18
+ // stops a track. It only HOLDS a reference to a `MediaStream` owned by the
19
+ // viewer's `RTCPeerConnection`. The viewer is the sole authority on the track
20
+ // lifecycle (created on `track`, removed on `ended` / `peer-left` / teardown).
21
+ // A consuming `<video>` unmounting clears its own `srcObject` and nothing else
22
+ // — it can never tear a peer down for the on-air composite.
23
+ export function createPeerStreamRegistry() {
24
+ const streams = new Map();
25
+ const listeners = new Map();
26
+ function notify(peerLabel) {
27
+ const set = listeners.get(peerLabel);
28
+ if (set === undefined)
29
+ return;
30
+ const value = streams.get(peerLabel) ?? null;
31
+ for (const listener of set)
32
+ listener(value);
33
+ }
34
+ return {
35
+ resolve(peerLabel) {
36
+ return streams.get(peerLabel) ?? null;
37
+ },
38
+ subscribe(peerLabel, listener) {
39
+ let set = listeners.get(peerLabel);
40
+ if (set === undefined) {
41
+ set = new Set();
42
+ listeners.set(peerLabel, set);
43
+ }
44
+ set.add(listener);
45
+ // Emit the current value synchronously so a late subscriber sees an
46
+ // already-connected peer without waiting for the next change.
47
+ listener(streams.get(peerLabel) ?? null);
48
+ return () => {
49
+ const s = listeners.get(peerLabel);
50
+ if (s === undefined)
51
+ return;
52
+ s.delete(listener);
53
+ if (s.size === 0)
54
+ listeners.delete(peerLabel);
55
+ };
56
+ },
57
+ set(peerLabel, stream) {
58
+ if (streams.get(peerLabel) === stream)
59
+ return; // idempotent re-emit guard
60
+ streams.set(peerLabel, stream);
61
+ notify(peerLabel);
62
+ },
63
+ remove(peerLabel) {
64
+ if (!streams.has(peerLabel))
65
+ return;
66
+ streams.delete(peerLabel);
67
+ notify(peerLabel);
68
+ },
69
+ clear() {
70
+ const labels = [...streams.keys()];
71
+ streams.clear();
72
+ for (const label of labels)
73
+ notify(label);
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=peer-stream-registry.js.map
@@ -0,0 +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;AAuB5D,MAAM,UAAU,wBAAwB;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAmC,CAAC;IAE7D,SAAS,MAAM,CAAC,SAAiB;QAC/B,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;QAC7C,KAAK,MAAM,QAAQ,IAAI,GAAG;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO,CAAC,SAAS;YACf,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;QACxC,CAAC;QACD,SAAS,CAAC,SAAS,EAAE,QAAQ;YAC3B,IAAI,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;gBAChB,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAChC,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAClB,oEAAoE;YACpE,8DAA8D;YAC9D,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,CAAC;YACzC,OAAO,GAAG,EAAE;gBACV,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACnC,IAAI,CAAC,KAAK,SAAS;oBAAE,OAAO;gBAC5B,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACnB,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;oBAAE,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChD,CAAC,CAAC;QACJ,CAAC;QACD,GAAG,CAAC,SAAS,EAAE,MAAM;YACnB,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,MAAM;gBAAE,OAAO,CAAC,2BAA2B;YAC1E,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAC/B,MAAM,CAAC,SAAS,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,CAAC,SAAS;YACd,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;gBAAE,OAAO;YACpC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1B,MAAM,CAAC,SAAS,CAAC,CAAC;QACpB,CAAC;QACD,KAAK;YACH,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACnC,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,KAAK,MAAM,KAAK,IAAI,MAAM;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumencast/runtime",
3
- "version": "0.9.0",
3
+ "version": "0.10.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.9.0"
39
+ "@lumencast/protocol": "0.10.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.9.0",
54
- "@lumencast/dev-server": "0.9.0"
53
+ "@lumencast/dev-server": "0.10.0",
54
+ "@lumencast/server": "0.10.0"
55
55
  },
56
56
  "scripts": {
57
57
  "dev": "vite",
package/src/app.tsx CHANGED
@@ -19,6 +19,7 @@ import type { RenderBundle } from "./render/bundle.js";
19
19
  import type { ConnectionStatus } from "./transport/ws.js";
20
20
  import { LumencastRuntimeProvider } from "./overlay/runtime-context.js";
21
21
  import type { ResolveCaptureDevice } from "./render/primitives/capture.js";
22
+ import type { ResolvePeerStream, SubscribePeerStream } from "./render/primitives/media.js";
22
23
  import type { LumencastMode } from "./types.js";
23
24
 
24
25
  const LazyBroadcastMode = lazy(() =>
@@ -38,6 +39,10 @@ export interface LumencastAppProps {
38
39
  sendInput: (patches: Patch[]) => void;
39
40
  /** ADR 004 §A1.3 — host resolver for `x-zab.capture` ACQUIRE mode. */
40
41
  resolveCaptureDevice?: ResolveCaptureDevice;
42
+ /** ADR 006 #4 — host resolver for the `media` primitive's LIVE mode. */
43
+ resolvePeerStream?: ResolvePeerStream;
44
+ /** ADR 006 #3 — reactive peer-stream channel (preferred over the resolver). */
45
+ subscribePeerStream?: SubscribePeerStream;
41
46
  }
42
47
 
43
48
  export function LumencastApp({
@@ -48,6 +53,8 @@ export function LumencastApp({
48
53
  crossfadeKeySignal,
49
54
  sendInput,
50
55
  resolveCaptureDevice,
56
+ resolvePeerStream,
57
+ subscribePeerStream,
51
58
  }: LumencastAppProps) {
52
59
  useSignals();
53
60
 
@@ -77,6 +84,8 @@ export function LumencastApp({
77
84
  status,
78
85
  sendInput,
79
86
  ...(resolveCaptureDevice !== undefined ? { resolveCaptureDevice } : {}),
87
+ ...(resolvePeerStream !== undefined ? { resolvePeerStream } : {}),
88
+ ...(subscribePeerStream !== undefined ? { subscribePeerStream } : {}),
80
89
  }}
81
90
  >
82
91
  <Suspense fallback={null}>
package/src/index.ts CHANGED
@@ -31,6 +31,41 @@ export { PRIMITIVE_PROP_ALLOWLIST } from "./render/prop-allowlist.js";
31
31
  // app (Prism/Solar) types its injected resolver against the runtime's contract.
32
32
  export type { ResolveCaptureDevice } from "./render/primitives/capture.js";
33
33
 
34
+ // ADR 006 #4 — host resolver mapping a `peerLabel` to a peer's live MediaStream
35
+ // (the `media` primitive's LIVE mode), supplied via `MountOptions.resolvePeerStream`.
36
+ // Exported so the WebRTC viewer (#3) types its injected resolver against the
37
+ // runtime's contract.
38
+ export type { ResolvePeerStream, SubscribePeerStream } from "./render/primitives/media.js";
39
+
40
+ // ADR 006 #3 — WebRTC viewer (mesh, viewer role) + peer-stream registry. The
41
+ // #3↔#4 bridge : a viewer joins Meet room(s), receives N peers and returns the
42
+ // `resolvePeerStream` / `subscribePeerStream` the `media`/`meet.peer` LIVE render
43
+ // consumes via `MountOptions`. The viewer never publishes (no getUserMedia) ; it
44
+ // owns the peer connections + the track lifecycle.
45
+ //
46
+ // FINAL MODEL : `createMultiRoomPeerViewer({ rooms: [...] })` joins EVERY room
47
+ // and aggregates all peers into ONE registry (first-connected-wins on a label
48
+ // collision). `createPeerViewerFromInjection` normalises the host-injected
49
+ // `window.__ZAB_PEER_VIEWER__` (multi-room `{rooms}` OR legacy single-room).
50
+ export {
51
+ createPeerViewer,
52
+ createMultiRoomPeerViewer,
53
+ createPeerViewerFromInjection,
54
+ MeetViewer,
55
+ createPeerStreamRegistry,
56
+ type PeerViewer,
57
+ type MultiRoomPeerViewer,
58
+ type MultiRoomPeerViewerOptions,
59
+ type RoomOptions,
60
+ type PeerViewerInjection,
61
+ type MeetViewerOptions,
62
+ type MeetViewerDeps,
63
+ type PeerInfo,
64
+ type RemoteTrackEvent,
65
+ type PeerStreamRegistry,
66
+ type PeerStreamListener,
67
+ } from "./webrtc/index.js";
68
+
34
69
  // Bundle types are useful for hosts that want to typecheck pre-compiled scenes.
35
70
  export type {
36
71
  RenderBundle,
package/src/mount.ts CHANGED
@@ -123,6 +123,17 @@ export function mount(options: MountOptions): LumencastHandle {
123
123
  ...(options.resolveCaptureDevice !== undefined
124
124
  ? { resolveCaptureDevice: options.resolveCaptureDevice }
125
125
  : {}),
126
+ // ADR 006 #4 — thread the host peer-stream resolver (supplied by the
127
+ // WebRTC viewer #3) so the `media` primitive's LIVE mode can render a
128
+ // peer's MediaStream in `srcObject`.
129
+ ...(options.resolvePeerStream !== undefined
130
+ ? { resolvePeerStream: options.resolvePeerStream }
131
+ : {}),
132
+ // ADR 006 #3 — reactive variant : the LIVE `media` node re-renders when a
133
+ // peer connects/leaves mid-show. `createPeerViewer()` supplies it.
134
+ ...(options.subscribePeerStream !== undefined
135
+ ? { subscribePeerStream: options.subscribePeerStream }
136
+ : {}),
126
137
  }),
127
138
  );
128
139
 
@@ -5,6 +5,7 @@ import type { RenderBundle } from "../render/bundle";
5
5
  import type { ConnectionStatus } from "../transport/ws";
6
6
  import type { LumencastMode } from "../types";
7
7
  import type { ResolveCaptureDevice } from "../render/primitives/capture";
8
+ import type { ResolvePeerStream, SubscribePeerStream } from "../render/primitives/media";
8
9
 
9
10
  export interface LumencastRuntime {
10
11
  mode: LumencastMode;
@@ -17,6 +18,15 @@ export interface LumencastRuntime {
17
18
  * physical `deviceId` for the `x-zab.capture` primitive's ACQUIRE mode.
18
19
  * Injected from `MountOptions`, NOT the bundle. Absent → default device. */
19
20
  resolveCaptureDevice?: ResolveCaptureDevice;
21
+ /** ADR 006 #4 — host-provided resolver mapping a LOGICAL `peerLabel` to the
22
+ * live `MediaStream` of a `meet.peer`, for the `media` primitive's LIVE mode.
23
+ * Injected from `MountOptions` (supplied by the WebRTC viewer #3), NOT the
24
+ * bundle. Absent → the live `media` node is a stream-less box. */
25
+ resolvePeerStream?: ResolvePeerStream;
26
+ /** ADR 006 #3 — reactive variant : the viewer pushes a peer's stream on
27
+ * connect and `null` on leave, so a LIVE `media` node re-renders when its
28
+ * peer joins mid-show. Preferred over `resolvePeerStream` when present. */
29
+ subscribePeerStream?: SubscribePeerStream;
20
30
  }
21
31
 
22
32
  const Ctx = createContext<LumencastRuntime | null>(null);