@q9labsai/chalk-client 4.1.17 → 5.0.1
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 +60 -42
- 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-ZRMGKMX4.js → chunk-2HJIJVE2.js} +552 -3651
- package/dist/{chunk-CZUJNAUI.js → chunk-AVXJ4HQU.js} +142 -49
- package/dist/{chunk-A7BVZEFM.js → chunk-CMVO7USN.js} +2 -8
- package/dist/{chunk-AUQTQOLR.js → chunk-IPAN4LUZ.js} +627 -2434
- package/dist/{chunk-2DZUHV6I.js → chunk-O2NOZKJP.js} +129 -721
- package/dist/client.d.ts +2559 -5303
- 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 +7 -13
- package/dist/connection/dependencies.d.ts.map +1 -1
- package/dist/connection/index.d.ts +1 -6
- package/dist/connection/index.d.ts.map +1 -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 +53 -776
- package/dist/generated/http-api.d.ts +2603 -5293
- package/dist/generated/http-api.d.ts.map +1 -1
- package/dist/generated/schemas.d.ts +1893 -6870
- 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 +10 -119
- 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/client.d.ts.map +1 -1
- package/dist/server/index.cjs +12 -112
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -109
- package/dist/server/types.d.cts +0 -7
- package/dist/server/types.d.ts +0 -7
- 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.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 +33 -12
- package/dist/space-client/terminal.test.helpers.d.ts.map +1 -1
- package/dist/space-client/types.d.ts +15 -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 +4 -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/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
|
-
recordReconnect,
|
|
3
|
-
traceReconnect
|
|
4
|
-
} from "./chunk-RCDXIDCN.js";
|
|
5
|
-
import {
|
|
6
|
-
journeyHeaders
|
|
7
|
-
} from "./chunk-HW5TTZL2.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,9 +207,13 @@ 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;
|
|
233
219
|
#pollFailures = 0;
|
|
@@ -247,8 +233,6 @@ var CloudflareSFUClient = class {
|
|
|
247
233
|
this.#replaceMediaConnection = options.replaceMediaConnection;
|
|
248
234
|
this.#onError = options.onError;
|
|
249
235
|
this.#onRemoteTrack = options.onRemoteTrack;
|
|
250
|
-
this.#onRtcSummary = options.onRtcSummary;
|
|
251
|
-
this.#recordReconnect = options.recordReconnect;
|
|
252
236
|
this.#onScreenEnded = options.onScreenEnded;
|
|
253
237
|
this.#peerConnectionFactory = options.peerConnectionFactory;
|
|
254
238
|
this.#connection = this.#createPeerConnection(options.bootstrap);
|
|
@@ -271,8 +255,13 @@ var CloudflareSFUClient = class {
|
|
|
271
255
|
this.#remotePullRetryAfter.delete(publicationId);
|
|
272
256
|
this.remotePublicationsChanged();
|
|
273
257
|
}
|
|
258
|
+
remoteParticipantsChanged(participantIds) {
|
|
259
|
+
this.#remoteParticipantIds = new Set(participantIds);
|
|
260
|
+
this.#remoteParticipantRevision++;
|
|
261
|
+
this.#remotePullIncomplete = true;
|
|
262
|
+
this.remotePublicationsChanged();
|
|
263
|
+
}
|
|
274
264
|
remotePublicationsChanged() {
|
|
275
|
-
recordReconnect(this.#recordReconnect, "remote_publications_changed");
|
|
276
265
|
if (this.#polling) {
|
|
277
266
|
this.#pollAfterCurrent = true;
|
|
278
267
|
return;
|
|
@@ -310,6 +299,17 @@ var CloudflareSFUClient = class {
|
|
|
310
299
|
async clearPreparedLocalTrack(source) {
|
|
311
300
|
const state = this.#localTracks.get(source);
|
|
312
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
|
+
}
|
|
313
313
|
if (state.enabled) await this.#setPreparedTrackEnabled(state, false);
|
|
314
314
|
if (state.transceiver) this.#reusableLocalTransceivers.set(source, state.transceiver);
|
|
315
315
|
if (state.providerPublicationId) this.#reusableLocalPublicationIds.set(source, state.providerPublicationId);
|
|
@@ -326,7 +326,7 @@ var CloudflareSFUClient = class {
|
|
|
326
326
|
const generation = this.#generation;
|
|
327
327
|
this.#setPhase("connecting", null);
|
|
328
328
|
await this.#activatePreparedTracks(
|
|
329
|
-
[...this.#localTracks.values()].filter((state) => state.source
|
|
329
|
+
[...this.#localTracks.values()].filter((state) => state.source === "microphone" || state.source === "camera"),
|
|
330
330
|
generation
|
|
331
331
|
);
|
|
332
332
|
}
|
|
@@ -337,7 +337,7 @@ var CloudflareSFUClient = class {
|
|
|
337
337
|
this.#pollFailedPull = false;
|
|
338
338
|
try {
|
|
339
339
|
const transport = this.#requireTransport();
|
|
340
|
-
const authoritative = await
|
|
340
|
+
const authoritative = await (() => transport.listPublications())();
|
|
341
341
|
this.#requireGeneration(generation);
|
|
342
342
|
await this.#reconcileRemotePublications(authoritative, generation);
|
|
343
343
|
this.#requireGeneration(generation);
|
|
@@ -358,6 +358,11 @@ var CloudflareSFUClient = class {
|
|
|
358
358
|
this.#schedulePoll(0);
|
|
359
359
|
}
|
|
360
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) {
|
|
361
366
|
const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
|
|
362
367
|
if (resolved.kind === "result") return resolved.result;
|
|
363
368
|
const state = resolved.value;
|
|
@@ -377,6 +382,7 @@ var CloudflareSFUClient = class {
|
|
|
377
382
|
return target.enabled && this.#locallyPausedSources.has(target.source);
|
|
378
383
|
}
|
|
379
384
|
setLocalSourceIntent(source, enabled) {
|
|
385
|
+
if (source === "screen") this.setLocalSourceIntent("screen_audio", enabled);
|
|
380
386
|
if (enabled) {
|
|
381
387
|
this.#locallyPausedSources.delete(source);
|
|
382
388
|
return;
|
|
@@ -390,6 +396,7 @@ var CloudflareSFUClient = class {
|
|
|
390
396
|
async closeForcedLocalPublication(source) {
|
|
391
397
|
this.setLocalSourceIntent(source, false);
|
|
392
398
|
await this.#retireLocalPublication(source);
|
|
399
|
+
if (source === "screen") await this.#retireLocalPublication("screen_audio");
|
|
393
400
|
if (!this.#replaceMediaConnection) return;
|
|
394
401
|
const generation = this.#generation;
|
|
395
402
|
await this.#serializeSDP(async () => {
|
|
@@ -408,7 +415,7 @@ var CloudflareSFUClient = class {
|
|
|
408
415
|
async #retireUnavailableLocalPublication(state) {
|
|
409
416
|
const publicationId = state.providerPublicationId;
|
|
410
417
|
if (!publicationId) return;
|
|
411
|
-
const authoritative = await
|
|
418
|
+
const authoritative = await (() => this.#requireTransport().listPublications())();
|
|
412
419
|
if (authoritative.publications.some((publication) => publication.publicationId === publicationId)) return;
|
|
413
420
|
if (state.providerPublicationId === publicationId) await this.#retireLocalPublication(state.source);
|
|
414
421
|
}
|
|
@@ -436,8 +443,8 @@ var CloudflareSFUClient = class {
|
|
|
436
443
|
const mid = requireTransceiverMid(transceiver);
|
|
437
444
|
await this.#boundPeerOperation(transceiver.sender.replaceTrack(null));
|
|
438
445
|
transceiver.stop();
|
|
439
|
-
const offer = await
|
|
440
|
-
await
|
|
446
|
+
const offer = await (() => connection.createOffer())();
|
|
447
|
+
await (() => connection.setLocalDescription(offer))();
|
|
441
448
|
try {
|
|
442
449
|
const response = await this.#requireTransport().closeTracks({
|
|
443
450
|
connectionId,
|
|
@@ -467,7 +474,6 @@ var CloudflareSFUClient = class {
|
|
|
467
474
|
this.#requireActive();
|
|
468
475
|
const options = "connectionId" in input ? { bootstrap: input } : input;
|
|
469
476
|
validateBootstrap(options.bootstrap);
|
|
470
|
-
recordReconnect(this.#recordReconnect, "media_rebuild_start");
|
|
471
477
|
const generation = ++this.#generation;
|
|
472
478
|
const connectionEpoch = ++this.#connectionEpoch;
|
|
473
479
|
this.#polling = false;
|
|
@@ -496,6 +502,7 @@ var CloudflareSFUClient = class {
|
|
|
496
502
|
try {
|
|
497
503
|
await this.#publishPreparedTracks(states, generation);
|
|
498
504
|
this.#requireGeneration(generation);
|
|
505
|
+
if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
|
|
499
506
|
this.#started = true;
|
|
500
507
|
this.#setPhase("live", null);
|
|
501
508
|
this.#schedulePoll(0);
|
|
@@ -507,6 +514,7 @@ var CloudflareSFUClient = class {
|
|
|
507
514
|
stop() {
|
|
508
515
|
if (this.#stopped) return;
|
|
509
516
|
this.#stopped = true;
|
|
517
|
+
this.#subscribedPublicationIds.clear();
|
|
510
518
|
this.#generation++;
|
|
511
519
|
this.#connectionEpoch++;
|
|
512
520
|
this.#clearPoll();
|
|
@@ -561,6 +569,7 @@ var CloudflareSFUClient = class {
|
|
|
561
569
|
const generation = this.#generation;
|
|
562
570
|
try {
|
|
563
571
|
await this.#publishPreparedTracks([state], generation);
|
|
572
|
+
if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
|
|
564
573
|
} catch (error) {
|
|
565
574
|
state.desiredEnabled = false;
|
|
566
575
|
state.enabled = false;
|
|
@@ -584,7 +593,6 @@ var CloudflareSFUClient = class {
|
|
|
584
593
|
if (states.length === 0) return;
|
|
585
594
|
await this.#serializeSDP(async () => {
|
|
586
595
|
const pending = await this.#prepareConnectionForPublication(states, generation);
|
|
587
|
-
this.#replacementAttemptedGeneration = null;
|
|
588
596
|
try {
|
|
589
597
|
await this.#publishPreparedTracksSerialized(pending, generation, this.#connection, this.#bootstrap.connectionId);
|
|
590
598
|
} catch (error) {
|
|
@@ -604,7 +612,7 @@ var CloudflareSFUClient = class {
|
|
|
604
612
|
await this.#replaceDormantConnectionBeforeNegotiation(generation);
|
|
605
613
|
return states;
|
|
606
614
|
}
|
|
607
|
-
const authoritative = await
|
|
615
|
+
const authoritative = await (() => this.#requireTransport().listPublications())();
|
|
608
616
|
this.#pauseUnavailableOtherSources(authoritative.publications, states);
|
|
609
617
|
this.#replacementAttemptedGeneration = null;
|
|
610
618
|
await this.#replaceMediaConnectionForRecovery(generation);
|
|
@@ -668,8 +676,8 @@ var CloudflareSFUClient = class {
|
|
|
668
676
|
async #negotiateLocalPublications(connection, connectionId, publications, generation) {
|
|
669
677
|
let offer;
|
|
670
678
|
try {
|
|
671
|
-
offer = await
|
|
672
|
-
await
|
|
679
|
+
offer = await (() => connection.createOffer())();
|
|
680
|
+
await (() => connection.setLocalDescription(offer))();
|
|
673
681
|
} catch (error) {
|
|
674
682
|
const cameras = publications.filter(({ state, transceiver }) => state.source === "camera" && transceiver.sender.getParameters().encodings.length > 1);
|
|
675
683
|
if (!simulcastUnsupported(error) || cameras.length === 0) throw error;
|
|
@@ -681,8 +689,8 @@ var CloudflareSFUClient = class {
|
|
|
681
689
|
publication.transceiver = connection.addTransceiver(publication.state.track, { direction: "sendonly" });
|
|
682
690
|
publication.state.transceiver = publication.transceiver;
|
|
683
691
|
}
|
|
684
|
-
offer = await
|
|
685
|
-
await
|
|
692
|
+
offer = await (() => connection.createOffer())();
|
|
693
|
+
await (() => connection.setLocalDescription(offer))();
|
|
686
694
|
}
|
|
687
695
|
const tracks = publications.map(
|
|
688
696
|
({ state, transceiver, trackName }) => ({
|
|
@@ -692,7 +700,7 @@ var CloudflareSFUClient = class {
|
|
|
692
700
|
source: state.source
|
|
693
701
|
})
|
|
694
702
|
);
|
|
695
|
-
const response = await
|
|
703
|
+
const response = await (() => this.#requireTransport().addTracks({ connectionId, ...providerDescription(requireDescription(offer)), tracks }))();
|
|
696
704
|
this.#requireGeneration(generation);
|
|
697
705
|
await this.#applyProviderDescription(response, connection);
|
|
698
706
|
return response;
|
|
@@ -738,10 +746,9 @@ var CloudflareSFUClient = class {
|
|
|
738
746
|
}
|
|
739
747
|
}
|
|
740
748
|
async #reconcileRemotePublications(authoritative, generation) {
|
|
741
|
-
const cursor = this.#
|
|
749
|
+
const cursor = this.#nextRemotePublicationCursor(authoritative);
|
|
742
750
|
if (cursor === null) return;
|
|
743
|
-
const
|
|
744
|
-
if (ordering === "stale" || ordering === "same" && !this.#remotePullIncomplete) return;
|
|
751
|
+
const participantRevision = this.#remoteParticipantRevision;
|
|
745
752
|
const desired = this.#desiredRemotePublications(authoritative, cursor);
|
|
746
753
|
const toPull = this.#pendingRemotePulls(desired);
|
|
747
754
|
this.#pollFailedPull = true;
|
|
@@ -753,8 +760,13 @@ var CloudflareSFUClient = class {
|
|
|
753
760
|
this.#remoteTracks.clear();
|
|
754
761
|
for (const [key, publication] of next) this.#remoteTracks.set(key, publication);
|
|
755
762
|
this.#cursor = cursor;
|
|
756
|
-
this.#remotePullIncomplete =
|
|
757
|
-
|
|
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
|
+
}
|
|
758
770
|
this.#publishSnapshot();
|
|
759
771
|
this.#emitRemote();
|
|
760
772
|
}
|
|
@@ -763,31 +775,40 @@ var CloudflareSFUClient = class {
|
|
|
763
775
|
for (const publicationId of this.#remotePullRetryAfter.keys()) {
|
|
764
776
|
if (!desiredIds.has(publicationId)) this.#remotePullRetryAfter.delete(publicationId);
|
|
765
777
|
}
|
|
778
|
+
for (const publicationId of this.#subscribedPublicationIds) {
|
|
779
|
+
if (!desiredIds.has(publicationId)) this.#subscribedPublicationIds.delete(publicationId);
|
|
780
|
+
}
|
|
766
781
|
const now = Date.now();
|
|
767
782
|
return [...desired].filter(([key, publication]) => {
|
|
768
783
|
if (this.#remoteTracks.get(key)?.publicationId === publication.publicationId) return false;
|
|
769
784
|
const remaining = (this.#remotePullRetryAfter.get(publication.publicationId) ?? 0) - now;
|
|
770
|
-
if (remaining > 0) recordReconnect(this.#recordReconnect, "remote_pull_deferred", { remaining_ms: remaining, source: publication.source });
|
|
771
785
|
return remaining <= 0;
|
|
772
786
|
}).map(([, publication]) => publication);
|
|
773
787
|
}
|
|
774
|
-
#
|
|
788
|
+
#nextRemotePublicationCursor(authoritative) {
|
|
775
789
|
if (this.#cursor?.signature === INVALID_PUBLICATION_SIGNATURE && this.#cursor.incarnation === authoritative.incarnation && this.#cursor.sequence === authoritative.sequence) return null;
|
|
790
|
+
let cursor;
|
|
776
791
|
try {
|
|
777
|
-
|
|
792
|
+
cursor = validatePublicationSnapshot(authoritative);
|
|
778
793
|
} catch (error) {
|
|
779
794
|
this.#quarantineInvalidRemotePublicationCursor(authoritative);
|
|
780
795
|
throw error;
|
|
781
796
|
}
|
|
797
|
+
const ordering = comparePublicationCursor(this.#cursor, cursor);
|
|
798
|
+
return ordering === "stale" || ordering === "same" && !this.#remotePullIncomplete ? null : cursor;
|
|
782
799
|
}
|
|
783
800
|
#desiredRemotePublications(authoritative, cursor) {
|
|
784
801
|
try {
|
|
785
|
-
|
|
802
|
+
const publications = authoritative.publications.filter((publication) => this.#canSubscribeToRemotePublication(publication));
|
|
803
|
+
return desiredRemotePublications(publications, this.#participantId);
|
|
786
804
|
} catch (error) {
|
|
787
805
|
this.#observeRemotePublicationCursor(cursor);
|
|
788
806
|
throw error;
|
|
789
807
|
}
|
|
790
808
|
}
|
|
809
|
+
#canSubscribeToRemotePublication(publication) {
|
|
810
|
+
return this.#remoteParticipantIds === null || this.#remoteParticipantIds.has(publication.participantId) || this.#subscribedPublicationIds.has(publication.publicationId);
|
|
811
|
+
}
|
|
791
812
|
async #pullWithRecovery(publications, cursor, generation) {
|
|
792
813
|
try {
|
|
793
814
|
return await this.#pull(publications, generation);
|
|
@@ -801,12 +822,13 @@ var CloudflareSFUClient = class {
|
|
|
801
822
|
}
|
|
802
823
|
}
|
|
803
824
|
#canReplaceConnectionAfterRemotePull(error, generation) {
|
|
804
|
-
return error
|
|
825
|
+
return this.#isRetryableConnectionFailure(error) && !this.#hasLiveRemoteTracks() && this.#replaceMediaConnection !== void 0 && this.#replacementAttemptedGeneration !== generation;
|
|
805
826
|
}
|
|
806
827
|
#hasLiveRemoteTracks() {
|
|
807
828
|
return [...this.#remoteTracks.values()].some((publication) => publication.track.readyState === "live");
|
|
808
829
|
}
|
|
809
830
|
async #retryRemotePullOnReplacement(publications, cursor, generation) {
|
|
831
|
+
this.#remoteRecoveryGeneration = generation;
|
|
810
832
|
try {
|
|
811
833
|
await this.#replaceMediaConnectionForRecovery(generation);
|
|
812
834
|
await this.#republishPreparedTracksAfterRecovery(generation);
|
|
@@ -818,6 +840,44 @@ var CloudflareSFUClient = class {
|
|
|
818
840
|
}
|
|
819
841
|
}
|
|
820
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) {
|
|
821
881
|
if (publications.length === 0) return [];
|
|
822
882
|
return this.#serializeSDP(async () => {
|
|
823
883
|
const connection = this.#connection;
|
|
@@ -841,7 +901,7 @@ var CloudflareSFUClient = class {
|
|
|
841
901
|
connection.addEventListener("track", onTrack);
|
|
842
902
|
try {
|
|
843
903
|
this.#requireGeneration(generation);
|
|
844
|
-
const response = await
|
|
904
|
+
const response = await (() => this.#requireTransport().addTracks({ connectionId, tracks: requested, allowPartialRemoteTracks: true }))();
|
|
845
905
|
this.#requireGeneration(generation);
|
|
846
906
|
this.#recordRemotePullErrors(publications, response.trackErrors ?? []);
|
|
847
907
|
const responseTracks = response.tracks ?? [];
|
|
@@ -851,7 +911,7 @@ var CloudflareSFUClient = class {
|
|
|
851
911
|
if (matched.length === 0) return [];
|
|
852
912
|
await this.#waitForConnection(connection, generation);
|
|
853
913
|
this.#negotiatedGeneration = generation;
|
|
854
|
-
await
|
|
914
|
+
await (() => waitFor(() => responseTracks.every((track) => track.mid !== void 0 && received.has(track.mid)), 5e3))();
|
|
855
915
|
this.#requireGeneration(generation);
|
|
856
916
|
return matched.map(({ publication, mid }) => {
|
|
857
917
|
const track = received.get(mid);
|
|
@@ -876,26 +936,24 @@ var CloudflareSFUClient = class {
|
|
|
876
936
|
for (const failure of failures) {
|
|
877
937
|
const publication = requested.get(`${failure.connectionId}\0${failure.trackName}`);
|
|
878
938
|
if (!publication) throw new CloudflareSFUError("Cloudflare SFU returned an unrequested remote track error", "invalid_publication");
|
|
879
|
-
const delay =
|
|
939
|
+
const delay = 15e3;
|
|
880
940
|
this.#remotePullRetryAfter.set(publication.publicationId, Date.now() + delay);
|
|
881
|
-
recordReconnect(this.#recordReconnect, "remote_pull_backoff", { delay_ms: delay, retry_kind: failure.code === "empty_track_error" ? "empty_track" : "provider_error", source: publication.source });
|
|
882
941
|
this.#reportError(new CloudflareSFUError(`Cloudflare SFU remote track failed: ${failure.code}`, "media_failed", { providerCode: failure.code }));
|
|
883
942
|
}
|
|
884
943
|
}
|
|
885
944
|
async #completeRenegotiation(response, connection, connectionId, generation) {
|
|
886
945
|
if (!response.requiresImmediateRenegotiation) return;
|
|
887
946
|
await this.#applyProviderDescription(response, connection);
|
|
888
|
-
const answer = await
|
|
889
|
-
await
|
|
890
|
-
await
|
|
947
|
+
const answer = await (() => connection.createAnswer())();
|
|
948
|
+
await (() => connection.setLocalDescription(answer))();
|
|
949
|
+
await (() => this.#requireTransport().renegotiate({ connectionId, ...providerDescription(requireDescription(answer)) }))();
|
|
891
950
|
this.#requireGeneration(generation);
|
|
892
951
|
}
|
|
893
952
|
async #applyProviderDescription(response, connection = this.#connection) {
|
|
894
|
-
await
|
|
953
|
+
await (() => connection.setRemoteDescription(requireSFUDescription(response.sessionDescription)))();
|
|
895
954
|
}
|
|
896
955
|
#serializeSDP(operation) {
|
|
897
956
|
const generation = this.#generation;
|
|
898
|
-
const queuedAt = performance.now();
|
|
899
957
|
let expired = false;
|
|
900
958
|
let timer;
|
|
901
959
|
const timeout = new CloudflareSFUError("Cloudflare SFU negotiation timed out. Try again.", "negotiation_timeout");
|
|
@@ -908,7 +966,6 @@ var CloudflareSFUClient = class {
|
|
|
908
966
|
});
|
|
909
967
|
const run = () => {
|
|
910
968
|
if (expired || generation !== this.#generation) throw timeout;
|
|
911
|
-
recordReconnect(this.#recordReconnect, "sdp_queue", { duration_ms: performance.now() - queuedAt });
|
|
912
969
|
return operation();
|
|
913
970
|
};
|
|
914
971
|
const pending = this.#sdpTail.then(run, run);
|
|
@@ -949,18 +1006,14 @@ var CloudflareSFUClient = class {
|
|
|
949
1006
|
this.#setFailure(error, "media_failed");
|
|
950
1007
|
}
|
|
951
1008
|
#waitForConnection(connection, generation) {
|
|
952
|
-
return
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
CONNECTION_TIMEOUT_MS,
|
|
961
|
-
"Timed out waiting for the Cloudflare SFU peer connection"
|
|
962
|
-
)
|
|
963
|
-
);
|
|
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
|
+
))();
|
|
964
1017
|
}
|
|
965
1018
|
#cameraSimulcastSender(state) {
|
|
966
1019
|
if (!state?.enabled) return void 0;
|
|
@@ -1023,84 +1076,16 @@ var CloudflareSFUClient = class {
|
|
|
1023
1076
|
};
|
|
1024
1077
|
const capture = () => {
|
|
1025
1078
|
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
1026
|
-
recordReconnect(this.#recordReconnect, "rtc_state", { connection_state: connection.connectionState, ice_connection_state: connection.iceConnectionState, signaling_state: connection.signalingState });
|
|
1027
1079
|
observe();
|
|
1028
|
-
const recorder = this.#onRtcSummary;
|
|
1029
|
-
if (recorder) {
|
|
1030
|
-
let stats;
|
|
1031
|
-
try {
|
|
1032
|
-
stats = Promise.resolve(connection.getStats());
|
|
1033
|
-
} catch {
|
|
1034
|
-
stats = Promise.reject(new Error("RTC stats are unavailable"));
|
|
1035
|
-
}
|
|
1036
|
-
void stats.then((report) => {
|
|
1037
|
-
if (disposed || generation !== this.#generation || connectionEpoch !== this.#connectionEpoch || this.#stopped) return;
|
|
1038
|
-
recorder(rtcConnectionState(connection), rtcStats(report));
|
|
1039
|
-
}).catch(() => void 0);
|
|
1040
|
-
}
|
|
1041
1080
|
if (connection.connectionState === "closed") dispose();
|
|
1042
1081
|
};
|
|
1043
1082
|
this.#disposeConnectionObservation = dispose;
|
|
1044
1083
|
cameraTimer = globalThis.setTimeout(adaptCamera, 1e3);
|
|
1045
|
-
if (this.#recordReconnect) this.#sampleRecoveryTraffic(connection, generation, connectionEpoch);
|
|
1046
1084
|
connection.addEventListener("connectionstatechange", capture);
|
|
1047
1085
|
connection.addEventListener("iceconnectionstatechange", capture);
|
|
1048
1086
|
connection.addEventListener("signalingstatechange", capture);
|
|
1049
1087
|
capture();
|
|
1050
1088
|
}
|
|
1051
|
-
#sampleRecoveryTraffic(connection, generation, connectionEpoch) {
|
|
1052
|
-
const traffic = /* @__PURE__ */ new Map();
|
|
1053
|
-
let dtlsState;
|
|
1054
|
-
const current = () => generation === this.#generation && connectionEpoch === this.#connectionEpoch && !this.#stopped && connection.connectionState !== "closed" && connection.connectionState !== "failed";
|
|
1055
|
-
const sample = async () => {
|
|
1056
|
-
if (!current()) return;
|
|
1057
|
-
try {
|
|
1058
|
-
const report = await connection.getStats();
|
|
1059
|
-
if (!current()) return;
|
|
1060
|
-
const now = performance.now();
|
|
1061
|
-
report.forEach((entry) => {
|
|
1062
|
-
if (entry.type === "transport") {
|
|
1063
|
-
const nextState = rtcString(entry, "dtlsState");
|
|
1064
|
-
if (nextState !== dtlsState) recordReconnect(this.#recordReconnect, "dtls_state", { dtls_state: nextState ?? "unknown" });
|
|
1065
|
-
dtlsState = nextState;
|
|
1066
|
-
}
|
|
1067
|
-
this.#observeRecoveryTraffic(entry, traffic, now);
|
|
1068
|
-
});
|
|
1069
|
-
} catch {
|
|
1070
|
-
if (current()) recordReconnect(this.#recordReconnect, "rtc_stats_unavailable");
|
|
1071
|
-
}
|
|
1072
|
-
if (current()) globalThis.setTimeout(() => void sample(), 250);
|
|
1073
|
-
};
|
|
1074
|
-
void sample();
|
|
1075
|
-
}
|
|
1076
|
-
#observeRecoveryTraffic(entry, traffic, now) {
|
|
1077
|
-
if (entry.type !== "inbound-rtp" && entry.type !== "outbound-rtp") return;
|
|
1078
|
-
const direction = entry.type === "inbound-rtp" ? "inbound" : "outbound";
|
|
1079
|
-
const kind = rtcString(entry, "kind") ?? "unknown";
|
|
1080
|
-
const previous = traffic.get(entry.id);
|
|
1081
|
-
const next = previous ?? {
|
|
1082
|
-
packets: { count: 0, lastProgress: now, stalled: false },
|
|
1083
|
-
frames: { count: 0, lastProgress: now, stalled: false }
|
|
1084
|
-
};
|
|
1085
|
-
const packets = recoveryTrafficCount(entry, direction, "packet");
|
|
1086
|
-
const frames = recoveryTrafficCount(entry, direction, "frame");
|
|
1087
|
-
this.#observeTrafficCounter(next.packets, { count: packets, now, direction, kind, unit: "packet", existed: previous !== void 0 });
|
|
1088
|
-
this.#observeTrafficCounter(next.frames, { count: frames, now, direction, kind, unit: "frame", existed: previous !== void 0 });
|
|
1089
|
-
traffic.set(entry.id, next);
|
|
1090
|
-
}
|
|
1091
|
-
#observeTrafficCounter(progress, sample) {
|
|
1092
|
-
const attributes = { direction: sample.direction, kind: sample.kind };
|
|
1093
|
-
if (sample.count > progress.count) {
|
|
1094
|
-
if (progress.count === 0) recordReconnect(this.#recordReconnect, `first_${sample.unit}`, attributes);
|
|
1095
|
-
else if (progress.stalled) recordReconnect(this.#recordReconnect, sample.unit === "packet" ? "rtp_resumed" : "frame_resumed", attributes);
|
|
1096
|
-
progress.lastProgress = sample.now;
|
|
1097
|
-
progress.stalled = false;
|
|
1098
|
-
} else if (trafficStalled(progress, sample)) {
|
|
1099
|
-
progress.stalled = true;
|
|
1100
|
-
recordReconnect(this.#recordReconnect, sample.unit === "packet" ? "rtp_stall" : "frame_stall", attributes);
|
|
1101
|
-
}
|
|
1102
|
-
progress.count = sample.count;
|
|
1103
|
-
}
|
|
1104
1089
|
async #replaceDormantConnectionBeforeNegotiation(generation) {
|
|
1105
1090
|
if (!this.#started || this.#negotiatedGeneration === generation || this.#replacementAttemptedGeneration === generation || !this.#replaceMediaConnection) return;
|
|
1106
1091
|
await this.#replaceMediaConnectionForRecovery(generation);
|
|
@@ -1112,7 +1097,7 @@ var CloudflareSFUClient = class {
|
|
|
1112
1097
|
}
|
|
1113
1098
|
this.#replacementAttemptedGeneration = generation;
|
|
1114
1099
|
const replaceMediaConnection = this.#replaceMediaConnection;
|
|
1115
|
-
const bootstrap = await
|
|
1100
|
+
const bootstrap = await replaceMediaConnection();
|
|
1116
1101
|
this.#requireGeneration(generation);
|
|
1117
1102
|
validateBootstrap(bootstrap);
|
|
1118
1103
|
if (bootstrap.connectionId === this.#bootstrap.connectionId) {
|
|
@@ -1316,76 +1301,11 @@ function validateClientOptions(options) {
|
|
|
1316
1301
|
throw new CloudflareSFUError("Cloudflare SFU polling interval is invalid", "invalid_bootstrap");
|
|
1317
1302
|
}
|
|
1318
1303
|
}
|
|
1319
|
-
function rtcConnectionState(connection) {
|
|
1320
|
-
return {
|
|
1321
|
-
connectionState: connection.connectionState,
|
|
1322
|
-
iceConnectionState: connection.iceConnectionState,
|
|
1323
|
-
signalingState: connection.signalingState
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
function rtcStats(report) {
|
|
1327
|
-
const entries = [];
|
|
1328
|
-
report.forEach((entry) => {
|
|
1329
|
-
if (typeof entry.type !== "string") return;
|
|
1330
|
-
entries.push({ type: entry.type, ...rtcTrafficStats(entry), ...rtcQualityStats(entry), ...rtcTransportStats(entry) });
|
|
1331
|
-
});
|
|
1332
|
-
return entries;
|
|
1333
|
-
}
|
|
1334
|
-
function rtcTrafficStats(entry) {
|
|
1335
|
-
const bytesReceived = rtcNumber(entry, "bytesReceived");
|
|
1336
|
-
const bytesSent = rtcNumber(entry, "bytesSent");
|
|
1337
|
-
const packetsReceived = rtcNumber(entry, "packetsReceived");
|
|
1338
|
-
const packetsSent = rtcNumber(entry, "packetsSent");
|
|
1339
|
-
return {
|
|
1340
|
-
...bytesReceived !== void 0 ? { bytesReceived } : {},
|
|
1341
|
-
...bytesSent !== void 0 ? { bytesSent } : {},
|
|
1342
|
-
...packetsReceived !== void 0 ? { packetsReceived } : {},
|
|
1343
|
-
...packetsSent !== void 0 ? { packetsSent } : {}
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
function rtcQualityStats(entry) {
|
|
1347
|
-
const kind = rtcString(entry, "kind");
|
|
1348
|
-
const framesDropped = rtcNumber(entry, "framesDropped");
|
|
1349
|
-
const jitter = rtcNumber(entry, "jitter");
|
|
1350
|
-
const packetsLost = rtcNumber(entry, "packetsLost");
|
|
1351
|
-
const roundTripTime = rtcNumber(entry, "roundTripTime");
|
|
1352
|
-
return {
|
|
1353
|
-
...kind !== void 0 ? { kind } : {},
|
|
1354
|
-
...framesDropped !== void 0 ? { framesDropped } : {},
|
|
1355
|
-
...jitter !== void 0 ? { jitter } : {},
|
|
1356
|
-
...packetsLost !== void 0 ? { packetsLost } : {},
|
|
1357
|
-
...roundTripTime !== void 0 ? { roundTripTime } : {}
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
function rtcTransportStats(entry) {
|
|
1361
|
-
const state = rtcString(entry, "state");
|
|
1362
|
-
const selected = rtcBoolean(entry, "selected");
|
|
1363
|
-
const nominated = rtcBoolean(entry, "nominated");
|
|
1364
|
-
const dtlsState = rtcString(entry, "dtlsState");
|
|
1365
|
-
return {
|
|
1366
|
-
...state !== void 0 ? { state } : {},
|
|
1367
|
-
...selected !== void 0 ? { selected } : {},
|
|
1368
|
-
...nominated !== void 0 ? { nominated } : {},
|
|
1369
|
-
...dtlsState !== void 0 ? { dtlsState } : {}
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1372
|
-
function rtcString(value, property) {
|
|
1373
|
-
const candidate = Reflect.get(value, property);
|
|
1374
|
-
return typeof candidate === "string" ? candidate : void 0;
|
|
1375
|
-
}
|
|
1376
|
-
function rtcNumber(value, property) {
|
|
1377
|
-
const candidate = Reflect.get(value, property);
|
|
1378
|
-
return typeof candidate === "number" ? candidate : void 0;
|
|
1379
|
-
}
|
|
1380
|
-
function rtcBoolean(value, property) {
|
|
1381
|
-
const candidate = Reflect.get(value, property);
|
|
1382
|
-
return typeof candidate === "boolean" ? candidate : void 0;
|
|
1383
|
-
}
|
|
1384
1304
|
function validateBootstrap(bootstrap) {
|
|
1385
1305
|
if (!bootstrap.connectionId.trim() || !bootstrap.stunServer.trim()) throw new CloudflareSFUError("Cloudflare SFU bootstrap is incomplete", "invalid_bootstrap");
|
|
1386
1306
|
}
|
|
1387
1307
|
function validateTrackSource(source, track) {
|
|
1388
|
-
const valid = source === "microphone" ? track.kind === "audio" : track.kind === "video";
|
|
1308
|
+
const valid = source === "microphone" || source === "screen_audio" ? track.kind === "audio" : track.kind === "video";
|
|
1389
1309
|
if (!valid) throw new CloudflareSFUError(`The prepared ${source} track has an incompatible kind`, "media_failed");
|
|
1390
1310
|
}
|
|
1391
1311
|
function desiredRemotePublications(publications, participantId) {
|
|
@@ -1462,8 +1382,13 @@ function remotePublicationEqual(left, right) {
|
|
|
1462
1382
|
function providerDescription(description) {
|
|
1463
1383
|
return { sessionDescription: description };
|
|
1464
1384
|
}
|
|
1385
|
+
function isRateLimited(error) {
|
|
1386
|
+
return error instanceof CloudflareSFUError && error.options.status === 429;
|
|
1387
|
+
}
|
|
1465
1388
|
function retryableRemotePull(error) {
|
|
1466
|
-
|
|
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);
|
|
1467
1392
|
}
|
|
1468
1393
|
function firefoxCameraSimulcastUnsupported() {
|
|
1469
1394
|
return typeof navigator !== "undefined" && /\bFirefox\/\d+/i.test(navigator.userAgent);
|
|
@@ -1476,13 +1401,6 @@ function mediaTargetFailure(error) {
|
|
|
1476
1401
|
const ambiguous = code === "signaling_timeout" || code === "negotiation_timeout";
|
|
1477
1402
|
return { outcome: ambiguous ? "ambiguous" : "retryable_failure", errorCode: code };
|
|
1478
1403
|
}
|
|
1479
|
-
function recoveryTrafficCount(entry, direction, unit) {
|
|
1480
|
-
const property = unit === "packet" ? direction === "inbound" ? "packetsReceived" : "packetsSent" : direction === "inbound" ? "framesDecoded" : "framesEncoded";
|
|
1481
|
-
return rtcNumber(entry, property) ?? 0;
|
|
1482
|
-
}
|
|
1483
|
-
function trafficStalled(progress, sample) {
|
|
1484
|
-
return sample.existed && !progress.stalled && sample.now - progress.lastProgress >= 1e3 && (sample.unit === "packet" || progress.count > 0);
|
|
1485
|
-
}
|
|
1486
1404
|
|
|
1487
1405
|
// src/media/transport.ts
|
|
1488
1406
|
var SIGNALING_TIMEOUT_MS = 8e3;
|
|
@@ -1501,7 +1419,7 @@ function createCloudflareSFUHTTPTransport(options) {
|
|
|
1501
1419
|
const response = await fetch(`${mediaPath}/${path}`, {
|
|
1502
1420
|
...init,
|
|
1503
1421
|
signal,
|
|
1504
|
-
headers: {
|
|
1422
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
|
|
1505
1423
|
});
|
|
1506
1424
|
if (!response.ok) {
|
|
1507
1425
|
throw new CloudflareSFUError(`Chalk SFU signaling failed with HTTP ${response.status}`, "signaling_failed", {
|
|
@@ -1578,514 +1496,6 @@ function requireCredential(options) {
|
|
|
1578
1496
|
throw new CloudflareSFUError("A media credential provider is required", "signaling_failed");
|
|
1579
1497
|
}
|
|
1580
1498
|
|
|
1581
|
-
// src/media/rtk.ts
|
|
1582
|
-
var CloudflareRTKError = class extends Error {
|
|
1583
|
-
code;
|
|
1584
|
-
constructor(message, code) {
|
|
1585
|
-
super(message);
|
|
1586
|
-
this.name = "CloudflareRTKError";
|
|
1587
|
-
this.code = code;
|
|
1588
|
-
}
|
|
1589
|
-
};
|
|
1590
|
-
var EMPTY_LOCAL2 = Object.freeze([]);
|
|
1591
|
-
var EMPTY_REMOTE2 = Object.freeze([]);
|
|
1592
|
-
var CloudflareRTKClient = class {
|
|
1593
|
-
#participantId;
|
|
1594
|
-
#onError;
|
|
1595
|
-
#onScreenEnded;
|
|
1596
|
-
#clientFactory;
|
|
1597
|
-
#snapshotListeners = /* @__PURE__ */ new Set();
|
|
1598
|
-
#sourceOperations = /* @__PURE__ */ new Map();
|
|
1599
|
-
#generationInvalidators = /* @__PURE__ */ new Set();
|
|
1600
|
-
#connection = null;
|
|
1601
|
-
#localTracks = /* @__PURE__ */ new Map();
|
|
1602
|
-
#connectionUnsubscribers = [];
|
|
1603
|
-
#participantUnsubscribers = /* @__PURE__ */ new Map();
|
|
1604
|
-
#snapshot;
|
|
1605
|
-
#stopped = false;
|
|
1606
|
-
#generation = 0;
|
|
1607
|
-
#screenDisableRequested = false;
|
|
1608
|
-
#initialAuthToken;
|
|
1609
|
-
constructor(options) {
|
|
1610
|
-
if (!options.authToken.trim()) throw new CloudflareRTKError("A RealtimeKit auth token is required", "invalid_client");
|
|
1611
|
-
if (!options.participantId.trim()) throw new CloudflareRTKError("A participant ID is required", "invalid_participant");
|
|
1612
|
-
this.#participantId = options.participantId;
|
|
1613
|
-
this.#onError = options.onError;
|
|
1614
|
-
this.#onScreenEnded = options.onScreenEnded;
|
|
1615
|
-
this.#clientFactory = options.clientFactory ?? defaultClientFactory;
|
|
1616
|
-
this.#snapshot = freezeSnapshot2({
|
|
1617
|
-
connection: { phase: "idle", peerConnectionState: null, iceConnectionState: null },
|
|
1618
|
-
cursor: null,
|
|
1619
|
-
localTracks: EMPTY_LOCAL2,
|
|
1620
|
-
remoteTracks: EMPTY_REMOTE2,
|
|
1621
|
-
failure: null
|
|
1622
|
-
});
|
|
1623
|
-
this.#initialAuthToken = options.authToken;
|
|
1624
|
-
}
|
|
1625
|
-
getSnapshot() {
|
|
1626
|
-
return this.#snapshot;
|
|
1627
|
-
}
|
|
1628
|
-
subscribe(listener) {
|
|
1629
|
-
return subscribeSnapshot(this.#snapshotListeners, listener);
|
|
1630
|
-
}
|
|
1631
|
-
prepareLocalTrack(source, track) {
|
|
1632
|
-
this.#requireStartable();
|
|
1633
|
-
validateTrack(source, track);
|
|
1634
|
-
if (this.#localTracks.has(source)) throw new CloudflareRTKError(`A ${source} track is already prepared`, "media_failed");
|
|
1635
|
-
const endedListener = source === "screen" ? () => this.#onScreenEnded?.() : null;
|
|
1636
|
-
if (endedListener) track.addEventListener("ended", endedListener);
|
|
1637
|
-
this.#localTracks.set(source, { source, track, enabled: false, endedListener });
|
|
1638
|
-
this.#publishSnapshot();
|
|
1639
|
-
}
|
|
1640
|
-
async clearPreparedLocalTrack(source) {
|
|
1641
|
-
const state = this.#localTracks.get(source);
|
|
1642
|
-
if (!state) return;
|
|
1643
|
-
const connection = this.#connection;
|
|
1644
|
-
if (state.endedListener) state.track.removeEventListener("ended", state.endedListener);
|
|
1645
|
-
this.#localTracks.delete(source);
|
|
1646
|
-
const actions = connection ? this.#sourceActions(state, connection) : null;
|
|
1647
|
-
const cleanup = actions?.enabled && state.enabled ? this.#queueSourceOperation(source, () => connection === this.#connection ? actions.disable() : Promise.resolve()) : Promise.resolve();
|
|
1648
|
-
this.#publishSnapshot();
|
|
1649
|
-
try {
|
|
1650
|
-
await cleanup;
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
if (!isStaleGeneration(error)) throw error;
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
async start(localMedia) {
|
|
1656
|
-
if (this.#snapshot.connection.phase === "live") return;
|
|
1657
|
-
this.#requireStartable();
|
|
1658
|
-
const generation = this.#advanceGeneration();
|
|
1659
|
-
this.#setPhase("connecting", null);
|
|
1660
|
-
try {
|
|
1661
|
-
const tracks = localMedia.getTracks().filter((track) => track.kind === "audio" || track.kind === "video");
|
|
1662
|
-
for (const track of tracks) {
|
|
1663
|
-
const source = track.kind === "audio" ? "microphone" : "camera";
|
|
1664
|
-
if (!this.#localTracks.has(source)) this.#localTracks.set(source, { source, track, enabled: false, endedListener: null });
|
|
1665
|
-
}
|
|
1666
|
-
if (!await this.#openConnection(this.#initialAuthToken, generation)) return;
|
|
1667
|
-
await this.#restoreLocalTracks(generation, true);
|
|
1668
|
-
} catch (error) {
|
|
1669
|
-
this.#failCurrentGeneration(generation, error);
|
|
1670
|
-
throw error;
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
async setLocalPublicationTarget(target) {
|
|
1674
|
-
const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
|
|
1675
|
-
if (resolved.kind === "result") return resolved.result;
|
|
1676
|
-
const generation = this.#generation;
|
|
1677
|
-
try {
|
|
1678
|
-
return await this.#queueSourceOperation(target.source, () => this.#applyLocalTarget(target, generation));
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
return { outcome: "retryable_failure", errorCode: error instanceof CloudflareRTKError ? error.code : "media_failed" };
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
async #queueSourceOperation(source, apply) {
|
|
1684
|
-
const previous = this.#sourceOperations.get(source) ?? Promise.resolve();
|
|
1685
|
-
const generation = this.#generation;
|
|
1686
|
-
let invalidate = () => {
|
|
1687
|
-
};
|
|
1688
|
-
const retired = new Promise((_resolve, reject) => {
|
|
1689
|
-
invalidate = () => reject(new CloudflareRTKError("The media operation belongs to a retired connection", "stale_generation"));
|
|
1690
|
-
this.#generationInvalidators.add(invalidate);
|
|
1691
|
-
});
|
|
1692
|
-
const operation = Promise.race([
|
|
1693
|
-
previous.catch(() => void 0).then(() => {
|
|
1694
|
-
if (!this.#isCurrentGeneration(generation)) throw new CloudflareRTKError("The queued media operation belongs to a retired connection", "stale_generation");
|
|
1695
|
-
return apply();
|
|
1696
|
-
}),
|
|
1697
|
-
retired
|
|
1698
|
-
]);
|
|
1699
|
-
this.#sourceOperations.set(source, operation);
|
|
1700
|
-
try {
|
|
1701
|
-
return await operation;
|
|
1702
|
-
} finally {
|
|
1703
|
-
this.#generationInvalidators.delete(invalidate);
|
|
1704
|
-
if (this.#sourceOperations.get(source) === operation) this.#sourceOperations.delete(source);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
async #applyLocalTarget(target, generation) {
|
|
1708
|
-
if (!this.#isCurrentGeneration(generation)) return { outcome: "retryable_failure", errorCode: "stale_generation" };
|
|
1709
|
-
const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
|
|
1710
|
-
if (resolved.kind === "result") return resolved.result;
|
|
1711
|
-
const state = resolved.value;
|
|
1712
|
-
try {
|
|
1713
|
-
if (!this.#connection) throw new CloudflareRTKError("The RealtimeKit connection is recovering", "stale_generation");
|
|
1714
|
-
if (state.enabled === target.enabled) return { outcome: "satisfied", errorCode: null };
|
|
1715
|
-
await this.#setSourceEnabled(state, target.enabled);
|
|
1716
|
-
return { outcome: "confirmed", errorCode: null };
|
|
1717
|
-
} catch (error) {
|
|
1718
|
-
this.#reportError(error);
|
|
1719
|
-
return { outcome: "retryable_failure", errorCode: error instanceof CloudflareRTKError ? error.code : "media_failed" };
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
observeLocalPublications(listener) {
|
|
1723
|
-
return observePublications(this.#snapshotListeners, listener, () => this.#projectLocalPublications());
|
|
1724
|
-
}
|
|
1725
|
-
observeRemotePublications(listener) {
|
|
1726
|
-
return observePublications(this.#snapshotListeners, listener, () => this.#projectRemotePublications());
|
|
1727
|
-
}
|
|
1728
|
-
async restart(input) {
|
|
1729
|
-
if (!isRTKAccess(input)) throw new CloudflareRTKError("The RealtimeKit adapter requires a Cloudflare RealtimeKit access grant", "invalid_client");
|
|
1730
|
-
if (this.#stopped) throw new CloudflareRTKError("The RealtimeKit media client has stopped", "media_stopped");
|
|
1731
|
-
const generation = this.#advanceGeneration();
|
|
1732
|
-
this.#setPhase("recovering", null);
|
|
1733
|
-
try {
|
|
1734
|
-
await this.#closeConnection();
|
|
1735
|
-
if (!this.#isCurrentGeneration(generation)) return;
|
|
1736
|
-
if (!await this.#openConnection(input.clientPayload.token, generation)) return;
|
|
1737
|
-
await this.#restoreLocalTracks(generation, false);
|
|
1738
|
-
} catch (error) {
|
|
1739
|
-
this.#failCurrentGeneration(generation, error);
|
|
1740
|
-
throw error;
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
stop() {
|
|
1744
|
-
if (this.#stopped) return;
|
|
1745
|
-
this.#stopped = true;
|
|
1746
|
-
this.#advanceGeneration();
|
|
1747
|
-
const leave = this.#closeConnection();
|
|
1748
|
-
void leave.catch((error) => this.#reportError(error));
|
|
1749
|
-
for (const state of this.#localTracks.values()) if (state.endedListener) state.track.removeEventListener("ended", state.endedListener);
|
|
1750
|
-
this.#localTracks.clear();
|
|
1751
|
-
this.#setPhase("stopped", null);
|
|
1752
|
-
}
|
|
1753
|
-
#isCurrentGeneration(generation) {
|
|
1754
|
-
return !this.#stopped && generation === this.#generation;
|
|
1755
|
-
}
|
|
1756
|
-
#advanceGeneration() {
|
|
1757
|
-
const generation = ++this.#generation;
|
|
1758
|
-
this.#sourceOperations.clear();
|
|
1759
|
-
for (const invalidate of this.#generationInvalidators) invalidate();
|
|
1760
|
-
this.#generationInvalidators.clear();
|
|
1761
|
-
return generation;
|
|
1762
|
-
}
|
|
1763
|
-
async #restoreLocalTracks(generation, initial) {
|
|
1764
|
-
for (const state of this.#localTracks.values()) {
|
|
1765
|
-
if (!this.#isCurrentGeneration(generation)) return;
|
|
1766
|
-
const enable = initial ? state.source !== "screen" : state.enabled;
|
|
1767
|
-
if (enable) {
|
|
1768
|
-
await this.#queueSourceOperation(state.source, async () => {
|
|
1769
|
-
if (!this.#isCurrentGeneration(generation) || this.#localTracks.get(state.source) !== state || !initial && !state.enabled) return;
|
|
1770
|
-
await this.#setSourceEnabled(state, true);
|
|
1771
|
-
}).catch((error) => {
|
|
1772
|
-
if (!this.#isCurrentGeneration(generation)) return;
|
|
1773
|
-
if (isStaleGeneration(error) && this.#localTracks.get(state.source) !== state) return;
|
|
1774
|
-
throw error;
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
if (this.#isCurrentGeneration(generation)) this.#setPhase("live", null);
|
|
1779
|
-
}
|
|
1780
|
-
#failCurrentGeneration(generation, error) {
|
|
1781
|
-
if (!this.#isCurrentGeneration(generation)) return;
|
|
1782
|
-
this.#setFailure(error);
|
|
1783
|
-
this.#reportError(error);
|
|
1784
|
-
}
|
|
1785
|
-
async #openConnection(authToken, generation) {
|
|
1786
|
-
const connection = await this.#clientFactory({ authToken, onError: (error) => this.#reportError(error) });
|
|
1787
|
-
if (!this.#isCurrentGeneration(generation)) {
|
|
1788
|
-
await connection.leave();
|
|
1789
|
-
return false;
|
|
1790
|
-
}
|
|
1791
|
-
this.#connection = connection;
|
|
1792
|
-
this.#bindConnection(connection);
|
|
1793
|
-
await connection.join();
|
|
1794
|
-
if (!this.#isCurrentGeneration(generation)) {
|
|
1795
|
-
await connection.leave();
|
|
1796
|
-
return false;
|
|
1797
|
-
}
|
|
1798
|
-
return true;
|
|
1799
|
-
}
|
|
1800
|
-
async #closeConnection() {
|
|
1801
|
-
const connection = this.#connection;
|
|
1802
|
-
this.#connection = null;
|
|
1803
|
-
this.#unbindConnection();
|
|
1804
|
-
if (connection) await connection.leave();
|
|
1805
|
-
}
|
|
1806
|
-
#bindConnection(connection) {
|
|
1807
|
-
const selfAudio = connection.self.onAudioUpdate(() => this.#syncLocalState("microphone"));
|
|
1808
|
-
const selfVideo = connection.self.onVideoUpdate(() => this.#syncLocalState("camera"));
|
|
1809
|
-
const selfScreen = connection.self.onScreenShareUpdate((payload) => {
|
|
1810
|
-
this.#syncLocalState("screen");
|
|
1811
|
-
if (!payload.screenShareEnabled && !this.#screenDisableRequested) this.#onScreenEnded?.();
|
|
1812
|
-
this.#screenDisableRequested = false;
|
|
1813
|
-
});
|
|
1814
|
-
const connectionLeft = connection.self.onLeft(() => {
|
|
1815
|
-
if (this.#stopped) return;
|
|
1816
|
-
const error = new CloudflareRTKError("The RealtimeKit connection ended unexpectedly", "media_failed");
|
|
1817
|
-
this.#setFailure(error);
|
|
1818
|
-
this.#reportError(error);
|
|
1819
|
-
});
|
|
1820
|
-
for (const participant of connection.participants.joined.list()) this.#bindParticipant(participant);
|
|
1821
|
-
const participantJoined = connection.participants.joined.onJoined((participant) => {
|
|
1822
|
-
this.#bindParticipant(participant);
|
|
1823
|
-
this.#publishSnapshot();
|
|
1824
|
-
});
|
|
1825
|
-
const participantLeft = connection.participants.joined.onLeft((participant) => {
|
|
1826
|
-
this.#unbindParticipant(participant.id);
|
|
1827
|
-
this.#publishSnapshot();
|
|
1828
|
-
});
|
|
1829
|
-
this.#connectionUnsubscribers = [selfAudio, selfVideo, selfScreen, connectionLeft, participantJoined, participantLeft];
|
|
1830
|
-
}
|
|
1831
|
-
#unbindConnection() {
|
|
1832
|
-
for (const unsubscribe of this.#connectionUnsubscribers) unsubscribe();
|
|
1833
|
-
this.#connectionUnsubscribers = [];
|
|
1834
|
-
for (const unsubscribers of this.#participantUnsubscribers.values()) for (const unsubscribe of unsubscribers) unsubscribe();
|
|
1835
|
-
this.#participantUnsubscribers.clear();
|
|
1836
|
-
}
|
|
1837
|
-
#bindParticipant(participant) {
|
|
1838
|
-
if (this.#participantUnsubscribers.has(participant.id)) return;
|
|
1839
|
-
const unsubscribers = [participant.onAudioUpdate?.(() => this.#publishSnapshot()), participant.onVideoUpdate?.(() => this.#publishSnapshot()), participant.onScreenShareUpdate?.(() => this.#publishSnapshot())].filter((unsubscribe) => unsubscribe !== void 0);
|
|
1840
|
-
this.#participantUnsubscribers.set(participant.id, unsubscribers);
|
|
1841
|
-
}
|
|
1842
|
-
#unbindParticipant(participantId) {
|
|
1843
|
-
const unsubscribers = this.#participantUnsubscribers.get(participantId);
|
|
1844
|
-
if (!unsubscribers) return;
|
|
1845
|
-
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
1846
|
-
this.#participantUnsubscribers.delete(participantId);
|
|
1847
|
-
}
|
|
1848
|
-
async #setSourceEnabled(state, enabled) {
|
|
1849
|
-
const connection = this.#connection;
|
|
1850
|
-
const generation = this.#generation;
|
|
1851
|
-
if (!connection) throw new CloudflareRTKError("The RealtimeKit connection is not active", "media_stopped");
|
|
1852
|
-
const actions = this.#sourceActions(state, connection);
|
|
1853
|
-
if (state.source === "screen") this.#screenDisableRequested = !enabled;
|
|
1854
|
-
await actions[enabled ? "enable" : "disable"]();
|
|
1855
|
-
try {
|
|
1856
|
-
this.#requireCurrentSource(state, connection, generation);
|
|
1857
|
-
} catch (error) {
|
|
1858
|
-
if (enabled)
|
|
1859
|
-
await actions.disable().catch((cause) => {
|
|
1860
|
-
this.#failCurrentGeneration(generation, cause);
|
|
1861
|
-
throw cause;
|
|
1862
|
-
});
|
|
1863
|
-
throw error;
|
|
1864
|
-
}
|
|
1865
|
-
state.enabled = enabled;
|
|
1866
|
-
state.track.enabled = enabled;
|
|
1867
|
-
this.#publishSnapshot();
|
|
1868
|
-
}
|
|
1869
|
-
#sourceActions(state, connection) {
|
|
1870
|
-
return {
|
|
1871
|
-
microphone: { enabled: connection.self.audioEnabled, enable: () => connection.self.enableAudio(state.track), disable: () => connection.self.disableAudio() },
|
|
1872
|
-
camera: { enabled: connection.self.videoEnabled, enable: () => connection.self.enableVideo(state.track), disable: () => connection.self.disableVideo() },
|
|
1873
|
-
screen: {
|
|
1874
|
-
enabled: connection.self.screenShareEnabled,
|
|
1875
|
-
enable: () => connection.self.enableScreenShare(),
|
|
1876
|
-
disable: () => {
|
|
1877
|
-
if (connection === this.#connection) this.#screenDisableRequested = true;
|
|
1878
|
-
return connection.self.disableScreenShare();
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}[state.source];
|
|
1882
|
-
}
|
|
1883
|
-
#requireCurrentSource(state, connection, generation) {
|
|
1884
|
-
if (!this.#isCurrentGeneration(generation) || connection !== this.#connection || this.#localTracks.get(state.source) !== state) {
|
|
1885
|
-
throw new CloudflareRTKError("The local media target belongs to a replaced connection or source", "stale_generation");
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
#syncLocalState(source) {
|
|
1889
|
-
const state = this.#localTracks.get(source);
|
|
1890
|
-
const connection = this.#connection;
|
|
1891
|
-
if (!state || !connection) return;
|
|
1892
|
-
state.enabled = source === "microphone" ? connection.self.audioEnabled : source === "camera" ? connection.self.videoEnabled : connection.self.screenShareEnabled;
|
|
1893
|
-
state.track.enabled = state.enabled;
|
|
1894
|
-
this.#publishSnapshot();
|
|
1895
|
-
}
|
|
1896
|
-
#publishSnapshot() {
|
|
1897
|
-
this.#snapshot = freezeSnapshot2({ ...this.#snapshot, localTracks: this.#projectLocalTracks(), remoteTracks: this.#projectRemoteTracks() });
|
|
1898
|
-
this.#notifyListeners();
|
|
1899
|
-
}
|
|
1900
|
-
#projectLocalTracks() {
|
|
1901
|
-
return Object.freeze([...this.#localTracks.values()].map((state) => ({ source: state.source, enabled: state.enabled, publicationId: `${this.#participantId}:${state.source}`, track: state.track })));
|
|
1902
|
-
}
|
|
1903
|
-
#projectLocalPublications() {
|
|
1904
|
-
return Object.freeze(this.#projectLocalTracks().map((track) => ({ participantId: this.#participantId, source: track.source, enabled: track.enabled, publicationId: track.publicationId })));
|
|
1905
|
-
}
|
|
1906
|
-
#projectRemotePublications() {
|
|
1907
|
-
return Object.freeze(this.#projectRemoteTracks().map((track) => ({ participantId: track.participantId, source: track.source, enabled: true, publicationId: track.publicationId })));
|
|
1908
|
-
}
|
|
1909
|
-
#projectRemoteTracks() {
|
|
1910
|
-
return projectRemoteTracks(this.#connection?.participants.joined.list() ?? []);
|
|
1911
|
-
}
|
|
1912
|
-
#setPhase(phase, failure) {
|
|
1913
|
-
this.#snapshot = freezeSnapshot2({ ...this.#snapshot, connection: { phase, peerConnectionState: null, iceConnectionState: null }, failure });
|
|
1914
|
-
this.#notifyListeners();
|
|
1915
|
-
}
|
|
1916
|
-
#setFailure(error) {
|
|
1917
|
-
this.#setPhase("failed", { code: "media_failed", recoverable: true });
|
|
1918
|
-
if (!(error instanceof CloudflareRTKError)) this.#reportError(error);
|
|
1919
|
-
}
|
|
1920
|
-
#requireStartable() {
|
|
1921
|
-
if (this.#stopped) throw new CloudflareRTKError("The RealtimeKit media client has stopped", "media_stopped");
|
|
1922
|
-
}
|
|
1923
|
-
#reportError(error) {
|
|
1924
|
-
try {
|
|
1925
|
-
this.#onError?.(error);
|
|
1926
|
-
} catch {
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
#notifyListeners() {
|
|
1930
|
-
for (const listener of this.#snapshotListeners) {
|
|
1931
|
-
try {
|
|
1932
|
-
listener();
|
|
1933
|
-
} catch (error) {
|
|
1934
|
-
this.#reportError(error);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
};
|
|
1939
|
-
function projectRemoteTracks(participants) {
|
|
1940
|
-
return Object.freeze(participants.flatMap(remoteTracksForParticipant));
|
|
1941
|
-
}
|
|
1942
|
-
function remoteTracksForParticipant(participant) {
|
|
1943
|
-
const participantId = participantIdentifier(participant);
|
|
1944
|
-
if (!participantId) return [];
|
|
1945
|
-
const publications = [];
|
|
1946
|
-
addRemotePublication(publications, participant, participantId, "microphone", participant.audioEnabled ? participant.audioTrack : null);
|
|
1947
|
-
addRemotePublication(publications, participant, participantId, "camera", participant.videoEnabled ? participant.videoTrack : null);
|
|
1948
|
-
addRemotePublication(publications, participant, participantId, "screen", participant.screenShareEnabled ? participant.screenShareTracks.video ?? null : null);
|
|
1949
|
-
return publications;
|
|
1950
|
-
}
|
|
1951
|
-
function participantIdentifier(participant) {
|
|
1952
|
-
return participant.customParticipantId?.trim() || participant.userId.trim() || participant.id.trim();
|
|
1953
|
-
}
|
|
1954
|
-
function addRemotePublication(publications, participant, participantId, source, track) {
|
|
1955
|
-
if (!track) return;
|
|
1956
|
-
publications.push({ participantId, source, publicationId: `${participant.id}:${source}`, track });
|
|
1957
|
-
}
|
|
1958
|
-
function validateTrack(source, track) {
|
|
1959
|
-
const expectedKind = source === "microphone" ? "audio" : "video";
|
|
1960
|
-
if (track.kind !== expectedKind) throw new CloudflareRTKError(`A ${source} track must be ${expectedKind}`, "invalid_target");
|
|
1961
|
-
}
|
|
1962
|
-
function isRTKAccess(value) {
|
|
1963
|
-
return value.provider === "cloudflare_rtk";
|
|
1964
|
-
}
|
|
1965
|
-
function freezeSnapshot2(snapshot) {
|
|
1966
|
-
const failure = snapshot.failure ? Object.freeze(snapshot.failure) : null;
|
|
1967
|
-
return Object.freeze({
|
|
1968
|
-
...snapshot,
|
|
1969
|
-
connection: Object.freeze(snapshot.connection),
|
|
1970
|
-
localTracks: Object.freeze(snapshot.localTracks.map((track) => Object.freeze(track))),
|
|
1971
|
-
remoteTracks: Object.freeze(snapshot.remoteTracks.map((track) => Object.freeze(track))),
|
|
1972
|
-
failure
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
async function defaultClientFactory(input) {
|
|
1976
|
-
const { default: RealtimeKitClient } = await import("@cloudflare/realtimekit");
|
|
1977
|
-
const client = await RealtimeKitClient.init({
|
|
1978
|
-
authToken: input.authToken,
|
|
1979
|
-
defaults: { audio: false, video: false },
|
|
1980
|
-
onError: input.onError
|
|
1981
|
-
});
|
|
1982
|
-
return adaptRealtimeKitClient(client);
|
|
1983
|
-
}
|
|
1984
|
-
function adaptRealtimeKitClient(client) {
|
|
1985
|
-
const self = client.self;
|
|
1986
|
-
return {
|
|
1987
|
-
join: () => client.join(),
|
|
1988
|
-
leave: () => client.leave(),
|
|
1989
|
-
self: {
|
|
1990
|
-
get peerId() {
|
|
1991
|
-
return self.peerId;
|
|
1992
|
-
},
|
|
1993
|
-
get audioEnabled() {
|
|
1994
|
-
return self.audioEnabled;
|
|
1995
|
-
},
|
|
1996
|
-
get videoEnabled() {
|
|
1997
|
-
return self.videoEnabled;
|
|
1998
|
-
},
|
|
1999
|
-
get screenShareEnabled() {
|
|
2000
|
-
return self.screenShareEnabled;
|
|
2001
|
-
},
|
|
2002
|
-
get audioTrack() {
|
|
2003
|
-
return self.audioTrack ?? null;
|
|
2004
|
-
},
|
|
2005
|
-
get videoTrack() {
|
|
2006
|
-
return self.videoTrack ?? null;
|
|
2007
|
-
},
|
|
2008
|
-
get screenShareTracks() {
|
|
2009
|
-
return {
|
|
2010
|
-
...self.screenShareTracks.audio ? { audio: self.screenShareTracks.audio } : {},
|
|
2011
|
-
...self.screenShareTracks.video ? { video: self.screenShareTracks.video } : {}
|
|
2012
|
-
};
|
|
2013
|
-
},
|
|
2014
|
-
enableAudio: (customTrack) => self.enableAudio(customTrack),
|
|
2015
|
-
enableVideo: (customTrack) => self.enableVideo(customTrack),
|
|
2016
|
-
enableScreenShare: () => self.enableScreenShare(),
|
|
2017
|
-
disableAudio: () => self.disableAudio(),
|
|
2018
|
-
disableVideo: () => self.disableVideo(),
|
|
2019
|
-
disableScreenShare: () => self.disableScreenShare(),
|
|
2020
|
-
onAudioUpdate: (listener) => {
|
|
2021
|
-
self.on("audioUpdate", listener);
|
|
2022
|
-
return () => self.off("audioUpdate", listener);
|
|
2023
|
-
},
|
|
2024
|
-
onVideoUpdate: (listener) => {
|
|
2025
|
-
self.on("videoUpdate", listener);
|
|
2026
|
-
return () => self.off("videoUpdate", listener);
|
|
2027
|
-
},
|
|
2028
|
-
onScreenShareUpdate: (listener) => {
|
|
2029
|
-
self.on("screenShareUpdate", listener);
|
|
2030
|
-
return () => self.off("screenShareUpdate", listener);
|
|
2031
|
-
},
|
|
2032
|
-
onLeft: (listener) => {
|
|
2033
|
-
self.on("roomLeft", listener);
|
|
2034
|
-
return () => self.off("roomLeft", listener);
|
|
2035
|
-
}
|
|
2036
|
-
},
|
|
2037
|
-
participants: {
|
|
2038
|
-
joined: {
|
|
2039
|
-
list: () => client.participants.joined.toArray().map(toParticipant),
|
|
2040
|
-
onJoined: (listener) => {
|
|
2041
|
-
const callback = (participant) => listener(toParticipant(participant));
|
|
2042
|
-
client.participants.joined.on("participantJoined", callback);
|
|
2043
|
-
return () => client.participants.joined.off("participantJoined", callback);
|
|
2044
|
-
},
|
|
2045
|
-
onLeft: (listener) => {
|
|
2046
|
-
const callback = (participant) => listener(toParticipant(participant));
|
|
2047
|
-
client.participants.joined.on("participantLeft", callback);
|
|
2048
|
-
return () => client.participants.joined.off("participantLeft", callback);
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
function toParticipant(participant) {
|
|
2055
|
-
return {
|
|
2056
|
-
id: participant.id,
|
|
2057
|
-
userId: participant.userId,
|
|
2058
|
-
...participant.customParticipantId === void 0 ? {} : { customParticipantId: participant.customParticipantId },
|
|
2059
|
-
audioEnabled: participant.audioEnabled,
|
|
2060
|
-
videoEnabled: participant.videoEnabled,
|
|
2061
|
-
screenShareEnabled: participant.screenShareEnabled,
|
|
2062
|
-
audioTrack: participant.audioTrack ?? null,
|
|
2063
|
-
videoTrack: participant.videoTrack ?? null,
|
|
2064
|
-
screenShareTracks: {
|
|
2065
|
-
...participant.screenShareTracks.audio ? { audio: participant.screenShareTracks.audio } : {},
|
|
2066
|
-
...participant.screenShareTracks.video ? { video: participant.screenShareTracks.video } : {}
|
|
2067
|
-
},
|
|
2068
|
-
onAudioUpdate: (listener) => {
|
|
2069
|
-
const callback = () => listener();
|
|
2070
|
-
participant.on("audioUpdate", callback);
|
|
2071
|
-
return () => participant.off("audioUpdate", callback);
|
|
2072
|
-
},
|
|
2073
|
-
onVideoUpdate: (listener) => {
|
|
2074
|
-
const callback = () => listener();
|
|
2075
|
-
participant.on("videoUpdate", callback);
|
|
2076
|
-
return () => participant.off("videoUpdate", callback);
|
|
2077
|
-
},
|
|
2078
|
-
onScreenShareUpdate: (listener) => {
|
|
2079
|
-
const callback = () => listener();
|
|
2080
|
-
participant.on("screenShareUpdate", callback);
|
|
2081
|
-
return () => participant.off("screenShareUpdate", callback);
|
|
2082
|
-
}
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
function isStaleGeneration(error) {
|
|
2086
|
-
return error instanceof CloudflareRTKError && error.code === "stale_generation";
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
1499
|
// src/media/capture-error.ts
|
|
2090
1500
|
function describeMediaCaptureError(cause) {
|
|
2091
1501
|
const name = typeof cause === "object" && cause !== null && "name" in cause ? cause.name : void 0;
|
|
@@ -2121,7 +1531,5 @@ export {
|
|
|
2121
1531
|
publicationKey,
|
|
2122
1532
|
waitFor,
|
|
2123
1533
|
CloudflareSFUClient,
|
|
2124
|
-
createCloudflareSFUHTTPTransport
|
|
2125
|
-
CloudflareRTKError,
|
|
2126
|
-
CloudflareRTKClient
|
|
1534
|
+
createCloudflareSFUHTTPTransport
|
|
2127
1535
|
};
|