@q9labsai/chalk-client 4.1.16 → 5.0.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 +99 -40
- package/dist/access/grant.d.ts +0 -10
- package/dist/access/grant.d.ts.map +1 -1
- package/dist/access/manager.d.ts.map +1 -1
- package/dist/{chunk-M3S7HNQF.js → chunk-2HJIJVE2.js} +601 -3628
- package/dist/{chunk-OKIL2ELF.js → chunk-AVXJ4HQU.js} +154 -50
- package/dist/{chunk-E4OK5K6N.js → chunk-CMVO7USN.js} +2 -8
- package/dist/{chunk-BD7FRQ67.js → chunk-IPAN4LUZ.js} +1089 -2457
- package/dist/{chunk-O7WTAXAY.js → chunk-O2NOZKJP.js} +170 -639
- package/dist/client.d.ts +2500 -5206
- package/dist/client.d.ts.map +1 -1
- package/dist/collaboration/types.d.ts +21 -1
- package/dist/collaboration/types.d.ts.map +1 -1
- package/dist/collaboration/wire.d.ts +8 -1
- package/dist/collaboration/wire.d.ts.map +1 -1
- package/dist/connection/dependencies.d.ts +8 -13
- package/dist/connection/dependencies.d.ts.map +1 -1
- package/dist/connection/diagnostics.d.ts +1 -1
- package/dist/connection/diagnostics.d.ts.map +1 -1
- package/dist/connection/index.d.ts +2 -6
- package/dist/connection/index.d.ts.map +1 -1
- package/dist/connection/lifecycle.d.ts +4 -1
- package/dist/connection/lifecycle.d.ts.map +1 -1
- package/dist/connection/media-devices.d.ts +11 -0
- package/dist/connection/media-devices.d.ts.map +1 -1
- package/dist/connection/production.d.ts +1 -1
- package/dist/connection/production.d.ts.map +1 -1
- package/dist/connection/types.d.ts +4 -4
- package/dist/connection/types.d.ts.map +1 -1
- package/dist/effect.d.ts +0 -4
- package/dist/effect.d.ts.map +1 -1
- package/dist/effect.js +65 -776
- package/dist/foreground-deadline.d.ts +9 -0
- package/dist/foreground-deadline.d.ts.map +1 -0
- package/dist/generated/http-api.d.ts +2561 -5213
- package/dist/generated/http-api.d.ts.map +1 -1
- package/dist/generated/schemas.d.ts +1919 -6814
- package/dist/generated/schemas.d.ts.map +1 -1
- package/dist/generated/sync.d.ts +191 -28
- package/dist/generated/sync.d.ts.map +1 -1
- package/dist/generated/whiteboard-v1.d.ts +0 -7
- package/dist/generated/whiteboard-v1.d.ts.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -128
- package/dist/media/client.d.ts +6 -5
- package/dist/media/client.d.ts.map +1 -1
- package/dist/media/index.d.ts +1 -2
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +1 -7
- package/dist/media/plane.d.ts +12 -1
- package/dist/media/plane.d.ts.map +1 -1
- package/dist/media/target.d.ts +4 -2
- package/dist/media/target.d.ts.map +1 -1
- package/dist/media/tracks.d.ts +2 -2
- package/dist/media/tracks.d.ts.map +1 -1
- package/dist/media/transport.d.ts.map +1 -1
- package/dist/media/types.d.ts +4 -11
- package/dist/media/types.d.ts.map +1 -1
- package/dist/public-invites/client.d.ts.map +1 -1
- package/dist/public-invites/index.d.ts +2 -2
- package/dist/public-invites/index.d.ts.map +1 -1
- package/dist/public-invites/index.js +2 -5
- package/dist/public-invites/types.d.ts +2 -6
- package/dist/public-invites/types.d.ts.map +1 -1
- package/dist/server/access-refresh.d.cts +5 -0
- package/dist/server/access-refresh.d.ts +5 -0
- package/dist/server/access-refresh.d.ts.map +1 -1
- package/dist/server/client.d.ts.map +1 -1
- package/dist/server/index.cjs +22 -117
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +19 -114
- package/dist/server/types.d.cts +2 -8
- package/dist/server/types.d.ts +2 -8
- package/dist/server/types.d.ts.map +1 -1
- package/dist/space-client/chat-controller-helpers.d.ts.map +1 -1
- package/dist/space-client/chat-controller.d.ts +2 -3
- package/dist/space-client/chat-controller.d.ts.map +1 -1
- package/dist/space-client/controller-effects.d.ts +0 -3
- package/dist/space-client/controller-effects.d.ts.map +1 -1
- package/dist/space-client/core.d.ts +3 -18
- package/dist/space-client/core.d.ts.map +1 -1
- package/dist/space-client/core.test.helpers.d.ts +1 -1
- package/dist/space-client/core.test.helpers.d.ts.map +1 -1
- package/dist/space-client/effect.d.ts +0 -2
- package/dist/space-client/effect.d.ts.map +1 -1
- package/dist/space-client/index.d.ts +1 -2
- package/dist/space-client/index.d.ts.map +1 -1
- package/dist/space-client/media-controller.d.ts +4 -4
- package/dist/space-client/media-controller.d.ts.map +1 -1
- package/dist/space-client/offline-actions-storage.d.ts +18 -3
- package/dist/space-client/offline-actions-storage.d.ts.map +1 -1
- package/dist/space-client/offline-actions.d.ts +1 -0
- package/dist/space-client/offline-actions.d.ts.map +1 -1
- package/dist/space-client/participants-controller.d.ts +2 -3
- package/dist/space-client/participants-controller.d.ts.map +1 -1
- package/dist/space-client/reactions-controller.d.ts +2 -3
- package/dist/space-client/reactions-controller.d.ts.map +1 -1
- package/dist/space-client/recording-controller.d.ts +2 -3
- package/dist/space-client/recording-controller.d.ts.map +1 -1
- package/dist/space-client/space-client.d.ts.map +1 -1
- package/dist/space-client/store.d.ts.map +1 -1
- package/dist/space-client/terminal.test.helpers.d.ts +136 -0
- package/dist/space-client/terminal.test.helpers.d.ts.map +1 -0
- package/dist/space-client/types.d.ts +17 -8
- package/dist/space-client/types.d.ts.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -3
- package/dist/sync/v1-client.d.ts +5 -3
- package/dist/sync/v1-client.d.ts.map +1 -1
- package/dist/sync/v1-collaboration-state.d.ts +10 -3
- package/dist/sync/v1-collaboration-state.d.ts.map +1 -1
- package/dist/sync/v1-types.d.ts +10 -9
- package/dist/sync/v1-types.d.ts.map +1 -1
- package/dist/webhooks/generated/event-v1.d.cts +10 -2
- package/dist/webhooks/generated/event-v1.d.ts +10 -2
- package/dist/webhooks/generated/event-v1.d.ts.map +1 -1
- package/dist/webhooks/generated/fixtures-v1.d.cts +2 -2
- package/dist/webhooks/generated/fixtures-v1.d.ts +2 -2
- package/dist/webhooks/index.cjs +10 -2
- package/dist/webhooks/index.js +10 -2
- package/dist/webhooks/test.cjs +2 -2
- package/dist/webhooks/test.js +2 -2
- package/dist/whiteboard/file-download-transport.d.ts +8 -0
- package/dist/whiteboard/file-download-transport.d.ts.map +1 -0
- package/dist/whiteboard/index.d.ts +1 -1
- package/dist/whiteboard/index.d.ts.map +1 -1
- package/dist/whiteboard/types.d.ts +12 -15
- package/dist/whiteboard/types.d.ts.map +1 -1
- package/dist/whiteboard/v1-client.d.ts.map +1 -1
- package/package.json +4 -16
- package/dist/access/episode-diagnostic-credential.d.ts +0 -11
- package/dist/access/episode-diagnostic-credential.d.ts.map +0 -1
- package/dist/chunk-EWTITNJ3.js +0 -1163
- package/dist/chunk-HW5TTZL2.js +0 -99
- package/dist/chunk-PBSYXPNJ.js +0 -23
- package/dist/chunk-RCDXIDCN.js +0 -33
- package/dist/connection/snapshot.d.ts +0 -23
- package/dist/connection/snapshot.d.ts.map +0 -1
- package/dist/feedback/collector.d.ts +0 -14
- package/dist/feedback/collector.d.ts.map +0 -1
- package/dist/feedback/context.d.ts +0 -38
- package/dist/feedback/context.d.ts.map +0 -1
- package/dist/feedback/controller.d.ts +0 -4
- package/dist/feedback/controller.d.ts.map +0 -1
- package/dist/feedback/index.d.ts +0 -7
- package/dist/feedback/index.d.ts.map +0 -1
- package/dist/feedback/transport.d.ts +0 -29
- package/dist/feedback/transport.d.ts.map +0 -1
- package/dist/feedback/types.d.ts +0 -202
- package/dist/feedback/types.d.ts.map +0 -1
- package/dist/feedback/validation.d.ts +0 -30
- package/dist/feedback/validation.d.ts.map +0 -1
- package/dist/media/rtk.d.ts +0 -101
- package/dist/media/rtk.d.ts.map +0 -1
- package/dist/space-client/episode-diagnostic-registry.d.ts +0 -13
- package/dist/space-client/episode-diagnostic-registry.d.ts.map +0 -1
- package/dist/space-client/episode-diagnostic-render-registry.d.ts +0 -4
- package/dist/space-client/episode-diagnostic-render-registry.d.ts.map +0 -1
- package/dist/space-client/episode-diagnostic-runtime.d.ts +0 -105
- package/dist/space-client/episode-diagnostic-runtime.d.ts.map +0 -1
- package/dist/telemetry/attributes.d.ts +0 -7
- package/dist/telemetry/attributes.d.ts.map +0 -1
- package/dist/telemetry/client.d.ts +0 -44
- package/dist/telemetry/client.d.ts.map +0 -1
- package/dist/telemetry/delivery.d.ts +0 -107
- package/dist/telemetry/delivery.d.ts.map +0 -1
- package/dist/telemetry/exporter.d.ts +0 -38
- package/dist/telemetry/exporter.d.ts.map +0 -1
- package/dist/telemetry/index.d.ts +0 -7
- package/dist/telemetry/index.d.ts.map +0 -1
- package/dist/telemetry/index.js +0 -50
- package/dist/telemetry/journey.d.ts +0 -52
- package/dist/telemetry/journey.d.ts.map +0 -1
- package/dist/telemetry/random.d.ts +0 -18
- package/dist/telemetry/random.d.ts.map +0 -1
- package/dist/telemetry/reconnect.d.ts +0 -7
- package/dist/telemetry/reconnect.d.ts.map +0 -1
- package/dist/telemetry/rtc.d.ts +0 -41
- package/dist/telemetry/rtc.d.ts.map +0 -1
- package/dist/telemetry/storage.d.ts +0 -22
- package/dist/telemetry/storage.d.ts.map +0 -1
- package/dist/telemetry/sync.d.ts +0 -10
- package/dist/telemetry/sync.d.ts.map +0 -1
- package/dist/telemetry/test-support.d.ts +0 -51
- package/dist/telemetry/test-support.d.ts.map +0 -1
- package/dist/telemetry/trace.d.ts +0 -18
- package/dist/telemetry/trace.d.ts.map +0 -1
- package/dist/telemetry/types.d.ts +0 -109
- package/dist/telemetry/types.d.ts.map +0 -1
- package/dist/whiteboard/file-http-transport.d.ts +0 -9
- package/dist/whiteboard/file-http-transport.d.ts.map +0 -1
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
journeyHeaders
|
|
3
|
-
} from "./chunk-HW5TTZL2.js";
|
|
4
|
-
import {
|
|
5
|
-
recordReconnect,
|
|
6
|
-
traceReconnect
|
|
7
|
-
} from "./chunk-RCDXIDCN.js";
|
|
8
|
-
|
|
9
1
|
// src/media/camera-uplink.ts
|
|
10
2
|
var CameraUplinkPolicy = class {
|
|
11
3
|
#healthySamples = 0;
|
|
@@ -46,14 +38,6 @@ function subscribeSnapshot(listeners, listener) {
|
|
|
46
38
|
listeners.delete(listener);
|
|
47
39
|
};
|
|
48
40
|
}
|
|
49
|
-
function observePublications(listeners, listener, project) {
|
|
50
|
-
const publication = () => listener(project());
|
|
51
|
-
listeners.add(publication);
|
|
52
|
-
listener(project());
|
|
53
|
-
return () => {
|
|
54
|
-
listeners.delete(publication);
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
41
|
|
|
58
42
|
// src/media/target.ts
|
|
59
43
|
function resolveMediaTarget(participantId, stopped, tracks, target) {
|
|
@@ -203,8 +187,6 @@ var CloudflareSFUClient = class {
|
|
|
203
187
|
#localListeners = /* @__PURE__ */ new Set();
|
|
204
188
|
#onError;
|
|
205
189
|
#onRemoteTrack;
|
|
206
|
-
#recordReconnect;
|
|
207
|
-
#onRtcSummary;
|
|
208
190
|
#onScreenEnded;
|
|
209
191
|
#participantId;
|
|
210
192
|
#peerConnectionFactory;
|
|
@@ -225,11 +207,17 @@ var CloudflareSFUClient = class {
|
|
|
225
207
|
#cursor = null;
|
|
226
208
|
#remotePullIncomplete = false;
|
|
227
209
|
#remotePullRetryAfter = /* @__PURE__ */ new Map();
|
|
210
|
+
#remoteParticipantIds = null;
|
|
211
|
+
#remoteParticipantRevision = 0;
|
|
212
|
+
#subscribedPublicationIds = /* @__PURE__ */ new Set();
|
|
228
213
|
#generation = 0;
|
|
229
214
|
#negotiatedGeneration = null;
|
|
230
215
|
#replacementAttemptedGeneration = null;
|
|
216
|
+
#remoteRecoveryGeneration = null;
|
|
231
217
|
#polling = false;
|
|
232
218
|
#pollAfterCurrent = false;
|
|
219
|
+
#pollFailures = 0;
|
|
220
|
+
#pollFailedPull = false;
|
|
233
221
|
#pollTimer;
|
|
234
222
|
#sdpTail = Promise.resolve();
|
|
235
223
|
#snapshot;
|
|
@@ -245,8 +233,6 @@ var CloudflareSFUClient = class {
|
|
|
245
233
|
this.#replaceMediaConnection = options.replaceMediaConnection;
|
|
246
234
|
this.#onError = options.onError;
|
|
247
235
|
this.#onRemoteTrack = options.onRemoteTrack;
|
|
248
|
-
this.#onRtcSummary = options.onRtcSummary;
|
|
249
|
-
this.#recordReconnect = options.recordReconnect;
|
|
250
236
|
this.#onScreenEnded = options.onScreenEnded;
|
|
251
237
|
this.#peerConnectionFactory = options.peerConnectionFactory;
|
|
252
238
|
this.#connection = this.#createPeerConnection(options.bootstrap);
|
|
@@ -269,8 +255,13 @@ var CloudflareSFUClient = class {
|
|
|
269
255
|
this.#remotePullRetryAfter.delete(publicationId);
|
|
270
256
|
this.remotePublicationsChanged();
|
|
271
257
|
}
|
|
258
|
+
remoteParticipantsChanged(participantIds) {
|
|
259
|
+
this.#remoteParticipantIds = new Set(participantIds);
|
|
260
|
+
this.#remoteParticipantRevision++;
|
|
261
|
+
this.#remotePullIncomplete = true;
|
|
262
|
+
this.remotePublicationsChanged();
|
|
263
|
+
}
|
|
272
264
|
remotePublicationsChanged() {
|
|
273
|
-
recordReconnect(this.#recordReconnect, "remote_publications_changed");
|
|
274
265
|
if (this.#polling) {
|
|
275
266
|
this.#pollAfterCurrent = true;
|
|
276
267
|
return;
|
|
@@ -308,6 +299,17 @@ var CloudflareSFUClient = class {
|
|
|
308
299
|
async clearPreparedLocalTrack(source) {
|
|
309
300
|
const state = this.#localTracks.get(source);
|
|
310
301
|
if (!state) return;
|
|
302
|
+
if (source === "screen_audio") {
|
|
303
|
+
try {
|
|
304
|
+
await this.#retireLocalPublication(source);
|
|
305
|
+
} finally {
|
|
306
|
+
this.#removeOwnedLocalTrack(state);
|
|
307
|
+
this.#localTracks.delete(source);
|
|
308
|
+
this.#publishSnapshot();
|
|
309
|
+
this.#emitLocal();
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
311
313
|
if (state.enabled) await this.#setPreparedTrackEnabled(state, false);
|
|
312
314
|
if (state.transceiver) this.#reusableLocalTransceivers.set(source, state.transceiver);
|
|
313
315
|
if (state.providerPublicationId) this.#reusableLocalPublicationIds.set(source, state.providerPublicationId);
|
|
@@ -324,7 +326,7 @@ var CloudflareSFUClient = class {
|
|
|
324
326
|
const generation = this.#generation;
|
|
325
327
|
this.#setPhase("connecting", null);
|
|
326
328
|
await this.#activatePreparedTracks(
|
|
327
|
-
[...this.#localTracks.values()].filter((state) => state.source
|
|
329
|
+
[...this.#localTracks.values()].filter((state) => state.source === "microphone" || state.source === "camera"),
|
|
328
330
|
generation
|
|
329
331
|
);
|
|
330
332
|
}
|
|
@@ -332,26 +334,35 @@ var CloudflareSFUClient = class {
|
|
|
332
334
|
if (!this.#started || this.#stopped || this.#polling) return;
|
|
333
335
|
this.#polling = true;
|
|
334
336
|
const generation = this.#generation;
|
|
337
|
+
this.#pollFailedPull = false;
|
|
335
338
|
try {
|
|
336
339
|
const transport = this.#requireTransport();
|
|
337
|
-
const authoritative = await
|
|
340
|
+
const authoritative = await (() => transport.listPublications())();
|
|
338
341
|
this.#requireGeneration(generation);
|
|
339
342
|
await this.#reconcileRemotePublications(authoritative, generation);
|
|
343
|
+
this.#requireGeneration(generation);
|
|
344
|
+
if (!this.#remotePullIncomplete) this.#pollFailures = 0;
|
|
340
345
|
} catch (error) {
|
|
341
346
|
if (generation === this.#generation && !this.#stopped) this.#reportError(error);
|
|
342
347
|
throw error;
|
|
343
348
|
} finally {
|
|
344
|
-
|
|
345
|
-
this.#polling = false;
|
|
346
|
-
if (this.#pollAfterCurrent) {
|
|
347
|
-
this.#pollAfterCurrent = false;
|
|
348
|
-
this.#clearPoll();
|
|
349
|
-
this.#schedulePoll(0);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
349
|
+
this.#finishPublicationPoll(generation);
|
|
352
350
|
}
|
|
353
351
|
}
|
|
352
|
+
#finishPublicationPoll(generation) {
|
|
353
|
+
if (generation !== this.#generation) return;
|
|
354
|
+
this.#polling = false;
|
|
355
|
+
if (!this.#pollAfterCurrent) return;
|
|
356
|
+
this.#pollAfterCurrent = false;
|
|
357
|
+
this.#clearPoll();
|
|
358
|
+
this.#schedulePoll(0);
|
|
359
|
+
}
|
|
354
360
|
async setLocalPublicationTarget(target) {
|
|
361
|
+
const result = await this.#setSinglePublicationTarget(target);
|
|
362
|
+
if (target.source !== "screen" || !this.#localTracks.has("screen_audio") || result.outcome !== "confirmed" && result.outcome !== "satisfied") return result;
|
|
363
|
+
return this.#setSinglePublicationTarget({ ...target, source: "screen_audio" });
|
|
364
|
+
}
|
|
365
|
+
async #setSinglePublicationTarget(target) {
|
|
355
366
|
const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
|
|
356
367
|
if (resolved.kind === "result") return resolved.result;
|
|
357
368
|
const state = resolved.value;
|
|
@@ -371,6 +382,7 @@ var CloudflareSFUClient = class {
|
|
|
371
382
|
return target.enabled && this.#locallyPausedSources.has(target.source);
|
|
372
383
|
}
|
|
373
384
|
setLocalSourceIntent(source, enabled) {
|
|
385
|
+
if (source === "screen") this.setLocalSourceIntent("screen_audio", enabled);
|
|
374
386
|
if (enabled) {
|
|
375
387
|
this.#locallyPausedSources.delete(source);
|
|
376
388
|
return;
|
|
@@ -384,6 +396,7 @@ var CloudflareSFUClient = class {
|
|
|
384
396
|
async closeForcedLocalPublication(source) {
|
|
385
397
|
this.setLocalSourceIntent(source, false);
|
|
386
398
|
await this.#retireLocalPublication(source);
|
|
399
|
+
if (source === "screen") await this.#retireLocalPublication("screen_audio");
|
|
387
400
|
if (!this.#replaceMediaConnection) return;
|
|
388
401
|
const generation = this.#generation;
|
|
389
402
|
await this.#serializeSDP(async () => {
|
|
@@ -402,7 +415,7 @@ var CloudflareSFUClient = class {
|
|
|
402
415
|
async #retireUnavailableLocalPublication(state) {
|
|
403
416
|
const publicationId = state.providerPublicationId;
|
|
404
417
|
if (!publicationId) return;
|
|
405
|
-
const authoritative = await
|
|
418
|
+
const authoritative = await (() => this.#requireTransport().listPublications())();
|
|
406
419
|
if (authoritative.publications.some((publication) => publication.publicationId === publicationId)) return;
|
|
407
420
|
if (state.providerPublicationId === publicationId) await this.#retireLocalPublication(state.source);
|
|
408
421
|
}
|
|
@@ -430,8 +443,8 @@ var CloudflareSFUClient = class {
|
|
|
430
443
|
const mid = requireTransceiverMid(transceiver);
|
|
431
444
|
await this.#boundPeerOperation(transceiver.sender.replaceTrack(null));
|
|
432
445
|
transceiver.stop();
|
|
433
|
-
const offer = await
|
|
434
|
-
await
|
|
446
|
+
const offer = await (() => connection.createOffer())();
|
|
447
|
+
await (() => connection.setLocalDescription(offer))();
|
|
435
448
|
try {
|
|
436
449
|
const response = await this.#requireTransport().closeTracks({
|
|
437
450
|
connectionId,
|
|
@@ -461,7 +474,6 @@ var CloudflareSFUClient = class {
|
|
|
461
474
|
this.#requireActive();
|
|
462
475
|
const options = "connectionId" in input ? { bootstrap: input } : input;
|
|
463
476
|
validateBootstrap(options.bootstrap);
|
|
464
|
-
recordReconnect(this.#recordReconnect, "media_rebuild_start");
|
|
465
477
|
const generation = ++this.#generation;
|
|
466
478
|
const connectionEpoch = ++this.#connectionEpoch;
|
|
467
479
|
this.#polling = false;
|
|
@@ -478,20 +490,19 @@ var CloudflareSFUClient = class {
|
|
|
478
490
|
if (options.bootstrap.connectionId !== this.#bootstrap.connectionId) this.#started = false;
|
|
479
491
|
this.#bootstrap = options.bootstrap;
|
|
480
492
|
if (options.transport) this.#transport = options.transport;
|
|
481
|
-
this.#
|
|
482
|
-
this.#observeConnection(this.#connection, generation, connectionEpoch);
|
|
493
|
+
this.#rebuildPeerConnection(options.bootstrap, generation, connectionEpoch);
|
|
483
494
|
for (const state of this.#localTracks.values()) {
|
|
484
495
|
state.transceiver = null;
|
|
485
496
|
state.enabled = false;
|
|
486
497
|
}
|
|
487
498
|
const enabled = [...this.#localTracks.values()].filter((state) => state.desiredEnabled && state.track.readyState !== "ended");
|
|
488
|
-
this.#setPhase("recovering", null);
|
|
489
499
|
await this.#activatePreparedTracks(enabled, generation);
|
|
490
500
|
}
|
|
491
501
|
async #activatePreparedTracks(states, generation) {
|
|
492
502
|
try {
|
|
493
503
|
await this.#publishPreparedTracks(states, generation);
|
|
494
504
|
this.#requireGeneration(generation);
|
|
505
|
+
if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
|
|
495
506
|
this.#started = true;
|
|
496
507
|
this.#setPhase("live", null);
|
|
497
508
|
this.#schedulePoll(0);
|
|
@@ -503,6 +514,7 @@ var CloudflareSFUClient = class {
|
|
|
503
514
|
stop() {
|
|
504
515
|
if (this.#stopped) return;
|
|
505
516
|
this.#stopped = true;
|
|
517
|
+
this.#subscribedPublicationIds.clear();
|
|
506
518
|
this.#generation++;
|
|
507
519
|
this.#connectionEpoch++;
|
|
508
520
|
this.#clearPoll();
|
|
@@ -557,6 +569,7 @@ var CloudflareSFUClient = class {
|
|
|
557
569
|
const generation = this.#generation;
|
|
558
570
|
try {
|
|
559
571
|
await this.#publishPreparedTracks([state], generation);
|
|
572
|
+
if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
|
|
560
573
|
} catch (error) {
|
|
561
574
|
state.desiredEnabled = false;
|
|
562
575
|
state.enabled = false;
|
|
@@ -580,7 +593,6 @@ var CloudflareSFUClient = class {
|
|
|
580
593
|
if (states.length === 0) return;
|
|
581
594
|
await this.#serializeSDP(async () => {
|
|
582
595
|
const pending = await this.#prepareConnectionForPublication(states, generation);
|
|
583
|
-
this.#replacementAttemptedGeneration = null;
|
|
584
596
|
try {
|
|
585
597
|
await this.#publishPreparedTracksSerialized(pending, generation, this.#connection, this.#bootstrap.connectionId);
|
|
586
598
|
} catch (error) {
|
|
@@ -600,7 +612,7 @@ var CloudflareSFUClient = class {
|
|
|
600
612
|
await this.#replaceDormantConnectionBeforeNegotiation(generation);
|
|
601
613
|
return states;
|
|
602
614
|
}
|
|
603
|
-
const authoritative = await
|
|
615
|
+
const authoritative = await (() => this.#requireTransport().listPublications())();
|
|
604
616
|
this.#pauseUnavailableOtherSources(authoritative.publications, states);
|
|
605
617
|
this.#replacementAttemptedGeneration = null;
|
|
606
618
|
await this.#replaceMediaConnectionForRecovery(generation);
|
|
@@ -664,8 +676,8 @@ var CloudflareSFUClient = class {
|
|
|
664
676
|
async #negotiateLocalPublications(connection, connectionId, publications, generation) {
|
|
665
677
|
let offer;
|
|
666
678
|
try {
|
|
667
|
-
offer = await
|
|
668
|
-
await
|
|
679
|
+
offer = await (() => connection.createOffer())();
|
|
680
|
+
await (() => connection.setLocalDescription(offer))();
|
|
669
681
|
} catch (error) {
|
|
670
682
|
const cameras = publications.filter(({ state, transceiver }) => state.source === "camera" && transceiver.sender.getParameters().encodings.length > 1);
|
|
671
683
|
if (!simulcastUnsupported(error) || cameras.length === 0) throw error;
|
|
@@ -677,8 +689,8 @@ var CloudflareSFUClient = class {
|
|
|
677
689
|
publication.transceiver = connection.addTransceiver(publication.state.track, { direction: "sendonly" });
|
|
678
690
|
publication.state.transceiver = publication.transceiver;
|
|
679
691
|
}
|
|
680
|
-
offer = await
|
|
681
|
-
await
|
|
692
|
+
offer = await (() => connection.createOffer())();
|
|
693
|
+
await (() => connection.setLocalDescription(offer))();
|
|
682
694
|
}
|
|
683
695
|
const tracks = publications.map(
|
|
684
696
|
({ state, transceiver, trackName }) => ({
|
|
@@ -688,7 +700,7 @@ var CloudflareSFUClient = class {
|
|
|
688
700
|
source: state.source
|
|
689
701
|
})
|
|
690
702
|
);
|
|
691
|
-
const response = await
|
|
703
|
+
const response = await (() => this.#requireTransport().addTracks({ connectionId, ...providerDescription(requireDescription(offer)), tracks }))();
|
|
692
704
|
this.#requireGeneration(generation);
|
|
693
705
|
await this.#applyProviderDescription(response, connection);
|
|
694
706
|
return response;
|
|
@@ -734,12 +746,12 @@ var CloudflareSFUClient = class {
|
|
|
734
746
|
}
|
|
735
747
|
}
|
|
736
748
|
async #reconcileRemotePublications(authoritative, generation) {
|
|
737
|
-
const cursor = this.#
|
|
749
|
+
const cursor = this.#nextRemotePublicationCursor(authoritative);
|
|
738
750
|
if (cursor === null) return;
|
|
739
|
-
const
|
|
740
|
-
if (ordering === "stale" || ordering === "same" && !this.#remotePullIncomplete) return;
|
|
751
|
+
const participantRevision = this.#remoteParticipantRevision;
|
|
741
752
|
const desired = this.#desiredRemotePublications(authoritative, cursor);
|
|
742
753
|
const toPull = this.#pendingRemotePulls(desired);
|
|
754
|
+
this.#pollFailedPull = true;
|
|
743
755
|
const pulled = await this.#pullWithRecovery(toPull, cursor, generation);
|
|
744
756
|
if (pulled === null) return;
|
|
745
757
|
this.#requireGeneration(generation);
|
|
@@ -748,8 +760,13 @@ var CloudflareSFUClient = class {
|
|
|
748
760
|
this.#remoteTracks.clear();
|
|
749
761
|
for (const [key, publication] of next) this.#remoteTracks.set(key, publication);
|
|
750
762
|
this.#cursor = cursor;
|
|
751
|
-
this.#remotePullIncomplete =
|
|
752
|
-
|
|
763
|
+
this.#remotePullIncomplete = participantRevision !== this.#remoteParticipantRevision || next.size !== desired.size;
|
|
764
|
+
if (!this.#remotePullIncomplete) this.#remoteRecoveryGeneration = null;
|
|
765
|
+
if (!this.#remotePullIncomplete && this.#hasLiveRemoteTracks()) this.#replacementAttemptedGeneration = null;
|
|
766
|
+
for (const publication of pulled) {
|
|
767
|
+
this.#subscribedPublicationIds.add(publication.publicationId);
|
|
768
|
+
this.#invokeListener(() => this.#onRemoteTrack?.(publication));
|
|
769
|
+
}
|
|
753
770
|
this.#publishSnapshot();
|
|
754
771
|
this.#emitRemote();
|
|
755
772
|
}
|
|
@@ -758,31 +775,40 @@ var CloudflareSFUClient = class {
|
|
|
758
775
|
for (const publicationId of this.#remotePullRetryAfter.keys()) {
|
|
759
776
|
if (!desiredIds.has(publicationId)) this.#remotePullRetryAfter.delete(publicationId);
|
|
760
777
|
}
|
|
778
|
+
for (const publicationId of this.#subscribedPublicationIds) {
|
|
779
|
+
if (!desiredIds.has(publicationId)) this.#subscribedPublicationIds.delete(publicationId);
|
|
780
|
+
}
|
|
761
781
|
const now = Date.now();
|
|
762
782
|
return [...desired].filter(([key, publication]) => {
|
|
763
783
|
if (this.#remoteTracks.get(key)?.publicationId === publication.publicationId) return false;
|
|
764
784
|
const remaining = (this.#remotePullRetryAfter.get(publication.publicationId) ?? 0) - now;
|
|
765
|
-
if (remaining > 0) recordReconnect(this.#recordReconnect, "remote_pull_deferred", { remaining_ms: remaining, source: publication.source });
|
|
766
785
|
return remaining <= 0;
|
|
767
786
|
}).map(([, publication]) => publication);
|
|
768
787
|
}
|
|
769
|
-
#
|
|
788
|
+
#nextRemotePublicationCursor(authoritative) {
|
|
770
789
|
if (this.#cursor?.signature === INVALID_PUBLICATION_SIGNATURE && this.#cursor.incarnation === authoritative.incarnation && this.#cursor.sequence === authoritative.sequence) return null;
|
|
790
|
+
let cursor;
|
|
771
791
|
try {
|
|
772
|
-
|
|
792
|
+
cursor = validatePublicationSnapshot(authoritative);
|
|
773
793
|
} catch (error) {
|
|
774
794
|
this.#quarantineInvalidRemotePublicationCursor(authoritative);
|
|
775
795
|
throw error;
|
|
776
796
|
}
|
|
797
|
+
const ordering = comparePublicationCursor(this.#cursor, cursor);
|
|
798
|
+
return ordering === "stale" || ordering === "same" && !this.#remotePullIncomplete ? null : cursor;
|
|
777
799
|
}
|
|
778
800
|
#desiredRemotePublications(authoritative, cursor) {
|
|
779
801
|
try {
|
|
780
|
-
|
|
802
|
+
const publications = authoritative.publications.filter((publication) => this.#canSubscribeToRemotePublication(publication));
|
|
803
|
+
return desiredRemotePublications(publications, this.#participantId);
|
|
781
804
|
} catch (error) {
|
|
782
805
|
this.#observeRemotePublicationCursor(cursor);
|
|
783
806
|
throw error;
|
|
784
807
|
}
|
|
785
808
|
}
|
|
809
|
+
#canSubscribeToRemotePublication(publication) {
|
|
810
|
+
return this.#remoteParticipantIds === null || this.#remoteParticipantIds.has(publication.participantId) || this.#subscribedPublicationIds.has(publication.publicationId);
|
|
811
|
+
}
|
|
786
812
|
async #pullWithRecovery(publications, cursor, generation) {
|
|
787
813
|
try {
|
|
788
814
|
return await this.#pull(publications, generation);
|
|
@@ -796,12 +822,13 @@ var CloudflareSFUClient = class {
|
|
|
796
822
|
}
|
|
797
823
|
}
|
|
798
824
|
#canReplaceConnectionAfterRemotePull(error, generation) {
|
|
799
|
-
return error
|
|
825
|
+
return this.#isRetryableConnectionFailure(error) && !this.#hasLiveRemoteTracks() && this.#replaceMediaConnection !== void 0 && this.#replacementAttemptedGeneration !== generation;
|
|
800
826
|
}
|
|
801
827
|
#hasLiveRemoteTracks() {
|
|
802
828
|
return [...this.#remoteTracks.values()].some((publication) => publication.track.readyState === "live");
|
|
803
829
|
}
|
|
804
830
|
async #retryRemotePullOnReplacement(publications, cursor, generation) {
|
|
831
|
+
this.#remoteRecoveryGeneration = generation;
|
|
805
832
|
try {
|
|
806
833
|
await this.#replaceMediaConnectionForRecovery(generation);
|
|
807
834
|
await this.#republishPreparedTracksAfterRecovery(generation);
|
|
@@ -813,6 +840,44 @@ var CloudflareSFUClient = class {
|
|
|
813
840
|
}
|
|
814
841
|
}
|
|
815
842
|
async #pull(publications, generation) {
|
|
843
|
+
const eligible = publications.filter((publication) => this.#canSubscribeToRemotePublication(publication));
|
|
844
|
+
try {
|
|
845
|
+
return await this.#pullBatch(eligible, generation);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
this.#requireGeneration(generation);
|
|
848
|
+
if (eligible.length <= 1 || !retryableRemotePull(error) || isRateLimited(error) || this.#canReplaceConnectionAfterRemotePull(error, generation) || error instanceof CloudflareSFUError && (error.options.status === 404 || error.options.status === 410)) {
|
|
849
|
+
this.#deferRemotePulls(eligible, error);
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return this.#pullIndividually(eligible, generation);
|
|
854
|
+
}
|
|
855
|
+
async #pullIndividually(eligible, generation) {
|
|
856
|
+
const pulled = [];
|
|
857
|
+
let failure;
|
|
858
|
+
for (const [index, publication] of eligible.entries()) {
|
|
859
|
+
if (!this.#canSubscribeToRemotePublication(publication)) continue;
|
|
860
|
+
try {
|
|
861
|
+
pulled.push(...await this.#pullBatch([publication], generation));
|
|
862
|
+
} catch (error) {
|
|
863
|
+
this.#requireGeneration(generation);
|
|
864
|
+
this.#deferRemotePulls([publication], error);
|
|
865
|
+
this.#reportError(error);
|
|
866
|
+
if (failure === void 0 || retryableRemotePull(error)) failure = error;
|
|
867
|
+
if (isRateLimited(error)) {
|
|
868
|
+
this.#deferRemotePulls(eligible.slice(index + 1), error);
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (failure && pulled.length === 0) throw failure;
|
|
874
|
+
return pulled;
|
|
875
|
+
}
|
|
876
|
+
#deferRemotePulls(publications, error) {
|
|
877
|
+
const retryAfter = retryableRemotePull(error) ? Date.now() + 15e3 : Number.POSITIVE_INFINITY;
|
|
878
|
+
for (const publication of publications) this.#remotePullRetryAfter.set(publication.publicationId, retryAfter);
|
|
879
|
+
}
|
|
880
|
+
async #pullBatch(publications, generation) {
|
|
816
881
|
if (publications.length === 0) return [];
|
|
817
882
|
return this.#serializeSDP(async () => {
|
|
818
883
|
const connection = this.#connection;
|
|
@@ -836,7 +901,7 @@ var CloudflareSFUClient = class {
|
|
|
836
901
|
connection.addEventListener("track", onTrack);
|
|
837
902
|
try {
|
|
838
903
|
this.#requireGeneration(generation);
|
|
839
|
-
const response = await
|
|
904
|
+
const response = await (() => this.#requireTransport().addTracks({ connectionId, tracks: requested, allowPartialRemoteTracks: true }))();
|
|
840
905
|
this.#requireGeneration(generation);
|
|
841
906
|
this.#recordRemotePullErrors(publications, response.trackErrors ?? []);
|
|
842
907
|
const responseTracks = response.tracks ?? [];
|
|
@@ -846,7 +911,7 @@ var CloudflareSFUClient = class {
|
|
|
846
911
|
if (matched.length === 0) return [];
|
|
847
912
|
await this.#waitForConnection(connection, generation);
|
|
848
913
|
this.#negotiatedGeneration = generation;
|
|
849
|
-
await
|
|
914
|
+
await (() => waitFor(() => responseTracks.every((track) => track.mid !== void 0 && received.has(track.mid)), 5e3))();
|
|
850
915
|
this.#requireGeneration(generation);
|
|
851
916
|
return matched.map(({ publication, mid }) => {
|
|
852
917
|
const track = received.get(mid);
|
|
@@ -871,26 +936,24 @@ var CloudflareSFUClient = class {
|
|
|
871
936
|
for (const failure of failures) {
|
|
872
937
|
const publication = requested.get(`${failure.connectionId}\0${failure.trackName}`);
|
|
873
938
|
if (!publication) throw new CloudflareSFUError("Cloudflare SFU returned an unrequested remote track error", "invalid_publication");
|
|
874
|
-
const delay =
|
|
939
|
+
const delay = 15e3;
|
|
875
940
|
this.#remotePullRetryAfter.set(publication.publicationId, Date.now() + delay);
|
|
876
|
-
recordReconnect(this.#recordReconnect, "remote_pull_backoff", { delay_ms: delay, retry_kind: failure.code === "empty_track_error" ? "empty_track" : "provider_error", source: publication.source });
|
|
877
941
|
this.#reportError(new CloudflareSFUError(`Cloudflare SFU remote track failed: ${failure.code}`, "media_failed", { providerCode: failure.code }));
|
|
878
942
|
}
|
|
879
943
|
}
|
|
880
944
|
async #completeRenegotiation(response, connection, connectionId, generation) {
|
|
881
945
|
if (!response.requiresImmediateRenegotiation) return;
|
|
882
946
|
await this.#applyProviderDescription(response, connection);
|
|
883
|
-
const answer = await
|
|
884
|
-
await
|
|
885
|
-
await
|
|
947
|
+
const answer = await (() => connection.createAnswer())();
|
|
948
|
+
await (() => connection.setLocalDescription(answer))();
|
|
949
|
+
await (() => this.#requireTransport().renegotiate({ connectionId, ...providerDescription(requireDescription(answer)) }))();
|
|
886
950
|
this.#requireGeneration(generation);
|
|
887
951
|
}
|
|
888
952
|
async #applyProviderDescription(response, connection = this.#connection) {
|
|
889
|
-
await
|
|
953
|
+
await (() => connection.setRemoteDescription(requireSFUDescription(response.sessionDescription)))();
|
|
890
954
|
}
|
|
891
955
|
#serializeSDP(operation) {
|
|
892
956
|
const generation = this.#generation;
|
|
893
|
-
const queuedAt = performance.now();
|
|
894
957
|
let expired = false;
|
|
895
958
|
let timer;
|
|
896
959
|
const timeout = new CloudflareSFUError("Cloudflare SFU negotiation timed out. Try again.", "negotiation_timeout");
|
|
@@ -903,7 +966,6 @@ var CloudflareSFUClient = class {
|
|
|
903
966
|
});
|
|
904
967
|
const run = () => {
|
|
905
968
|
if (expired || generation !== this.#generation) throw timeout;
|
|
906
|
-
recordReconnect(this.#recordReconnect, "sdp_queue", { duration_ms: performance.now() - queuedAt });
|
|
907
969
|
return operation();
|
|
908
970
|
};
|
|
909
971
|
const pending = this.#sdpTail.then(run, run);
|
|
@@ -944,18 +1006,14 @@ var CloudflareSFUClient = class {
|
|
|
944
1006
|
this.#setFailure(error, "media_failed");
|
|
945
1007
|
}
|
|
946
1008
|
#waitForConnection(connection, generation) {
|
|
947
|
-
return
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
CONNECTION_TIMEOUT_MS,
|
|
956
|
-
"Timed out waiting for the Cloudflare SFU peer connection"
|
|
957
|
-
)
|
|
958
|
-
);
|
|
1009
|
+
return (() => waitFor(
|
|
1010
|
+
() => {
|
|
1011
|
+
this.#requireGeneration(generation);
|
|
1012
|
+
return connectionIsLive(connection);
|
|
1013
|
+
},
|
|
1014
|
+
CONNECTION_TIMEOUT_MS,
|
|
1015
|
+
"Timed out waiting for the Cloudflare SFU peer connection"
|
|
1016
|
+
))();
|
|
959
1017
|
}
|
|
960
1018
|
#cameraSimulcastSender(state) {
|
|
961
1019
|
if (!state?.enabled) return void 0;
|
|
@@ -1018,84 +1076,16 @@ var CloudflareSFUClient = class {
|
|
|
1018
1076
|
};
|
|
1019
1077
|
const capture = () => {
|
|
1020
1078
|
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
1021
|
-
recordReconnect(this.#recordReconnect, "rtc_state", { connection_state: connection.connectionState, ice_connection_state: connection.iceConnectionState, signaling_state: connection.signalingState });
|
|
1022
1079
|
observe();
|
|
1023
|
-
const recorder = this.#onRtcSummary;
|
|
1024
|
-
if (recorder) {
|
|
1025
|
-
let stats;
|
|
1026
|
-
try {
|
|
1027
|
-
stats = Promise.resolve(connection.getStats());
|
|
1028
|
-
} catch {
|
|
1029
|
-
stats = Promise.reject(new Error("RTC stats are unavailable"));
|
|
1030
|
-
}
|
|
1031
|
-
void stats.then((report) => {
|
|
1032
|
-
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
1033
|
-
recorder(rtcConnectionState(connection), rtcStats(report));
|
|
1034
|
-
}).catch(() => void 0);
|
|
1035
|
-
}
|
|
1036
1080
|
if (connection.connectionState === "closed") dispose();
|
|
1037
1081
|
};
|
|
1038
1082
|
this.#disposeConnectionObservation = dispose;
|
|
1039
1083
|
cameraTimer = globalThis.setTimeout(adaptCamera, 1e3);
|
|
1040
|
-
if (this.#recordReconnect) this.#sampleRecoveryTraffic(connection, generation, connectionEpoch);
|
|
1041
1084
|
connection.addEventListener("connectionstatechange", capture);
|
|
1042
1085
|
connection.addEventListener("iceconnectionstatechange", capture);
|
|
1043
1086
|
connection.addEventListener("signalingstatechange", capture);
|
|
1044
1087
|
capture();
|
|
1045
1088
|
}
|
|
1046
|
-
#sampleRecoveryTraffic(connection, generation, connectionEpoch) {
|
|
1047
|
-
const traffic = /* @__PURE__ */ new Map();
|
|
1048
|
-
let dtlsState;
|
|
1049
|
-
const current = () => generation === this.#generation && connectionEpoch === this.#connectionEpoch && !this.#stopped && connection.connectionState !== "closed" && connection.connectionState !== "failed";
|
|
1050
|
-
const sample = async () => {
|
|
1051
|
-
if (!current()) return;
|
|
1052
|
-
try {
|
|
1053
|
-
const report = await connection.getStats();
|
|
1054
|
-
if (!current()) return;
|
|
1055
|
-
const now = performance.now();
|
|
1056
|
-
report.forEach((entry) => {
|
|
1057
|
-
if (entry.type === "transport") {
|
|
1058
|
-
const nextState = rtcString(entry, "dtlsState");
|
|
1059
|
-
if (nextState !== dtlsState) recordReconnect(this.#recordReconnect, "dtls_state", { dtls_state: nextState ?? "unknown" });
|
|
1060
|
-
dtlsState = nextState;
|
|
1061
|
-
}
|
|
1062
|
-
this.#observeRecoveryTraffic(entry, traffic, now);
|
|
1063
|
-
});
|
|
1064
|
-
} catch {
|
|
1065
|
-
if (current()) recordReconnect(this.#recordReconnect, "rtc_stats_unavailable");
|
|
1066
|
-
}
|
|
1067
|
-
if (current()) globalThis.setTimeout(() => void sample(), 250);
|
|
1068
|
-
};
|
|
1069
|
-
void sample();
|
|
1070
|
-
}
|
|
1071
|
-
#observeRecoveryTraffic(entry, traffic, now) {
|
|
1072
|
-
if (entry.type !== "inbound-rtp" && entry.type !== "outbound-rtp") return;
|
|
1073
|
-
const direction = entry.type === "inbound-rtp" ? "inbound" : "outbound";
|
|
1074
|
-
const kind = rtcString(entry, "kind") ?? "unknown";
|
|
1075
|
-
const previous = traffic.get(entry.id);
|
|
1076
|
-
const next = previous ?? {
|
|
1077
|
-
packets: { count: 0, lastProgress: now, stalled: false },
|
|
1078
|
-
frames: { count: 0, lastProgress: now, stalled: false }
|
|
1079
|
-
};
|
|
1080
|
-
const packets = recoveryTrafficCount(entry, direction, "packet");
|
|
1081
|
-
const frames = recoveryTrafficCount(entry, direction, "frame");
|
|
1082
|
-
this.#observeTrafficCounter(next.packets, { count: packets, now, direction, kind, unit: "packet", existed: previous !== void 0 });
|
|
1083
|
-
this.#observeTrafficCounter(next.frames, { count: frames, now, direction, kind, unit: "frame", existed: previous !== void 0 });
|
|
1084
|
-
traffic.set(entry.id, next);
|
|
1085
|
-
}
|
|
1086
|
-
#observeTrafficCounter(progress, sample) {
|
|
1087
|
-
const attributes = { direction: sample.direction, kind: sample.kind };
|
|
1088
|
-
if (sample.count > progress.count) {
|
|
1089
|
-
if (progress.count === 0) recordReconnect(this.#recordReconnect, `first_${sample.unit}`, attributes);
|
|
1090
|
-
else if (progress.stalled) recordReconnect(this.#recordReconnect, sample.unit === "packet" ? "rtp_resumed" : "frame_resumed", attributes);
|
|
1091
|
-
progress.lastProgress = sample.now;
|
|
1092
|
-
progress.stalled = false;
|
|
1093
|
-
} else if (trafficStalled(progress, sample)) {
|
|
1094
|
-
progress.stalled = true;
|
|
1095
|
-
recordReconnect(this.#recordReconnect, sample.unit === "packet" ? "rtp_stall" : "frame_stall", attributes);
|
|
1096
|
-
}
|
|
1097
|
-
progress.count = sample.count;
|
|
1098
|
-
}
|
|
1099
1089
|
async #replaceDormantConnectionBeforeNegotiation(generation) {
|
|
1100
1090
|
if (!this.#started || this.#negotiatedGeneration === generation || this.#replacementAttemptedGeneration === generation || !this.#replaceMediaConnection) return;
|
|
1101
1091
|
await this.#replaceMediaConnectionForRecovery(generation);
|
|
@@ -1107,7 +1097,7 @@ var CloudflareSFUClient = class {
|
|
|
1107
1097
|
}
|
|
1108
1098
|
this.#replacementAttemptedGeneration = generation;
|
|
1109
1099
|
const replaceMediaConnection = this.#replaceMediaConnection;
|
|
1110
|
-
const bootstrap = await
|
|
1100
|
+
const bootstrap = await replaceMediaConnection();
|
|
1111
1101
|
this.#requireGeneration(generation);
|
|
1112
1102
|
validateBootstrap(bootstrap);
|
|
1113
1103
|
if (bootstrap.connectionId === this.#bootstrap.connectionId) {
|
|
@@ -1120,16 +1110,14 @@ var CloudflareSFUClient = class {
|
|
|
1120
1110
|
this.#clearRemoteTracks();
|
|
1121
1111
|
this.#cursor = null;
|
|
1122
1112
|
this.#bootstrap = bootstrap;
|
|
1123
|
-
this.#
|
|
1113
|
+
this.#rebuildPeerConnection(bootstrap, generation, connectionEpoch);
|
|
1124
1114
|
this.#retiredLocalConnection = null;
|
|
1125
1115
|
this.#negotiatedGeneration = null;
|
|
1126
|
-
this.#observeConnection(this.#connection, generation, connectionEpoch);
|
|
1127
1116
|
for (const state of this.#localTracks.values()) {
|
|
1128
1117
|
state.transceiver = null;
|
|
1129
1118
|
state.enabled = false;
|
|
1130
1119
|
state.providerPublicationId = null;
|
|
1131
1120
|
}
|
|
1132
|
-
this.#setPhase("recovering", null);
|
|
1133
1121
|
this.remotePublicationsChanged();
|
|
1134
1122
|
}
|
|
1135
1123
|
async #republishPreparedTracksAfterRecovery(generation) {
|
|
@@ -1152,7 +1140,17 @@ var CloudflareSFUClient = class {
|
|
|
1152
1140
|
const create = this.#peerConnectionFactory ?? ((configuration) => new RTCPeerConnection(configuration));
|
|
1153
1141
|
return create({ iceServers: [{ urls: bootstrap.stunServer }], bundlePolicy: "max-bundle" });
|
|
1154
1142
|
}
|
|
1143
|
+
#rebuildPeerConnection(bootstrap, generation, connectionEpoch) {
|
|
1144
|
+
try {
|
|
1145
|
+
this.#connection = this.#createPeerConnection(bootstrap);
|
|
1146
|
+
this.#observeConnection(this.#connection, generation, connectionEpoch);
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
this.#setFailure(error, "media_failed");
|
|
1149
|
+
throw error;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1155
1152
|
#disposeConnection(stopSenders) {
|
|
1153
|
+
if (!stopSenders) this.#setPhase("recovering", null);
|
|
1156
1154
|
this.#disposeConnectionObservation?.();
|
|
1157
1155
|
this.#disposeConnectionObservation = void 0;
|
|
1158
1156
|
if (stopSenders) {
|
|
@@ -1198,15 +1196,27 @@ var CloudflareSFUClient = class {
|
|
|
1198
1196
|
this.#schedulePoll();
|
|
1199
1197
|
return;
|
|
1200
1198
|
}
|
|
1199
|
+
let nextDelay = this.#pollIntervalMs;
|
|
1201
1200
|
try {
|
|
1202
1201
|
await this.refreshRemotePublications();
|
|
1202
|
+
if (this.#remotePullIncomplete) nextDelay = this.#incompletePullDelay();
|
|
1203
1203
|
} catch {
|
|
1204
|
+
if (this.#pollFailedPull && this.#remotePullIncomplete) nextDelay = this.#incompletePullDelay();
|
|
1205
|
+
else {
|
|
1206
|
+
this.#pollFailures = Math.min(this.#pollFailures + 1, 6);
|
|
1207
|
+
nextDelay = Math.min(this.#pollIntervalMs, (500 + Math.random() * 500) * 2 ** (this.#pollFailures - 1));
|
|
1208
|
+
}
|
|
1204
1209
|
} finally {
|
|
1205
|
-
this.#schedulePoll(
|
|
1210
|
+
this.#schedulePoll(nextDelay);
|
|
1206
1211
|
}
|
|
1207
1212
|
}, delayMs);
|
|
1208
1213
|
}
|
|
1214
|
+
#incompletePullDelay() {
|
|
1215
|
+
this.#pollFailures = Math.min(this.#pollFailures + 1, 6);
|
|
1216
|
+
return Math.min(this.#pollIntervalMs, 750 * 2 ** (this.#pollFailures - 1));
|
|
1217
|
+
}
|
|
1209
1218
|
#clearPoll() {
|
|
1219
|
+
this.#pollFailures = 0;
|
|
1210
1220
|
if (this.#pollTimer !== void 0) globalThis.clearTimeout(this.#pollTimer);
|
|
1211
1221
|
this.#pollTimer = void 0;
|
|
1212
1222
|
}
|
|
@@ -1291,76 +1301,11 @@ function validateClientOptions(options) {
|
|
|
1291
1301
|
throw new CloudflareSFUError("Cloudflare SFU polling interval is invalid", "invalid_bootstrap");
|
|
1292
1302
|
}
|
|
1293
1303
|
}
|
|
1294
|
-
function rtcConnectionState(connection) {
|
|
1295
|
-
return {
|
|
1296
|
-
connectionState: connection.connectionState,
|
|
1297
|
-
iceConnectionState: connection.iceConnectionState,
|
|
1298
|
-
signalingState: connection.signalingState
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
function rtcStats(report) {
|
|
1302
|
-
const entries = [];
|
|
1303
|
-
report.forEach((entry) => {
|
|
1304
|
-
if (typeof entry.type !== "string") return;
|
|
1305
|
-
entries.push({ type: entry.type, ...rtcTrafficStats(entry), ...rtcQualityStats(entry), ...rtcTransportStats(entry) });
|
|
1306
|
-
});
|
|
1307
|
-
return entries;
|
|
1308
|
-
}
|
|
1309
|
-
function rtcTrafficStats(entry) {
|
|
1310
|
-
const bytesReceived = rtcNumber(entry, "bytesReceived");
|
|
1311
|
-
const bytesSent = rtcNumber(entry, "bytesSent");
|
|
1312
|
-
const packetsReceived = rtcNumber(entry, "packetsReceived");
|
|
1313
|
-
const packetsSent = rtcNumber(entry, "packetsSent");
|
|
1314
|
-
return {
|
|
1315
|
-
...bytesReceived !== void 0 ? { bytesReceived } : {},
|
|
1316
|
-
...bytesSent !== void 0 ? { bytesSent } : {},
|
|
1317
|
-
...packetsReceived !== void 0 ? { packetsReceived } : {},
|
|
1318
|
-
...packetsSent !== void 0 ? { packetsSent } : {}
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
function rtcQualityStats(entry) {
|
|
1322
|
-
const kind = rtcString(entry, "kind");
|
|
1323
|
-
const framesDropped = rtcNumber(entry, "framesDropped");
|
|
1324
|
-
const jitter = rtcNumber(entry, "jitter");
|
|
1325
|
-
const packetsLost = rtcNumber(entry, "packetsLost");
|
|
1326
|
-
const roundTripTime = rtcNumber(entry, "roundTripTime");
|
|
1327
|
-
return {
|
|
1328
|
-
...kind !== void 0 ? { kind } : {},
|
|
1329
|
-
...framesDropped !== void 0 ? { framesDropped } : {},
|
|
1330
|
-
...jitter !== void 0 ? { jitter } : {},
|
|
1331
|
-
...packetsLost !== void 0 ? { packetsLost } : {},
|
|
1332
|
-
...roundTripTime !== void 0 ? { roundTripTime } : {}
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
function rtcTransportStats(entry) {
|
|
1336
|
-
const state = rtcString(entry, "state");
|
|
1337
|
-
const selected = rtcBoolean(entry, "selected");
|
|
1338
|
-
const nominated = rtcBoolean(entry, "nominated");
|
|
1339
|
-
const dtlsState = rtcString(entry, "dtlsState");
|
|
1340
|
-
return {
|
|
1341
|
-
...state !== void 0 ? { state } : {},
|
|
1342
|
-
...selected !== void 0 ? { selected } : {},
|
|
1343
|
-
...nominated !== void 0 ? { nominated } : {},
|
|
1344
|
-
...dtlsState !== void 0 ? { dtlsState } : {}
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
function rtcString(value, property) {
|
|
1348
|
-
const candidate = Reflect.get(value, property);
|
|
1349
|
-
return typeof candidate === "string" ? candidate : void 0;
|
|
1350
|
-
}
|
|
1351
|
-
function rtcNumber(value, property) {
|
|
1352
|
-
const candidate = Reflect.get(value, property);
|
|
1353
|
-
return typeof candidate === "number" ? candidate : void 0;
|
|
1354
|
-
}
|
|
1355
|
-
function rtcBoolean(value, property) {
|
|
1356
|
-
const candidate = Reflect.get(value, property);
|
|
1357
|
-
return typeof candidate === "boolean" ? candidate : void 0;
|
|
1358
|
-
}
|
|
1359
1304
|
function validateBootstrap(bootstrap) {
|
|
1360
1305
|
if (!bootstrap.connectionId.trim() || !bootstrap.stunServer.trim()) throw new CloudflareSFUError("Cloudflare SFU bootstrap is incomplete", "invalid_bootstrap");
|
|
1361
1306
|
}
|
|
1362
1307
|
function validateTrackSource(source, track) {
|
|
1363
|
-
const valid = source === "microphone" ? track.kind === "audio" : track.kind === "video";
|
|
1308
|
+
const valid = source === "microphone" || source === "screen_audio" ? track.kind === "audio" : track.kind === "video";
|
|
1364
1309
|
if (!valid) throw new CloudflareSFUError(`The prepared ${source} track has an incompatible kind`, "media_failed");
|
|
1365
1310
|
}
|
|
1366
1311
|
function desiredRemotePublications(publications, participantId) {
|
|
@@ -1437,8 +1382,13 @@ function remotePublicationEqual(left, right) {
|
|
|
1437
1382
|
function providerDescription(description) {
|
|
1438
1383
|
return { sessionDescription: description };
|
|
1439
1384
|
}
|
|
1385
|
+
function isRateLimited(error) {
|
|
1386
|
+
return error instanceof CloudflareSFUError && error.options.status === 429;
|
|
1387
|
+
}
|
|
1440
1388
|
function retryableRemotePull(error) {
|
|
1441
|
-
|
|
1389
|
+
if (!(error instanceof CloudflareSFUError)) return false;
|
|
1390
|
+
if (error.code === "signaling_failed") return error.options.retryableConnection === true || error.options.status === 429;
|
|
1391
|
+
return ["signaling_timeout", "media_failed", "negotiation_timeout"].includes(error.code);
|
|
1442
1392
|
}
|
|
1443
1393
|
function firefoxCameraSimulcastUnsupported() {
|
|
1444
1394
|
return typeof navigator !== "undefined" && /\bFirefox\/\d+/i.test(navigator.userAgent);
|
|
@@ -1451,13 +1401,6 @@ function mediaTargetFailure(error) {
|
|
|
1451
1401
|
const ambiguous = code === "signaling_timeout" || code === "negotiation_timeout";
|
|
1452
1402
|
return { outcome: ambiguous ? "ambiguous" : "retryable_failure", errorCode: code };
|
|
1453
1403
|
}
|
|
1454
|
-
function recoveryTrafficCount(entry, direction, unit) {
|
|
1455
|
-
const property = unit === "packet" ? direction === "inbound" ? "packetsReceived" : "packetsSent" : direction === "inbound" ? "framesDecoded" : "framesEncoded";
|
|
1456
|
-
return rtcNumber(entry, property) ?? 0;
|
|
1457
|
-
}
|
|
1458
|
-
function trafficStalled(progress, sample) {
|
|
1459
|
-
return sample.existed && !progress.stalled && sample.now - progress.lastProgress >= 1e3 && (sample.unit === "packet" || progress.count > 0);
|
|
1460
|
-
}
|
|
1461
1404
|
|
|
1462
1405
|
// src/media/transport.ts
|
|
1463
1406
|
var SIGNALING_TIMEOUT_MS = 8e3;
|
|
@@ -1476,7 +1419,7 @@ function createCloudflareSFUHTTPTransport(options) {
|
|
|
1476
1419
|
const response = await fetch(`${mediaPath}/${path}`, {
|
|
1477
1420
|
...init,
|
|
1478
1421
|
signal,
|
|
1479
|
-
headers: {
|
|
1422
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
|
|
1480
1423
|
});
|
|
1481
1424
|
if (!response.ok) {
|
|
1482
1425
|
throw new CloudflareSFUError(`Chalk SFU signaling failed with HTTP ${response.status}`, "signaling_failed", {
|
|
@@ -1553,416 +1496,6 @@ function requireCredential(options) {
|
|
|
1553
1496
|
throw new CloudflareSFUError("A media credential provider is required", "signaling_failed");
|
|
1554
1497
|
}
|
|
1555
1498
|
|
|
1556
|
-
// src/media/rtk.ts
|
|
1557
|
-
var CloudflareRTKError = class extends Error {
|
|
1558
|
-
code;
|
|
1559
|
-
constructor(message, code) {
|
|
1560
|
-
super(message);
|
|
1561
|
-
this.name = "CloudflareRTKError";
|
|
1562
|
-
this.code = code;
|
|
1563
|
-
}
|
|
1564
|
-
};
|
|
1565
|
-
var EMPTY_LOCAL2 = Object.freeze([]);
|
|
1566
|
-
var EMPTY_REMOTE2 = Object.freeze([]);
|
|
1567
|
-
var CloudflareRTKClient = class {
|
|
1568
|
-
#participantId;
|
|
1569
|
-
#onError;
|
|
1570
|
-
#onScreenEnded;
|
|
1571
|
-
#clientFactory;
|
|
1572
|
-
#snapshotListeners = /* @__PURE__ */ new Set();
|
|
1573
|
-
#sourceOperations = /* @__PURE__ */ new Map();
|
|
1574
|
-
#connection = null;
|
|
1575
|
-
#localTracks = /* @__PURE__ */ new Map();
|
|
1576
|
-
#connectionUnsubscribers = [];
|
|
1577
|
-
#participantUnsubscribers = /* @__PURE__ */ new Map();
|
|
1578
|
-
#snapshot;
|
|
1579
|
-
#stopped = false;
|
|
1580
|
-
#screenDisableRequested = false;
|
|
1581
|
-
#initialAuthToken;
|
|
1582
|
-
constructor(options) {
|
|
1583
|
-
if (!options.authToken.trim()) throw new CloudflareRTKError("A RealtimeKit auth token is required", "invalid_client");
|
|
1584
|
-
if (!options.participantId.trim()) throw new CloudflareRTKError("A participant ID is required", "invalid_participant");
|
|
1585
|
-
this.#participantId = options.participantId;
|
|
1586
|
-
this.#onError = options.onError;
|
|
1587
|
-
this.#onScreenEnded = options.onScreenEnded;
|
|
1588
|
-
this.#clientFactory = options.clientFactory ?? defaultClientFactory;
|
|
1589
|
-
this.#snapshot = freezeSnapshot2({
|
|
1590
|
-
connection: { phase: "idle", peerConnectionState: null, iceConnectionState: null },
|
|
1591
|
-
cursor: null,
|
|
1592
|
-
localTracks: EMPTY_LOCAL2,
|
|
1593
|
-
remoteTracks: EMPTY_REMOTE2,
|
|
1594
|
-
failure: null
|
|
1595
|
-
});
|
|
1596
|
-
this.#initialAuthToken = options.authToken;
|
|
1597
|
-
}
|
|
1598
|
-
getSnapshot() {
|
|
1599
|
-
return this.#snapshot;
|
|
1600
|
-
}
|
|
1601
|
-
subscribe(listener) {
|
|
1602
|
-
return subscribeSnapshot(this.#snapshotListeners, listener);
|
|
1603
|
-
}
|
|
1604
|
-
prepareLocalTrack(source, track) {
|
|
1605
|
-
this.#requireStartable();
|
|
1606
|
-
validateTrack(source, track);
|
|
1607
|
-
if (this.#localTracks.has(source)) throw new CloudflareRTKError(`A ${source} track is already prepared`, "media_failed");
|
|
1608
|
-
const endedListener = source === "screen" ? () => this.#onScreenEnded?.() : null;
|
|
1609
|
-
if (endedListener) track.addEventListener("ended", endedListener);
|
|
1610
|
-
this.#localTracks.set(source, { source, track, enabled: false, endedListener });
|
|
1611
|
-
this.#publishSnapshot();
|
|
1612
|
-
}
|
|
1613
|
-
async clearPreparedLocalTrack(source) {
|
|
1614
|
-
const state = this.#localTracks.get(source);
|
|
1615
|
-
if (!state) return;
|
|
1616
|
-
if (state.enabled) {
|
|
1617
|
-
await this.#setSourceEnabled(state, false);
|
|
1618
|
-
}
|
|
1619
|
-
if (state.endedListener) state.track.removeEventListener("ended", state.endedListener);
|
|
1620
|
-
this.#localTracks.delete(source);
|
|
1621
|
-
this.#publishSnapshot();
|
|
1622
|
-
}
|
|
1623
|
-
async start(localMedia) {
|
|
1624
|
-
if (this.#snapshot.connection.phase === "live") return;
|
|
1625
|
-
this.#requireStartable();
|
|
1626
|
-
this.#setPhase("connecting", null);
|
|
1627
|
-
try {
|
|
1628
|
-
const tracks = localMedia.getTracks().filter((track) => track.kind === "audio" || track.kind === "video");
|
|
1629
|
-
for (const track of tracks) {
|
|
1630
|
-
const source = track.kind === "audio" ? "microphone" : "camera";
|
|
1631
|
-
if (!this.#localTracks.has(source)) this.#localTracks.set(source, { source, track, enabled: false, endedListener: null });
|
|
1632
|
-
}
|
|
1633
|
-
await this.#openConnection(this.#initialAuthToken);
|
|
1634
|
-
for (const state of this.#localTracks.values()) {
|
|
1635
|
-
if (state.source === "microphone" || state.source === "camera") await this.#setSourceEnabled(state, true);
|
|
1636
|
-
}
|
|
1637
|
-
this.#setPhase("live", null);
|
|
1638
|
-
} catch (error) {
|
|
1639
|
-
this.#setFailure(error);
|
|
1640
|
-
this.#reportError(error);
|
|
1641
|
-
throw error;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
async setLocalPublicationTarget(target) {
|
|
1645
|
-
const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
|
|
1646
|
-
if (resolved.kind === "result") return resolved.result;
|
|
1647
|
-
const state = resolved.value;
|
|
1648
|
-
const previous = this.#sourceOperations.get(target.source) ?? Promise.resolve();
|
|
1649
|
-
const operation = previous.catch(() => void 0).then(async () => {
|
|
1650
|
-
if (state.enabled === target.enabled) return "satisfied";
|
|
1651
|
-
try {
|
|
1652
|
-
await this.#setSourceEnabled(state, target.enabled);
|
|
1653
|
-
return "confirmed";
|
|
1654
|
-
} catch (error) {
|
|
1655
|
-
this.#reportError(error);
|
|
1656
|
-
return "retryable_failure";
|
|
1657
|
-
}
|
|
1658
|
-
});
|
|
1659
|
-
this.#sourceOperations.set(target.source, operation);
|
|
1660
|
-
try {
|
|
1661
|
-
const outcome = await operation;
|
|
1662
|
-
return { outcome, errorCode: outcome === "retryable_failure" ? "media_failed" : null };
|
|
1663
|
-
} finally {
|
|
1664
|
-
if (this.#sourceOperations.get(target.source) === operation) this.#sourceOperations.delete(target.source);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
observeLocalPublications(listener) {
|
|
1668
|
-
return observePublications(this.#snapshotListeners, listener, () => this.#projectLocalPublications());
|
|
1669
|
-
}
|
|
1670
|
-
observeRemotePublications(listener) {
|
|
1671
|
-
return observePublications(this.#snapshotListeners, listener, () => this.#projectRemotePublications());
|
|
1672
|
-
}
|
|
1673
|
-
async restart(input) {
|
|
1674
|
-
if (!isRTKAccess(input)) throw new CloudflareRTKError("The RealtimeKit adapter requires a Cloudflare RealtimeKit access grant", "invalid_client");
|
|
1675
|
-
if (this.#stopped) throw new CloudflareRTKError("The RealtimeKit media client has stopped", "media_stopped");
|
|
1676
|
-
this.#setPhase("recovering", null);
|
|
1677
|
-
try {
|
|
1678
|
-
await this.#closeConnection();
|
|
1679
|
-
await this.#openConnection(input.clientPayload.token);
|
|
1680
|
-
for (const state of this.#localTracks.values()) if (state.enabled) await this.#setSourceEnabled(state, true);
|
|
1681
|
-
this.#setPhase("live", null);
|
|
1682
|
-
} catch (error) {
|
|
1683
|
-
this.#setFailure(error);
|
|
1684
|
-
this.#reportError(error);
|
|
1685
|
-
throw error;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
stop() {
|
|
1689
|
-
if (this.#stopped) return;
|
|
1690
|
-
this.#stopped = true;
|
|
1691
|
-
const leave = this.#closeConnection();
|
|
1692
|
-
void leave.catch((error) => this.#reportError(error));
|
|
1693
|
-
for (const state of this.#localTracks.values()) if (state.endedListener) state.track.removeEventListener("ended", state.endedListener);
|
|
1694
|
-
this.#localTracks.clear();
|
|
1695
|
-
this.#setPhase("stopped", null);
|
|
1696
|
-
}
|
|
1697
|
-
async #openConnection(authToken) {
|
|
1698
|
-
this.#connection = await this.#clientFactory({ authToken, onError: (error) => this.#reportError(error) });
|
|
1699
|
-
this.#bindConnection(this.#connection);
|
|
1700
|
-
await this.#connection.join();
|
|
1701
|
-
}
|
|
1702
|
-
async #closeConnection() {
|
|
1703
|
-
const connection = this.#connection;
|
|
1704
|
-
this.#connection = null;
|
|
1705
|
-
this.#unbindConnection();
|
|
1706
|
-
if (connection) await connection.leave();
|
|
1707
|
-
}
|
|
1708
|
-
#bindConnection(connection) {
|
|
1709
|
-
const selfAudio = connection.self.onAudioUpdate(() => this.#syncLocalState("microphone"));
|
|
1710
|
-
const selfVideo = connection.self.onVideoUpdate(() => this.#syncLocalState("camera"));
|
|
1711
|
-
const selfScreen = connection.self.onScreenShareUpdate((payload) => {
|
|
1712
|
-
this.#syncLocalState("screen");
|
|
1713
|
-
if (!payload.screenShareEnabled && !this.#screenDisableRequested) this.#onScreenEnded?.();
|
|
1714
|
-
this.#screenDisableRequested = false;
|
|
1715
|
-
});
|
|
1716
|
-
const connectionLeft = connection.self.onLeft(() => {
|
|
1717
|
-
if (this.#stopped) return;
|
|
1718
|
-
const error = new CloudflareRTKError("The RealtimeKit connection ended unexpectedly", "media_failed");
|
|
1719
|
-
this.#setFailure(error);
|
|
1720
|
-
this.#reportError(error);
|
|
1721
|
-
});
|
|
1722
|
-
for (const participant of connection.participants.joined.list()) this.#bindParticipant(participant);
|
|
1723
|
-
const participantJoined = connection.participants.joined.onJoined((participant) => {
|
|
1724
|
-
this.#bindParticipant(participant);
|
|
1725
|
-
this.#publishSnapshot();
|
|
1726
|
-
});
|
|
1727
|
-
const participantLeft = connection.participants.joined.onLeft((participant) => {
|
|
1728
|
-
this.#unbindParticipant(participant.id);
|
|
1729
|
-
this.#publishSnapshot();
|
|
1730
|
-
});
|
|
1731
|
-
this.#connectionUnsubscribers = [selfAudio, selfVideo, selfScreen, connectionLeft, participantJoined, participantLeft];
|
|
1732
|
-
}
|
|
1733
|
-
#unbindConnection() {
|
|
1734
|
-
for (const unsubscribe of this.#connectionUnsubscribers) unsubscribe();
|
|
1735
|
-
this.#connectionUnsubscribers = [];
|
|
1736
|
-
for (const unsubscribers of this.#participantUnsubscribers.values()) for (const unsubscribe of unsubscribers) unsubscribe();
|
|
1737
|
-
this.#participantUnsubscribers.clear();
|
|
1738
|
-
}
|
|
1739
|
-
#bindParticipant(participant) {
|
|
1740
|
-
if (this.#participantUnsubscribers.has(participant.id)) return;
|
|
1741
|
-
const unsubscribers = [participant.onAudioUpdate?.(() => this.#publishSnapshot()), participant.onVideoUpdate?.(() => this.#publishSnapshot()), participant.onScreenShareUpdate?.(() => this.#publishSnapshot())].filter((unsubscribe) => unsubscribe !== void 0);
|
|
1742
|
-
this.#participantUnsubscribers.set(participant.id, unsubscribers);
|
|
1743
|
-
}
|
|
1744
|
-
#unbindParticipant(participantId) {
|
|
1745
|
-
const unsubscribers = this.#participantUnsubscribers.get(participantId);
|
|
1746
|
-
if (!unsubscribers) return;
|
|
1747
|
-
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
1748
|
-
this.#participantUnsubscribers.delete(participantId);
|
|
1749
|
-
}
|
|
1750
|
-
async #setSourceEnabled(state, enabled) {
|
|
1751
|
-
const connection = this.#connection;
|
|
1752
|
-
if (!connection) throw new CloudflareRTKError("The RealtimeKit connection is not active", "media_stopped");
|
|
1753
|
-
if (state.source === "microphone") {
|
|
1754
|
-
if (enabled) await connection.self.enableAudio(state.track);
|
|
1755
|
-
else await connection.self.disableAudio();
|
|
1756
|
-
} else if (state.source === "camera") {
|
|
1757
|
-
if (enabled) await connection.self.enableVideo(state.track);
|
|
1758
|
-
else await connection.self.disableVideo();
|
|
1759
|
-
} else {
|
|
1760
|
-
this.#screenDisableRequested = !enabled;
|
|
1761
|
-
if (enabled) await connection.self.enableScreenShare();
|
|
1762
|
-
else await connection.self.disableScreenShare();
|
|
1763
|
-
}
|
|
1764
|
-
state.enabled = enabled;
|
|
1765
|
-
state.track.enabled = enabled;
|
|
1766
|
-
this.#publishSnapshot();
|
|
1767
|
-
}
|
|
1768
|
-
#syncLocalState(source) {
|
|
1769
|
-
const state = this.#localTracks.get(source);
|
|
1770
|
-
const connection = this.#connection;
|
|
1771
|
-
if (!state || !connection) return;
|
|
1772
|
-
state.enabled = source === "microphone" ? connection.self.audioEnabled : source === "camera" ? connection.self.videoEnabled : connection.self.screenShareEnabled;
|
|
1773
|
-
state.track.enabled = state.enabled;
|
|
1774
|
-
this.#publishSnapshot();
|
|
1775
|
-
}
|
|
1776
|
-
#publishSnapshot() {
|
|
1777
|
-
this.#snapshot = freezeSnapshot2({ ...this.#snapshot, localTracks: this.#projectLocalTracks(), remoteTracks: this.#projectRemoteTracks() });
|
|
1778
|
-
this.#notifyListeners();
|
|
1779
|
-
}
|
|
1780
|
-
#projectLocalTracks() {
|
|
1781
|
-
return Object.freeze([...this.#localTracks.values()].map((state) => ({ source: state.source, enabled: state.enabled, publicationId: `${this.#participantId}:${state.source}`, track: state.track })));
|
|
1782
|
-
}
|
|
1783
|
-
#projectLocalPublications() {
|
|
1784
|
-
return Object.freeze(this.#projectLocalTracks().map((track) => ({ participantId: this.#participantId, source: track.source, enabled: track.enabled, publicationId: track.publicationId })));
|
|
1785
|
-
}
|
|
1786
|
-
#projectRemotePublications() {
|
|
1787
|
-
return Object.freeze(this.#projectRemoteTracks().map((track) => ({ participantId: track.participantId, source: track.source, enabled: true, publicationId: track.publicationId })));
|
|
1788
|
-
}
|
|
1789
|
-
#projectRemoteTracks() {
|
|
1790
|
-
return projectRemoteTracks(this.#connection?.participants.joined.list() ?? []);
|
|
1791
|
-
}
|
|
1792
|
-
#setPhase(phase, failure) {
|
|
1793
|
-
this.#snapshot = freezeSnapshot2({ ...this.#snapshot, connection: { phase, peerConnectionState: null, iceConnectionState: null }, failure });
|
|
1794
|
-
this.#notifyListeners();
|
|
1795
|
-
}
|
|
1796
|
-
#setFailure(error) {
|
|
1797
|
-
this.#setPhase("failed", { code: "media_failed", recoverable: true });
|
|
1798
|
-
if (!(error instanceof CloudflareRTKError)) this.#reportError(error);
|
|
1799
|
-
}
|
|
1800
|
-
#requireStartable() {
|
|
1801
|
-
if (this.#stopped) throw new CloudflareRTKError("The RealtimeKit media client has stopped", "media_stopped");
|
|
1802
|
-
}
|
|
1803
|
-
#reportError(error) {
|
|
1804
|
-
try {
|
|
1805
|
-
this.#onError?.(error);
|
|
1806
|
-
} catch {
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
#notifyListeners() {
|
|
1810
|
-
for (const listener of this.#snapshotListeners) {
|
|
1811
|
-
try {
|
|
1812
|
-
listener();
|
|
1813
|
-
} catch (error) {
|
|
1814
|
-
this.#reportError(error);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
};
|
|
1819
|
-
function projectRemoteTracks(participants) {
|
|
1820
|
-
return Object.freeze(participants.flatMap(remoteTracksForParticipant));
|
|
1821
|
-
}
|
|
1822
|
-
function remoteTracksForParticipant(participant) {
|
|
1823
|
-
const participantId = participantIdentifier(participant);
|
|
1824
|
-
if (!participantId) return [];
|
|
1825
|
-
const publications = [];
|
|
1826
|
-
addRemotePublication(publications, participant, participantId, "microphone", participant.audioEnabled ? participant.audioTrack : null);
|
|
1827
|
-
addRemotePublication(publications, participant, participantId, "camera", participant.videoEnabled ? participant.videoTrack : null);
|
|
1828
|
-
addRemotePublication(publications, participant, participantId, "screen", participant.screenShareEnabled ? participant.screenShareTracks.video ?? null : null);
|
|
1829
|
-
return publications;
|
|
1830
|
-
}
|
|
1831
|
-
function participantIdentifier(participant) {
|
|
1832
|
-
return participant.customParticipantId?.trim() || participant.userId.trim() || participant.id.trim();
|
|
1833
|
-
}
|
|
1834
|
-
function addRemotePublication(publications, participant, participantId, source, track) {
|
|
1835
|
-
if (!track) return;
|
|
1836
|
-
publications.push({ participantId, source, publicationId: `${participant.id}:${source}`, track });
|
|
1837
|
-
}
|
|
1838
|
-
function validateTrack(source, track) {
|
|
1839
|
-
const expectedKind = source === "microphone" ? "audio" : "video";
|
|
1840
|
-
if (track.kind !== expectedKind) throw new CloudflareRTKError(`A ${source} track must be ${expectedKind}`, "invalid_target");
|
|
1841
|
-
}
|
|
1842
|
-
function isRTKAccess(value) {
|
|
1843
|
-
return value.provider === "cloudflare_rtk";
|
|
1844
|
-
}
|
|
1845
|
-
function freezeSnapshot2(snapshot) {
|
|
1846
|
-
const failure = snapshot.failure ? Object.freeze(snapshot.failure) : null;
|
|
1847
|
-
return Object.freeze({
|
|
1848
|
-
...snapshot,
|
|
1849
|
-
connection: Object.freeze(snapshot.connection),
|
|
1850
|
-
localTracks: Object.freeze(snapshot.localTracks.map((track) => Object.freeze(track))),
|
|
1851
|
-
remoteTracks: Object.freeze(snapshot.remoteTracks.map((track) => Object.freeze(track))),
|
|
1852
|
-
failure
|
|
1853
|
-
});
|
|
1854
|
-
}
|
|
1855
|
-
async function defaultClientFactory(input) {
|
|
1856
|
-
const { default: RealtimeKitClient } = await import("@cloudflare/realtimekit");
|
|
1857
|
-
const client = await RealtimeKitClient.init({
|
|
1858
|
-
authToken: input.authToken,
|
|
1859
|
-
defaults: { audio: false, video: false },
|
|
1860
|
-
onError: input.onError
|
|
1861
|
-
});
|
|
1862
|
-
return adaptRealtimeKitClient(client);
|
|
1863
|
-
}
|
|
1864
|
-
function adaptRealtimeKitClient(client) {
|
|
1865
|
-
const self = client.self;
|
|
1866
|
-
return {
|
|
1867
|
-
join: () => client.join(),
|
|
1868
|
-
leave: () => client.leave(),
|
|
1869
|
-
self: {
|
|
1870
|
-
get peerId() {
|
|
1871
|
-
return self.peerId;
|
|
1872
|
-
},
|
|
1873
|
-
get audioEnabled() {
|
|
1874
|
-
return self.audioEnabled;
|
|
1875
|
-
},
|
|
1876
|
-
get videoEnabled() {
|
|
1877
|
-
return self.videoEnabled;
|
|
1878
|
-
},
|
|
1879
|
-
get screenShareEnabled() {
|
|
1880
|
-
return self.screenShareEnabled;
|
|
1881
|
-
},
|
|
1882
|
-
get audioTrack() {
|
|
1883
|
-
return self.audioTrack ?? null;
|
|
1884
|
-
},
|
|
1885
|
-
get videoTrack() {
|
|
1886
|
-
return self.videoTrack ?? null;
|
|
1887
|
-
},
|
|
1888
|
-
get screenShareTracks() {
|
|
1889
|
-
return {
|
|
1890
|
-
...self.screenShareTracks.audio ? { audio: self.screenShareTracks.audio } : {},
|
|
1891
|
-
...self.screenShareTracks.video ? { video: self.screenShareTracks.video } : {}
|
|
1892
|
-
};
|
|
1893
|
-
},
|
|
1894
|
-
enableAudio: (customTrack) => self.enableAudio(customTrack),
|
|
1895
|
-
enableVideo: (customTrack) => self.enableVideo(customTrack),
|
|
1896
|
-
enableScreenShare: () => self.enableScreenShare(),
|
|
1897
|
-
disableAudio: () => self.disableAudio(),
|
|
1898
|
-
disableVideo: () => self.disableVideo(),
|
|
1899
|
-
disableScreenShare: () => self.disableScreenShare(),
|
|
1900
|
-
onAudioUpdate: (listener) => {
|
|
1901
|
-
self.on("audioUpdate", listener);
|
|
1902
|
-
return () => self.off("audioUpdate", listener);
|
|
1903
|
-
},
|
|
1904
|
-
onVideoUpdate: (listener) => {
|
|
1905
|
-
self.on("videoUpdate", listener);
|
|
1906
|
-
return () => self.off("videoUpdate", listener);
|
|
1907
|
-
},
|
|
1908
|
-
onScreenShareUpdate: (listener) => {
|
|
1909
|
-
self.on("screenShareUpdate", listener);
|
|
1910
|
-
return () => self.off("screenShareUpdate", listener);
|
|
1911
|
-
},
|
|
1912
|
-
onLeft: (listener) => {
|
|
1913
|
-
self.on("roomLeft", listener);
|
|
1914
|
-
return () => self.off("roomLeft", listener);
|
|
1915
|
-
}
|
|
1916
|
-
},
|
|
1917
|
-
participants: {
|
|
1918
|
-
joined: {
|
|
1919
|
-
list: () => client.participants.joined.toArray().map(toParticipant),
|
|
1920
|
-
onJoined: (listener) => {
|
|
1921
|
-
const callback = (participant) => listener(toParticipant(participant));
|
|
1922
|
-
client.participants.joined.on("participantJoined", callback);
|
|
1923
|
-
return () => client.participants.joined.off("participantJoined", callback);
|
|
1924
|
-
},
|
|
1925
|
-
onLeft: (listener) => {
|
|
1926
|
-
const callback = (participant) => listener(toParticipant(participant));
|
|
1927
|
-
client.participants.joined.on("participantLeft", callback);
|
|
1928
|
-
return () => client.participants.joined.off("participantLeft", callback);
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
|
-
function toParticipant(participant) {
|
|
1935
|
-
return {
|
|
1936
|
-
id: participant.id,
|
|
1937
|
-
userId: participant.userId,
|
|
1938
|
-
...participant.customParticipantId === void 0 ? {} : { customParticipantId: participant.customParticipantId },
|
|
1939
|
-
audioEnabled: participant.audioEnabled,
|
|
1940
|
-
videoEnabled: participant.videoEnabled,
|
|
1941
|
-
screenShareEnabled: participant.screenShareEnabled,
|
|
1942
|
-
audioTrack: participant.audioTrack ?? null,
|
|
1943
|
-
videoTrack: participant.videoTrack ?? null,
|
|
1944
|
-
screenShareTracks: {
|
|
1945
|
-
...participant.screenShareTracks.audio ? { audio: participant.screenShareTracks.audio } : {},
|
|
1946
|
-
...participant.screenShareTracks.video ? { video: participant.screenShareTracks.video } : {}
|
|
1947
|
-
},
|
|
1948
|
-
onAudioUpdate: (listener) => {
|
|
1949
|
-
const callback = () => listener();
|
|
1950
|
-
participant.on("audioUpdate", callback);
|
|
1951
|
-
return () => participant.off("audioUpdate", callback);
|
|
1952
|
-
},
|
|
1953
|
-
onVideoUpdate: (listener) => {
|
|
1954
|
-
const callback = () => listener();
|
|
1955
|
-
participant.on("videoUpdate", callback);
|
|
1956
|
-
return () => participant.off("videoUpdate", callback);
|
|
1957
|
-
},
|
|
1958
|
-
onScreenShareUpdate: (listener) => {
|
|
1959
|
-
const callback = () => listener();
|
|
1960
|
-
participant.on("screenShareUpdate", callback);
|
|
1961
|
-
return () => participant.off("screenShareUpdate", callback);
|
|
1962
|
-
}
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
1499
|
// src/media/capture-error.ts
|
|
1967
1500
|
function describeMediaCaptureError(cause) {
|
|
1968
1501
|
const name = typeof cause === "object" && cause !== null && "name" in cause ? cause.name : void 0;
|
|
@@ -1988,6 +1521,7 @@ function describeMediaCaptureError(cause) {
|
|
|
1988
1521
|
}
|
|
1989
1522
|
|
|
1990
1523
|
export {
|
|
1524
|
+
describeMediaCaptureError,
|
|
1991
1525
|
CloudflareSFUError,
|
|
1992
1526
|
parseCloudflareSFUPublicationID,
|
|
1993
1527
|
requireDescription,
|
|
@@ -1997,8 +1531,5 @@ export {
|
|
|
1997
1531
|
publicationKey,
|
|
1998
1532
|
waitFor,
|
|
1999
1533
|
CloudflareSFUClient,
|
|
2000
|
-
createCloudflareSFUHTTPTransport
|
|
2001
|
-
CloudflareRTKError,
|
|
2002
|
-
CloudflareRTKClient,
|
|
2003
|
-
describeMediaCaptureError
|
|
1534
|
+
createCloudflareSFUHTTPTransport
|
|
2004
1535
|
};
|