@fishjam-cloud/composition 0.29.0-rc.1 → 0.29.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/dist/chunk-XAZDKX25.mjs +301 -0
- package/dist/core.d.mts +84 -0
- package/dist/core.d.ts +84 -0
- package/dist/core.js +327 -0
- package/dist/core.mjs +6 -0
- package/dist/index.d.mts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +336 -2
- package/dist/index.mjs +34 -2
- package/dist/types-LgEdO1AS.d.mts +45 -0
- package/dist/types-LgEdO1AS.d.ts +45 -0
- package/package.json +23 -6
package/dist/core.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/core.ts
|
|
21
|
+
var core_exports = {};
|
|
22
|
+
__export(core_exports, {
|
|
23
|
+
compositionStore: () => compositionStore
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(core_exports);
|
|
26
|
+
|
|
27
|
+
// src/store.ts
|
|
28
|
+
var EMPTY_SNAPSHOT = { peers: [], vad: {} };
|
|
29
|
+
var assertNever = (event) => {
|
|
30
|
+
throw new Error(`Unhandled composition event: ${JSON.stringify(event)}`);
|
|
31
|
+
};
|
|
32
|
+
var normalizeMetadata = (raw) => {
|
|
33
|
+
if (raw == null) return {};
|
|
34
|
+
if (typeof raw === "string") {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return raw;
|
|
43
|
+
};
|
|
44
|
+
var splitMetadata = (raw) => {
|
|
45
|
+
const obj = normalizeMetadata(raw);
|
|
46
|
+
return { peer: obj.peer, server: obj.server };
|
|
47
|
+
};
|
|
48
|
+
var roleOf = (stream) => {
|
|
49
|
+
const type = stream.video?.metadata.type ?? stream.audio?.metadata.type;
|
|
50
|
+
if (type === "camera" || type === "microphone") return "camera";
|
|
51
|
+
if (type === "screenShareVideo" || type === "screenShareAudio") return "screenShare";
|
|
52
|
+
return "custom";
|
|
53
|
+
};
|
|
54
|
+
var CompositionStore = class {
|
|
55
|
+
peers = /* @__PURE__ */ new Map();
|
|
56
|
+
roomId;
|
|
57
|
+
vad = /* @__PURE__ */ new Map();
|
|
58
|
+
listeners = /* @__PURE__ */ new Set();
|
|
59
|
+
cachedSnapshot = EMPTY_SNAPSHOT;
|
|
60
|
+
cachedPeers = EMPTY_SNAPSHOT.peers;
|
|
61
|
+
subscribe = (cb) => {
|
|
62
|
+
this.listeners.add(cb);
|
|
63
|
+
return () => this.listeners.delete(cb);
|
|
64
|
+
};
|
|
65
|
+
getSnapshot = () => {
|
|
66
|
+
if (this.cachedSnapshot === null) {
|
|
67
|
+
this.cachedSnapshot = { peers: this.cachedPeers, roomId: this.roomId, vad: this.vadRecord() };
|
|
68
|
+
}
|
|
69
|
+
return this.cachedSnapshot;
|
|
70
|
+
};
|
|
71
|
+
getPeers = () => this.cachedPeers;
|
|
72
|
+
getPeer = (peerId) => this.getPeers().find((peer) => peer.id === peerId);
|
|
73
|
+
getRoomId = () => this.roomId;
|
|
74
|
+
getVadStatus = (peerId) => {
|
|
75
|
+
const peer = this.peers.get(peerId);
|
|
76
|
+
if (!peer) return "silence";
|
|
77
|
+
for (const track of peer.tracks.values())
|
|
78
|
+
if (track.type === "audio" && track.inputId && this.vad.get(track.inputId) === "speech") return "speech";
|
|
79
|
+
return "silence";
|
|
80
|
+
};
|
|
81
|
+
reset() {
|
|
82
|
+
this.peers.clear();
|
|
83
|
+
this.vad.clear();
|
|
84
|
+
this.roomId = void 0;
|
|
85
|
+
this.cachedSnapshot = null;
|
|
86
|
+
this.cachedPeers = [];
|
|
87
|
+
this.commit();
|
|
88
|
+
}
|
|
89
|
+
seedFromRoom(room) {
|
|
90
|
+
this.peers.clear();
|
|
91
|
+
this.vad.clear();
|
|
92
|
+
this.roomId = room.id;
|
|
93
|
+
for (const peer of room.peers) {
|
|
94
|
+
const internal = {
|
|
95
|
+
id: peer.id,
|
|
96
|
+
metadata: splitMetadata(peer.metadata),
|
|
97
|
+
tracks: /* @__PURE__ */ new Map()
|
|
98
|
+
};
|
|
99
|
+
for (const track of peer.tracks ?? []) {
|
|
100
|
+
if (!track.id) continue;
|
|
101
|
+
internal.tracks.set(track.id, { id: track.id, metadata: normalizeMetadata(track.metadata), type: track.type });
|
|
102
|
+
}
|
|
103
|
+
this.peers.set(peer.id, internal);
|
|
104
|
+
}
|
|
105
|
+
this.rebuildPeers();
|
|
106
|
+
this.commit();
|
|
107
|
+
}
|
|
108
|
+
applyNotification(event) {
|
|
109
|
+
if (event.data.roomId !== this.roomId) return;
|
|
110
|
+
let changed;
|
|
111
|
+
switch (event.type) {
|
|
112
|
+
case "peerConnected":
|
|
113
|
+
changed = this.onPeerConnected(event.data);
|
|
114
|
+
break;
|
|
115
|
+
case "peerDisconnected":
|
|
116
|
+
changed = this.onPeerDisconnected(event.data);
|
|
117
|
+
break;
|
|
118
|
+
case "peerMetadataUpdated":
|
|
119
|
+
changed = this.onPeerMetadataUpdated(event.data);
|
|
120
|
+
break;
|
|
121
|
+
case "trackAdded":
|
|
122
|
+
changed = this.onTrackAdded(event.data);
|
|
123
|
+
break;
|
|
124
|
+
case "trackRemoved":
|
|
125
|
+
changed = this.onTrackRemoved(event.data);
|
|
126
|
+
break;
|
|
127
|
+
case "trackMetadataUpdated":
|
|
128
|
+
changed = this.onTrackMetadataUpdated(event.data);
|
|
129
|
+
break;
|
|
130
|
+
case "trackForwarding":
|
|
131
|
+
changed = this.onTrackForwarding(event.data);
|
|
132
|
+
break;
|
|
133
|
+
case "trackForwardingRemoved":
|
|
134
|
+
changed = this.onTrackForwardingRemoved(event.data);
|
|
135
|
+
break;
|
|
136
|
+
case "vadNotification":
|
|
137
|
+
changed = this.onVadNotification(event.data);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
return assertNever(event);
|
|
141
|
+
}
|
|
142
|
+
if (changed) this.commit();
|
|
143
|
+
}
|
|
144
|
+
ensurePeer(peerId) {
|
|
145
|
+
let peer = this.peers.get(peerId);
|
|
146
|
+
if (!peer) {
|
|
147
|
+
peer = { id: peerId, metadata: { peer: void 0, server: void 0 }, tracks: /* @__PURE__ */ new Map() };
|
|
148
|
+
this.peers.set(peerId, peer);
|
|
149
|
+
}
|
|
150
|
+
return peer;
|
|
151
|
+
}
|
|
152
|
+
onPeerConnected(data) {
|
|
153
|
+
if (this.peers.has(data.peerId)) return false;
|
|
154
|
+
this.ensurePeer(data.peerId);
|
|
155
|
+
this.replacePeer(data.peerId);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
onPeerDisconnected(data) {
|
|
159
|
+
const peer = this.peers.get(data.peerId);
|
|
160
|
+
if (!peer) return false;
|
|
161
|
+
for (const { inputId } of peer.tracks.values()) if (inputId) this.vad.delete(inputId);
|
|
162
|
+
this.peers.delete(data.peerId);
|
|
163
|
+
this.replacePeer(data.peerId);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
onPeerMetadataUpdated(data) {
|
|
167
|
+
this.ensurePeer(data.peerId).metadata = splitMetadata(data.metadata);
|
|
168
|
+
this.replacePeer(data.peerId);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
onTrackAdded(data) {
|
|
172
|
+
if (!data.peerId || !data.track) return false;
|
|
173
|
+
const peer = this.ensurePeer(data.peerId);
|
|
174
|
+
peer.tracks.set(data.track.id, {
|
|
175
|
+
id: data.track.id,
|
|
176
|
+
metadata: normalizeMetadata(data.track.metadata),
|
|
177
|
+
type: data.track.type
|
|
178
|
+
});
|
|
179
|
+
this.replacePeer(data.peerId);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
onTrackMetadataUpdated(data) {
|
|
183
|
+
if (!data.peerId || !data.track) return false;
|
|
184
|
+
const peer = this.peers.get(data.peerId);
|
|
185
|
+
if (!peer) return false;
|
|
186
|
+
const metadata = normalizeMetadata(data.track.metadata);
|
|
187
|
+
const inputId = peer.tracks.get(data.track.id)?.inputId;
|
|
188
|
+
peer.tracks.set(data.track.id, { id: data.track.id, metadata, type: data.track.type, inputId });
|
|
189
|
+
this.replacePeer(data.peerId);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
onTrackRemoved(data) {
|
|
193
|
+
if (!data.peerId || !data.track) return false;
|
|
194
|
+
const peer = this.peers.get(data.peerId);
|
|
195
|
+
if (!peer) return false;
|
|
196
|
+
const track = peer.tracks.get(data.track.id);
|
|
197
|
+
if (!track) return false;
|
|
198
|
+
peer.tracks.delete(data.track.id);
|
|
199
|
+
if (track.type === "audio" && track.inputId) this.vad.delete(track.inputId);
|
|
200
|
+
this.replacePeer(data.peerId);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
onTrackForwarding(data) {
|
|
204
|
+
const peer = this.ensurePeer(data.peerId);
|
|
205
|
+
if (data.videoTrack) {
|
|
206
|
+
peer.tracks.set(data.videoTrack.id, {
|
|
207
|
+
id: data.videoTrack.id,
|
|
208
|
+
metadata: normalizeMetadata(data.videoTrack.metadata),
|
|
209
|
+
inputId: data.inputId,
|
|
210
|
+
type: data.videoTrack.type
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (data.audioTrack) {
|
|
214
|
+
peer.tracks.set(data.audioTrack.id, {
|
|
215
|
+
id: data.audioTrack.id,
|
|
216
|
+
metadata: normalizeMetadata(data.audioTrack.metadata),
|
|
217
|
+
inputId: data.inputId,
|
|
218
|
+
type: data.audioTrack.type
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
this.replacePeer(data.peerId);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
onTrackForwardingRemoved(data) {
|
|
225
|
+
let changed = this.vad.delete(data.inputId);
|
|
226
|
+
const peer = this.peers.get(data.peerId);
|
|
227
|
+
if (!peer) {
|
|
228
|
+
if (changed) this.cachedSnapshot = null;
|
|
229
|
+
return changed;
|
|
230
|
+
}
|
|
231
|
+
for (const track of peer.tracks.values()) {
|
|
232
|
+
if (track.inputId === data.inputId) {
|
|
233
|
+
track.inputId = void 0;
|
|
234
|
+
changed = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (changed) this.replacePeer(data.peerId);
|
|
238
|
+
return changed;
|
|
239
|
+
}
|
|
240
|
+
onVadNotification(data) {
|
|
241
|
+
const peer = this.peers.get(data.peerId);
|
|
242
|
+
if (!peer) return false;
|
|
243
|
+
const track = peer.tracks.get(data.trackId);
|
|
244
|
+
if (!track || !track.inputId) return false;
|
|
245
|
+
if (this.vad.get(track.inputId) === data.status) return false;
|
|
246
|
+
this.vad.set(track.inputId, data.status);
|
|
247
|
+
this.cachedSnapshot = null;
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
commit() {
|
|
251
|
+
for (const cb of this.listeners) cb();
|
|
252
|
+
}
|
|
253
|
+
replacePeer(peerId) {
|
|
254
|
+
const internal = this.peers.get(peerId);
|
|
255
|
+
const next = this.cachedPeers.slice();
|
|
256
|
+
const idx = next.findIndex((peer) => peer.id === peerId);
|
|
257
|
+
if (!internal) {
|
|
258
|
+
if (idx >= 0) next.splice(idx, 1);
|
|
259
|
+
} else if (idx >= 0) {
|
|
260
|
+
next[idx] = this.derivePeer(internal);
|
|
261
|
+
} else {
|
|
262
|
+
next.push(this.derivePeer(internal));
|
|
263
|
+
}
|
|
264
|
+
this.cachedPeers = next;
|
|
265
|
+
this.cachedSnapshot = null;
|
|
266
|
+
}
|
|
267
|
+
rebuildPeers() {
|
|
268
|
+
this.cachedPeers = Array.from(this.peers.values(), (peer) => this.derivePeer(peer));
|
|
269
|
+
this.cachedSnapshot = null;
|
|
270
|
+
}
|
|
271
|
+
vadRecord() {
|
|
272
|
+
const vad = {};
|
|
273
|
+
for (const [inputId, status] of this.vad) vad[inputId] = status;
|
|
274
|
+
return vad;
|
|
275
|
+
}
|
|
276
|
+
derivePeer(peer) {
|
|
277
|
+
const streams = /* @__PURE__ */ new Map();
|
|
278
|
+
let cameraStream;
|
|
279
|
+
let screenShareStream;
|
|
280
|
+
const customStreams = [];
|
|
281
|
+
for (const track of peer.tracks.values()) {
|
|
282
|
+
if (!track.inputId) continue;
|
|
283
|
+
const stream = streams.get(track.inputId) || { inputId: track.inputId };
|
|
284
|
+
if (track.type === "audio")
|
|
285
|
+
stream.audio = {
|
|
286
|
+
id: track.id,
|
|
287
|
+
paused: Boolean(track.metadata.paused),
|
|
288
|
+
metadata: track.metadata,
|
|
289
|
+
type: "audio"
|
|
290
|
+
};
|
|
291
|
+
else
|
|
292
|
+
stream.video = {
|
|
293
|
+
id: track.id,
|
|
294
|
+
paused: Boolean(track.metadata.paused),
|
|
295
|
+
metadata: track.metadata,
|
|
296
|
+
type: "video"
|
|
297
|
+
};
|
|
298
|
+
streams.set(track.inputId, stream);
|
|
299
|
+
}
|
|
300
|
+
for (const stream of streams.values()) {
|
|
301
|
+
const role = roleOf(stream);
|
|
302
|
+
switch (role) {
|
|
303
|
+
case "camera":
|
|
304
|
+
cameraStream = stream;
|
|
305
|
+
break;
|
|
306
|
+
case "screenShare":
|
|
307
|
+
screenShareStream = stream;
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
customStreams.push(stream);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
id: peer.id,
|
|
315
|
+
metadata: peer.metadata,
|
|
316
|
+
streams: Array.from(streams.values()),
|
|
317
|
+
cameraStream,
|
|
318
|
+
screenShareStream,
|
|
319
|
+
customStreams
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
var compositionStore = new CompositionStore();
|
|
324
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
325
|
+
0 && (module.exports = {
|
|
326
|
+
compositionStore
|
|
327
|
+
});
|
package/dist/core.mjs
ADDED
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
+
import { P as PeerWithStreams } from './types-LgEdO1AS.mjs';
|
|
2
|
+
export { A as AudioTrackState, S as Stream, T as TrackState, V as VideoTrackState } from './types-LgEdO1AS.mjs';
|
|
3
|
+
import { VadStatus } from '@fishjam-cloud/js-server-sdk';
|
|
4
|
+
export { VadStatus } from '@fishjam-cloud/js-server-sdk';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* All peers in the linked room, projected into composition streams. Flat list —
|
|
8
|
+
* the worker is not a peer, so there is no local/remote split.
|
|
9
|
+
*/
|
|
10
|
+
declare function usePeers<PeerMetadata = unknown, ServerMetadata = unknown>(): PeerWithStreams<PeerMetadata, ServerMetadata>[];
|
|
11
|
+
/**
|
|
12
|
+
* The peer with the given `peerId`, or `undefined`.
|
|
13
|
+
*/
|
|
14
|
+
declare function usePeer<PeerMetadata = unknown, ServerMetadata = unknown>(peerId: string): PeerWithStreams<PeerMetadata, ServerMetadata> | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The linked room's id, or `undefined` when no room is linked.
|
|
17
|
+
*/
|
|
18
|
+
declare function useRoom(): {
|
|
19
|
+
id: string;
|
|
20
|
+
} | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Voice-activity status for the peer identified by `peerId`: `'speech'` when any
|
|
23
|
+
* of the peer's forwarded audio inputs is speaking, otherwise `'silence'`.
|
|
24
|
+
*/
|
|
25
|
+
declare function useSpeakingState(peerId: string): VadStatus;
|
|
26
|
+
|
|
1
27
|
interface CompositionEventBus {
|
|
2
28
|
on<T = unknown>(eventName: string, callback: (data: T) => void): () => void;
|
|
3
29
|
}
|
|
30
|
+
declare global {
|
|
31
|
+
var eventBus: CompositionEventBus;
|
|
32
|
+
}
|
|
4
33
|
declare const eventBus: CompositionEventBus;
|
|
5
34
|
|
|
6
|
-
export { type CompositionEventBus, eventBus };
|
|
35
|
+
export { type CompositionEventBus, PeerWithStreams, eventBus, usePeer, usePeers, useRoom, useSpeakingState };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
+
import { P as PeerWithStreams } from './types-LgEdO1AS.js';
|
|
2
|
+
export { A as AudioTrackState, S as Stream, T as TrackState, V as VideoTrackState } from './types-LgEdO1AS.js';
|
|
3
|
+
import { VadStatus } from '@fishjam-cloud/js-server-sdk';
|
|
4
|
+
export { VadStatus } from '@fishjam-cloud/js-server-sdk';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* All peers in the linked room, projected into composition streams. Flat list —
|
|
8
|
+
* the worker is not a peer, so there is no local/remote split.
|
|
9
|
+
*/
|
|
10
|
+
declare function usePeers<PeerMetadata = unknown, ServerMetadata = unknown>(): PeerWithStreams<PeerMetadata, ServerMetadata>[];
|
|
11
|
+
/**
|
|
12
|
+
* The peer with the given `peerId`, or `undefined`.
|
|
13
|
+
*/
|
|
14
|
+
declare function usePeer<PeerMetadata = unknown, ServerMetadata = unknown>(peerId: string): PeerWithStreams<PeerMetadata, ServerMetadata> | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The linked room's id, or `undefined` when no room is linked.
|
|
17
|
+
*/
|
|
18
|
+
declare function useRoom(): {
|
|
19
|
+
id: string;
|
|
20
|
+
} | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Voice-activity status for the peer identified by `peerId`: `'speech'` when any
|
|
23
|
+
* of the peer's forwarded audio inputs is speaking, otherwise `'silence'`.
|
|
24
|
+
*/
|
|
25
|
+
declare function useSpeakingState(peerId: string): VadStatus;
|
|
26
|
+
|
|
1
27
|
interface CompositionEventBus {
|
|
2
28
|
on<T = unknown>(eventName: string, callback: (data: T) => void): () => void;
|
|
3
29
|
}
|
|
30
|
+
declare global {
|
|
31
|
+
var eventBus: CompositionEventBus;
|
|
32
|
+
}
|
|
4
33
|
declare const eventBus: CompositionEventBus;
|
|
5
34
|
|
|
6
|
-
export { type CompositionEventBus, eventBus };
|
|
35
|
+
export { type CompositionEventBus, PeerWithStreams, eventBus, usePeer, usePeers, useRoom, useSpeakingState };
|