@q9labsai/chalk-client 5.2.5 → 5.2.6
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/CHANGELOG.md +4 -0
- package/dist/{chunk-UMAE5GCV.js → chunk-CJVLTSIO.js} +19 -1
- package/dist/{chunk-BUNSEDXL.js → chunk-I7Y4HR2P.js} +67 -39
- package/dist/{chunk-LKNT6XFD.js → chunk-WFRTUBN6.js} +1 -1
- package/dist/{chunk-FD25AMLG.js → chunk-YIDKETOX.js} +124 -57
- package/dist/client.d.ts +1 -0
- package/dist/connection/production.d.ts +1 -1
- package/dist/effect.js +3 -3
- package/dist/generated/http-api.d.ts +1 -0
- package/dist/generated/schemas.d.ts +1 -0
- package/dist/index.js +4 -4
- package/dist/media/client.d.ts +4 -3
- package/dist/media/index.js +1 -1
- package/dist/media/plane.d.ts +6 -2
- package/dist/media/types.d.ts +2 -1
- package/dist/public-invites/index.js +2 -2
- package/dist/space-client/promise-facade.d.ts +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.2.6
|
|
4
|
+
|
|
5
|
+
- Reuse microphone and camera senders after short browser capture interruptions, keeping capture disabled until a fresh user enable. Republish after long interruptions before trusting an expired provider track, and detach again if capture is interrupted during reattachment. Report interrupted owned capture as unavailable to other participants through the existing Sync pause command. Include a bounded replacement cause on the first SFU track request after a media connection is replaced.
|
|
6
|
+
|
|
3
7
|
## 5.2.0
|
|
4
8
|
|
|
5
9
|
### board-draw-policy
|
|
@@ -1109,7 +1109,25 @@ var AddCloudflareSFUTracksPathParamsSchema = Schema.Struct({
|
|
|
1109
1109
|
tenant_id: TenantIdSchema
|
|
1110
1110
|
});
|
|
1111
1111
|
var AddCloudflareSFUTracksQueryParamsSchema = Schema.Struct({
|
|
1112
|
-
allow_partial_remote_tracks: Schema.optional(Schema.Boolean)
|
|
1112
|
+
allow_partial_remote_tracks: Schema.optional(Schema.Boolean),
|
|
1113
|
+
replacement_cause: Schema.optional(
|
|
1114
|
+
Schema.Literals([
|
|
1115
|
+
"user_pause",
|
|
1116
|
+
"moderation_pause",
|
|
1117
|
+
"sync_recovery_loss",
|
|
1118
|
+
"discovery_absent",
|
|
1119
|
+
"capture_cleanup",
|
|
1120
|
+
"consent_revoked",
|
|
1121
|
+
"publish_failure",
|
|
1122
|
+
"enable_failure",
|
|
1123
|
+
"projection_mismatch",
|
|
1124
|
+
"forced_pause",
|
|
1125
|
+
"local_signaling_failure",
|
|
1126
|
+
"remote_signaling_failure",
|
|
1127
|
+
"dormant_connection",
|
|
1128
|
+
"access_refresh"
|
|
1129
|
+
])
|
|
1130
|
+
)
|
|
1113
1131
|
});
|
|
1114
1132
|
var AddCloudflareSFUTracksRequestBodySchema = CloudflareSFUTracksRequestSchema;
|
|
1115
1133
|
var AddCloudflareSFUTracksResponseSchema = CloudflareSFUTracksAPIResponseSchema;
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
describeMediaCaptureError,
|
|
7
7
|
grantedMediaFromStream,
|
|
8
8
|
markMediaFailure
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-YIDKETOX.js";
|
|
10
10
|
import {
|
|
11
11
|
AccessGrantError,
|
|
12
12
|
requireParsedAccessGrant
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CJVLTSIO.js";
|
|
14
14
|
import {
|
|
15
15
|
ChatAttachmentMimeTypeSchema,
|
|
16
16
|
ChatReactionSetFrameSchema,
|
|
@@ -2699,6 +2699,7 @@ function createSFUConnectionMediaClient(client) {
|
|
|
2699
2699
|
function bindConnectionMediaClient(client, restart) {
|
|
2700
2700
|
return {
|
|
2701
2701
|
setLocalPublicationTarget: client.setLocalPublicationTarget.bind(client),
|
|
2702
|
+
...client.pauseLocalCapture ? { pauseLocalCapture: client.pauseLocalCapture.bind(client) } : {},
|
|
2702
2703
|
...client.setLocalSourceIntent ? { setLocalSourceIntent: client.setLocalSourceIntent.bind(client) } : {},
|
|
2703
2704
|
...client.closeForcedLocalPublication ? { closeForcedLocalPublication: client.closeForcedLocalPublication.bind(client) } : {},
|
|
2704
2705
|
observeLocalPublications: client.observeLocalPublications.bind(client),
|
|
@@ -2998,7 +2999,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
2998
2999
|
yield* publish();
|
|
2999
3000
|
});
|
|
3000
3001
|
const enqueue = (effect, join = false) => Effect2.gen(function* () {
|
|
3001
|
-
if (model.closed) return yield* Effect2.fail(lifecycleFailure("invalid_state", false, "The Connection scope is closed"));
|
|
3002
|
+
if (model.closed) return yield* Effect2.fail(lifecycleFailure("invalid_state", false, "The Connection scope is closed. Rejoin the call."));
|
|
3002
3003
|
const deferred = yield* Deferred2.make();
|
|
3003
3004
|
yield* Queue.offer(queue, { effect, deferred, join });
|
|
3004
3005
|
return yield* Deferred2.await(deferred);
|
|
@@ -3328,7 +3329,7 @@ var makeConnectionLifecycleLayerFromServices = (options) => Layer3.effect(
|
|
|
3328
3329
|
yield* scheduleRefresh();
|
|
3329
3330
|
})
|
|
3330
3331
|
);
|
|
3331
|
-
const requireOpen = Effect2.suspend(() => model.closed ? Effect2.fail(lifecycleFailure("invalid_state", false, "The Connection scope is closed")) : Effect2.void);
|
|
3332
|
+
const requireOpen = Effect2.suspend(() => model.closed ? Effect2.fail(lifecycleFailure("invalid_state", false, "The Connection scope is closed. Rejoin the call.")) : Effect2.void);
|
|
3332
3333
|
const withFreshAccess = (operation) => Effect2.gen(function* () {
|
|
3333
3334
|
yield* requireOpen;
|
|
3334
3335
|
if (yield* access.requiresRefresh) {
|
|
@@ -4949,7 +4950,7 @@ var MediaProjectionMonitor = class {
|
|
|
4949
4950
|
var MEDIA_SOURCES = ["microphone", "camera", "screen"];
|
|
4950
4951
|
var MEDIA_READY_TIMEOUT_MS = 1e4;
|
|
4951
4952
|
function sourceWorker(enabled) {
|
|
4952
|
-
return { desired: { enabled, revision: 0, controlRevision: -1, previousEnabled: enabled }, result: { revision: 0, status: "confirmed" }, initialRevision: 0, localEnabled: enabled, pause: null, serverSequence: -1, forcedRevision: null, pausedPublicationId: null };
|
|
4953
|
+
return { desired: { enabled, revision: 0, controlRevision: -1, previousEnabled: enabled }, result: { revision: 0, status: "confirmed" }, initialRevision: 0, localEnabled: enabled, pause: null, serverSequence: -1, forcedRevision: null, pausedPublicationId: null, capturePause: "none" };
|
|
4953
4954
|
}
|
|
4954
4955
|
var MediaControllerService = class extends Context6.Service()("@chalk/client/MediaController") {
|
|
4955
4956
|
};
|
|
@@ -4992,6 +4993,7 @@ var MediaControllerRuntime = class {
|
|
|
4992
4993
|
#tracks = /* @__PURE__ */ new Map();
|
|
4993
4994
|
#sources = { microphone: sourceWorker(true), camera: sourceWorker(true), screen: sourceWorker(false) };
|
|
4994
4995
|
#syncLive = false;
|
|
4996
|
+
#disposed = false;
|
|
4995
4997
|
#recovering = null;
|
|
4996
4998
|
#pendingSources = /* @__PURE__ */ new Map();
|
|
4997
4999
|
#pendingCaptures = /* @__PURE__ */ new Set();
|
|
@@ -5024,7 +5026,7 @@ var MediaControllerRuntime = class {
|
|
|
5024
5026
|
changed: () => this.#publish(),
|
|
5025
5027
|
report: (event) => connection.recordTransportDiagnostic(event),
|
|
5026
5028
|
pause: (source, revision) => {
|
|
5027
|
-
if (this.#ports) this.#pausePublication(this.#ports, source, revision);
|
|
5029
|
+
if (this.#ports) this.#pausePublication(this.#ports, source, revision, "projection_mismatch");
|
|
5028
5030
|
}
|
|
5029
5031
|
});
|
|
5030
5032
|
this.#unsubscribeConnection = connection.subscribe(() => this.#handleConnectionChange());
|
|
@@ -5130,8 +5132,9 @@ var MediaControllerRuntime = class {
|
|
|
5130
5132
|
this.#fork(this.#refreshDevices().pipe(Effect5.ignore));
|
|
5131
5133
|
}
|
|
5132
5134
|
dispose() {
|
|
5135
|
+
this.#disposed = true;
|
|
5133
5136
|
this.#projection.reset();
|
|
5134
|
-
for (const check of this.#readinessChecks) check(normalizeMediaError(cancelledCaptureError()));
|
|
5137
|
+
for (const check of this.#readinessChecks) check(normalizeMediaError(this.#cancelledCaptureError()));
|
|
5135
5138
|
this.#cancelCaptures();
|
|
5136
5139
|
for (const unsubscribe of [this.#unsubscribeMedia, this.#unsubscribeRequests, this.#unsubscribePause, this.#unsubscribeSync]) unsubscribe?.();
|
|
5137
5140
|
this.#unsubscribeConnection?.();
|
|
@@ -5161,7 +5164,7 @@ var MediaControllerRuntime = class {
|
|
|
5161
5164
|
const check = (failure2) => {
|
|
5162
5165
|
if (failure2) return resume(Effect5.fail(failure2));
|
|
5163
5166
|
if (this.#sources[source].desired.revision !== revision) return resume(Effect5.void);
|
|
5164
|
-
if (ports && !this.#canUsePorts(ports)) return resume(Effect5.fail(normalizeMediaError(cancelledCaptureError())));
|
|
5167
|
+
if (ports && !this.#canUsePorts(ports)) return resume(Effect5.fail(normalizeMediaError(this.#cancelledCaptureError())));
|
|
5165
5168
|
const state = this.#connection.getSnapshot().state;
|
|
5166
5169
|
const phases = [state, this.#ports?.sync.getSnapshot().connection.phase, this.#store.getSnapshot().connection.status];
|
|
5167
5170
|
if (phases.every((phase) => phase === "live")) return resume(Effect5.void);
|
|
@@ -5210,6 +5213,7 @@ var MediaControllerRuntime = class {
|
|
|
5210
5213
|
const controlRevision = Math.max(state.serverSequence, this.#ports?.sync.getSnapshot().control?.revision ?? -1);
|
|
5211
5214
|
const desired = { enabled, revision: state.desired.revision + 1, controlRevision, options, previousEnabled: state.localEnabled };
|
|
5212
5215
|
state.desired = desired;
|
|
5216
|
+
state.capturePause = "none";
|
|
5213
5217
|
if (!enabled) {
|
|
5214
5218
|
state.localEnabled = false;
|
|
5215
5219
|
this.#pauseTracks(source);
|
|
@@ -5219,8 +5223,8 @@ var MediaControllerRuntime = class {
|
|
|
5219
5223
|
}
|
|
5220
5224
|
const ports = this.#ports;
|
|
5221
5225
|
if (ports) {
|
|
5222
|
-
ports.media.setLocalSourceIntent?.(source, false);
|
|
5223
|
-
this.#retainPause(state, this.#beginLocalPause(ports, source));
|
|
5226
|
+
ports.media.setLocalSourceIntent?.(source, false, "user_pause");
|
|
5227
|
+
this.#retainPause(state, this.#beginLocalPause(ports, source, "user_pause"));
|
|
5224
5228
|
}
|
|
5225
5229
|
}
|
|
5226
5230
|
for (const check of this.#readinessChecks) check();
|
|
@@ -5316,11 +5320,11 @@ var MediaControllerRuntime = class {
|
|
|
5316
5320
|
if (state.desired.revision !== intent.revision) return;
|
|
5317
5321
|
state.result = { revision: intent.revision, status: "failed", cause, cleanup: !intent.enabled || !previous };
|
|
5318
5322
|
if (state.result.cleanup) {
|
|
5319
|
-
if (intent.enabled && source !== "screen") this.#pausePublication(ports, source, intent.revision);
|
|
5323
|
+
if (intent.enabled && source !== "screen") this.#pausePublication(ports, source, intent.revision, "enable_failure");
|
|
5320
5324
|
else {
|
|
5321
5325
|
state.localEnabled = false;
|
|
5322
5326
|
this.#pauseTracks(source);
|
|
5323
|
-
ports.media.setLocalSourceIntent?.(source, false);
|
|
5327
|
+
ports.media.setLocalSourceIntent?.(source, false, "enable_failure");
|
|
5324
5328
|
}
|
|
5325
5329
|
}
|
|
5326
5330
|
});
|
|
@@ -5434,7 +5438,7 @@ var MediaControllerRuntime = class {
|
|
|
5434
5438
|
}
|
|
5435
5439
|
#assertCurrentIntent(source, revision, enabling = true) {
|
|
5436
5440
|
const state = this.#sources[source];
|
|
5437
|
-
if (state.desired.revision !== revision || enabling && state.forcedRevision === revision) throw cancelledCaptureError();
|
|
5441
|
+
if (state.desired.revision !== revision || enabling && state.forcedRevision === revision) throw this.#cancelledCaptureError();
|
|
5438
5442
|
}
|
|
5439
5443
|
#hasConsent(source) {
|
|
5440
5444
|
const state = this.#sources[source];
|
|
@@ -5519,9 +5523,9 @@ var MediaControllerRuntime = class {
|
|
|
5519
5523
|
)
|
|
5520
5524
|
);
|
|
5521
5525
|
}
|
|
5522
|
-
#beginLocalPause(ports, source) {
|
|
5526
|
+
#beginLocalPause(ports, source, cause) {
|
|
5523
5527
|
this.#sources[source].pausedPublicationId = ports.media.getSnapshot().localTracks.find((item) => item.source === source)?.publicationId ?? null;
|
|
5524
|
-
const pause = ports.media.closeForcedLocalPublication ? Promise.resolve().then(() => ports.media.closeForcedLocalPublication(source)) : Effect5.runPromise(this.#localTarget(ports, source, false));
|
|
5528
|
+
const pause = ports.media.closeForcedLocalPublication ? Promise.resolve().then(() => ports.media.closeForcedLocalPublication(source, cause)) : Effect5.runPromise(this.#localTarget(ports, source, false));
|
|
5525
5529
|
void pause.catch(() => void 0);
|
|
5526
5530
|
return pause;
|
|
5527
5531
|
}
|
|
@@ -5594,15 +5598,15 @@ var MediaControllerRuntime = class {
|
|
|
5594
5598
|
return;
|
|
5595
5599
|
}
|
|
5596
5600
|
const state = this.#camera;
|
|
5597
|
-
if (!("owner" in state)) throw cancelledCaptureError();
|
|
5598
|
-
if (state.status === "live" && state.track !== track) throw cancelledCaptureError();
|
|
5601
|
+
if (!("owner" in state)) throw this.#cancelledCaptureError();
|
|
5602
|
+
if (state.status === "live" && state.track !== track) throw this.#cancelledCaptureError();
|
|
5599
5603
|
const onEnded = () => this.#handleCameraEnded(track);
|
|
5600
5604
|
this.#transitionCamera({ status: "live", owner: state.owner, track, publication: this.#ports?.media ?? "initial", onEnded });
|
|
5601
5605
|
}
|
|
5602
5606
|
#handleCameraEnded(track) {
|
|
5603
5607
|
const state = this.#camera;
|
|
5604
5608
|
if (state.status !== "live" || state.track !== track) return;
|
|
5605
|
-
if (this.#ports && state.publication === this.#ports.media) this.#pausePublication(this.#ports, "camera", this.#sources.camera.desired.revision);
|
|
5609
|
+
if (this.#ports && state.publication === this.#ports.media) this.#pausePublication(this.#ports, "camera", this.#sources.camera.desired.revision, "capture_cleanup");
|
|
5606
5610
|
this.#transitionCamera({ status: "ended" });
|
|
5607
5611
|
}
|
|
5608
5612
|
#watchMicrophone(track) {
|
|
@@ -5615,7 +5619,7 @@ var MediaControllerRuntime = class {
|
|
|
5615
5619
|
if (track.muted || track.readyState === "ended") {
|
|
5616
5620
|
track.enabled = false;
|
|
5617
5621
|
const state = this.#sources.microphone;
|
|
5618
|
-
if (this.#ports) this.#pausePublication(this.#ports, "microphone", state.desired.revision);
|
|
5622
|
+
if (this.#ports) this.#pausePublication(this.#ports, "microphone", state.desired.revision, "capture_cleanup");
|
|
5619
5623
|
else {
|
|
5620
5624
|
state.forcedRevision = state.desired.revision;
|
|
5621
5625
|
state.localEnabled = false;
|
|
@@ -5694,7 +5698,7 @@ var MediaControllerRuntime = class {
|
|
|
5694
5698
|
references: 0,
|
|
5695
5699
|
cancel: () => {
|
|
5696
5700
|
abort.abort();
|
|
5697
|
-
Deferred3.doneUnsafe(owner.result, Effect5.fail(cancelledCaptureError()));
|
|
5701
|
+
Deferred3.doneUnsafe(owner.result, Effect5.fail(this.#cancelledCaptureError()));
|
|
5698
5702
|
}
|
|
5699
5703
|
};
|
|
5700
5704
|
this.#transitionCamera({ status: "capturing", owner });
|
|
@@ -5706,13 +5710,13 @@ var MediaControllerRuntime = class {
|
|
|
5706
5710
|
const state = this.#camera;
|
|
5707
5711
|
if (abort.signal.aborted || !("owner" in state) || state.owner !== owner) {
|
|
5708
5712
|
track.stop();
|
|
5709
|
-
throw cancelledCaptureError();
|
|
5713
|
+
throw this.#cancelledCaptureError();
|
|
5710
5714
|
}
|
|
5711
5715
|
const onEnded = () => this.#handleCameraEnded(track);
|
|
5712
5716
|
this.#transitionCamera({ status: "live", owner, track, publication: state.status === "live" ? state.publication : null, onEnded });
|
|
5713
5717
|
if (track.readyState !== "live") {
|
|
5714
5718
|
this.#transitionCamera({ status: "ended" });
|
|
5715
|
-
throw cancelledCaptureError();
|
|
5719
|
+
throw this.#cancelledCaptureError();
|
|
5716
5720
|
}
|
|
5717
5721
|
},
|
|
5718
5722
|
catch: (cause) => cause
|
|
@@ -5735,7 +5739,7 @@ var MediaControllerRuntime = class {
|
|
|
5735
5739
|
if (owner.references === 0 && "owner" in current && current.owner === owner && (current.status !== "live" || current.publication === null)) this.#transitionCamera({ status: "ended" });
|
|
5736
5740
|
};
|
|
5737
5741
|
return Deferred3.await(owner.result).pipe(
|
|
5738
|
-
Effect5.flatMap((track) => track.readyState === "live" ? Effect5.succeed({ track, release }) : Effect5.fail(cancelledCaptureError())),
|
|
5742
|
+
Effect5.flatMap((track) => track.readyState === "live" ? Effect5.succeed({ track, release }) : Effect5.fail(this.#cancelledCaptureError())),
|
|
5739
5743
|
Effect5.onExit(
|
|
5740
5744
|
(exit) => Effect5.sync(() => {
|
|
5741
5745
|
if (Exit2.isFailure(exit)) release();
|
|
@@ -5768,25 +5772,25 @@ var MediaControllerRuntime = class {
|
|
|
5768
5772
|
const cancel = () => {
|
|
5769
5773
|
cancelled = true;
|
|
5770
5774
|
stopStream(captured);
|
|
5771
|
-
Deferred3.doneUnsafe(cancellation, Effect5.fail(cancelledCaptureError()));
|
|
5775
|
+
Deferred3.doneUnsafe(cancellation, Effect5.fail(this.#cancelledCaptureError()));
|
|
5772
5776
|
};
|
|
5773
5777
|
this.#pendingCaptures.add(cancel);
|
|
5774
5778
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
5775
5779
|
if (signal?.aborted) cancel();
|
|
5776
5780
|
return foreign2(async () => {
|
|
5777
|
-
if (cancelled) throw cancelledCaptureError();
|
|
5781
|
+
if (cancelled) throw this.#cancelledCaptureError();
|
|
5778
5782
|
const stream = await operation();
|
|
5779
5783
|
captured = stream;
|
|
5780
5784
|
if (cancelled || this.#ports?.media.getSnapshot().connection.phase === "stopped") {
|
|
5781
5785
|
stopStream(stream);
|
|
5782
|
-
throw cancelledCaptureError();
|
|
5786
|
+
throw this.#cancelledCaptureError();
|
|
5783
5787
|
}
|
|
5784
5788
|
return stream;
|
|
5785
5789
|
}).pipe(
|
|
5786
5790
|
Effect5.mapError(onCaptureError),
|
|
5787
5791
|
Effect5.flatMap(use),
|
|
5788
5792
|
Effect5.raceFirst(Deferred3.await(cancellation)),
|
|
5789
|
-
Effect5.tap(() => cancelled ? Effect5.fail(cancelledCaptureError()) : Effect5.void),
|
|
5793
|
+
Effect5.tap(() => cancelled ? Effect5.fail(this.#cancelledCaptureError()) : Effect5.void),
|
|
5790
5794
|
Effect5.onExit(
|
|
5791
5795
|
(exit) => Effect5.sync(() => {
|
|
5792
5796
|
this.#pendingCaptures.delete(cancel);
|
|
@@ -5860,23 +5864,48 @@ var MediaControllerRuntime = class {
|
|
|
5860
5864
|
const failure2 = isPermissionDenied(input.cause) || captureWasUnavailable ? captureError(input.cause, input.action, input.permissionMessage) : input.cause;
|
|
5861
5865
|
return rollback.pipe(Effect5.andThen(Effect5.sync(() => this.#publish())), Effect5.andThen(Effect5.fail(failure2)));
|
|
5862
5866
|
}
|
|
5867
|
+
#cancelledCaptureError() {
|
|
5868
|
+
return new ConnectionError({ code: "invalid_state", action: null, recoverable: !this.#disposed, message: this.#disposed ? "This media client has been disposed. Rejoin the call." : "Media capture was cancelled before its result could be used" });
|
|
5869
|
+
}
|
|
5863
5870
|
#canUsePorts(ports) {
|
|
5864
5871
|
return this.#ports?.sync === ports.sync && this.#ports.media === ports.media && ports.media.getSnapshot().connection.phase !== "stopped";
|
|
5865
5872
|
}
|
|
5866
5873
|
#assertActivePorts(ports, action) {
|
|
5867
5874
|
if (this.#connection.getSnapshot().state === "live" && this.#canUsePorts(ports)) return;
|
|
5868
|
-
throw new ConnectionError({ code: "invalid_state", action, recoverable:
|
|
5875
|
+
throw new ConnectionError({ code: "invalid_state", action, recoverable: !this.#disposed, message: this.#disposed ? "This media client has been disposed. Rejoin the call." : `${action} belongs to an inactive connection` });
|
|
5869
5876
|
}
|
|
5870
|
-
#pausePublication(ports, source, revision) {
|
|
5877
|
+
#pausePublication(ports, source, revision, cause) {
|
|
5871
5878
|
const state = this.#sources[source];
|
|
5872
5879
|
if (state.desired.revision !== revision || state.forcedRevision === revision || ports.sync.getSnapshot().control?.status === "ended") return;
|
|
5873
5880
|
state.forcedRevision = revision;
|
|
5874
5881
|
state.localEnabled = false;
|
|
5875
5882
|
this.#pauseTracks(source);
|
|
5876
|
-
ports.media.
|
|
5877
|
-
|
|
5883
|
+
if (cause === "capture_cleanup" && source !== "screen" && ports.media.pauseLocalCapture) {
|
|
5884
|
+
state.capturePause = "pending";
|
|
5885
|
+
this.#retainPause(state, ports.media.pauseLocalCapture(source));
|
|
5886
|
+
this.#notifyCapturePauses(ports);
|
|
5887
|
+
} else {
|
|
5888
|
+
ports.media.setLocalSourceIntent?.(source, false, cause);
|
|
5889
|
+
this.#retainPause(state, this.#beginLocalPause(ports, source, cause));
|
|
5890
|
+
}
|
|
5878
5891
|
this.#publish();
|
|
5879
5892
|
}
|
|
5893
|
+
#notifyCapturePauses(ports) {
|
|
5894
|
+
for (const source of ["microphone", "camera"]) {
|
|
5895
|
+
const state = this.#sources[source];
|
|
5896
|
+
if (state.capturePause === "pending") this.#retainPause(state, this.#announceCapturePause(ports, source));
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
async #announceCapturePause(ports, source) {
|
|
5900
|
+
const snapshot = ports.sync.getSnapshot();
|
|
5901
|
+
if (snapshot.connection.phase !== "live") return;
|
|
5902
|
+
const owned = ports.media.getSnapshot().localTracks.find((item) => item.source === source);
|
|
5903
|
+
const projected = snapshot.media?.items.find((item) => item.participantId === snapshot.participantId && item.source === source);
|
|
5904
|
+
if (!owned?.publicationId || !projected?.enabled || projected.publicationId !== owned.publicationId) return;
|
|
5905
|
+
this.#sources[source].capturePause = "announced";
|
|
5906
|
+
const send = source === "microphone" ? ports.sync.setMicrophoneEnabled : ports.sync.setCameraEnabled;
|
|
5907
|
+
await send.call(ports.sync, false);
|
|
5908
|
+
}
|
|
5880
5909
|
#bind(ports) {
|
|
5881
5910
|
if (ports?.media.getSnapshot().connection.phase === "stopped") this.#cancelCaptures();
|
|
5882
5911
|
if (ports && this.#ports?.sync === ports.sync && this.#ports.media === ports.media) return;
|
|
@@ -5914,10 +5943,11 @@ var MediaControllerRuntime = class {
|
|
|
5914
5943
|
const state = this.#sources[pause.source];
|
|
5915
5944
|
if (pause.revision <= state.serverSequence) return;
|
|
5916
5945
|
state.serverSequence = pause.revision;
|
|
5917
|
-
if (pause.revision > state.desired.controlRevision) this.#pausePublication(ports, pause.source, state.desired.revision);
|
|
5946
|
+
if (pause.revision > state.desired.controlRevision) this.#pausePublication(ports, pause.source, state.desired.revision, "moderation_pause");
|
|
5918
5947
|
}) ?? null;
|
|
5919
5948
|
}
|
|
5920
5949
|
#rememberRecoveryIntents() {
|
|
5950
|
+
for (const state of Object.values(this.#sources)) if (state.capturePause === "announced") state.capturePause = "pending";
|
|
5921
5951
|
if (!this.#ports) return;
|
|
5922
5952
|
this.#recovering ??= new Map(MEDIA_SOURCES.filter((source) => this.#sources[source].desired.enabled).map((source) => [source, this.#sources[source].desired.revision]));
|
|
5923
5953
|
}
|
|
@@ -5926,7 +5956,7 @@ var MediaControllerRuntime = class {
|
|
|
5926
5956
|
this.#recovering = null;
|
|
5927
5957
|
for (const [source, revision] of recovering ?? []) {
|
|
5928
5958
|
const current = snapshot.media?.items.find((item) => item.participantId === snapshot.participantId && item.source === source);
|
|
5929
|
-
if (!current?.enabled) this.#pausePublication(ports, source, revision);
|
|
5959
|
+
if (!current?.enabled) this.#pausePublication(ports, source, revision, "sync_recovery_loss");
|
|
5930
5960
|
}
|
|
5931
5961
|
}
|
|
5932
5962
|
#subscribeSync(ports) {
|
|
@@ -5934,6 +5964,7 @@ var MediaControllerRuntime = class {
|
|
|
5934
5964
|
this.#removeRequestsFromMissingParticipants(snapshot);
|
|
5935
5965
|
this.#applyRemoteMediaState(ports, snapshot);
|
|
5936
5966
|
this.#reconcileIntents();
|
|
5967
|
+
this.#notifyCapturePauses(ports);
|
|
5937
5968
|
this.#publish();
|
|
5938
5969
|
});
|
|
5939
5970
|
}
|
|
@@ -6029,8 +6060,8 @@ var MediaControllerRuntime = class {
|
|
|
6029
6060
|
const state = this.#sources[source];
|
|
6030
6061
|
if (!this.#hasConsent(source) && publication?.enabled && state.pausedPublicationId !== publication.publicationId) {
|
|
6031
6062
|
this.#pauseTracks(source);
|
|
6032
|
-
ports.media.setLocalSourceIntent?.(source, false);
|
|
6033
|
-
this.#retainPause(state, this.#beginLocalPause(ports, source));
|
|
6063
|
+
ports.media.setLocalSourceIntent?.(source, false, "consent_revoked");
|
|
6064
|
+
this.#retainPause(state, this.#beginLocalPause(ports, source, "consent_revoked"));
|
|
6034
6065
|
}
|
|
6035
6066
|
}
|
|
6036
6067
|
#observeProjection(ports, source) {
|
|
@@ -6094,9 +6125,6 @@ function confirmedLocalMediaState(track, desired, pending, confirmed, live, conv
|
|
|
6094
6125
|
if (!live && track && liveTrack(track)) return "requesting";
|
|
6095
6126
|
return confirmed ? "enabled" : pending ? "requesting" : "disabled";
|
|
6096
6127
|
}
|
|
6097
|
-
function cancelledCaptureError() {
|
|
6098
|
-
return new ConnectionError({ code: "invalid_state", action: null, recoverable: false, message: "Media capture was cancelled before its result could be used" });
|
|
6099
|
-
}
|
|
6100
6128
|
function foreign2(operation) {
|
|
6101
6129
|
return Effect5.tryPromise({ try: operation, catch: (cause) => cause });
|
|
6102
6130
|
}
|
|
@@ -7193,7 +7221,7 @@ var SpaceClientCore = class {
|
|
|
7193
7221
|
}
|
|
7194
7222
|
}
|
|
7195
7223
|
#requireActive() {
|
|
7196
|
-
if (this.#disposed) throw new SpaceClientError({ code: "connection.invalid_state", recoverable: false, message: "The SpaceClient has been disposed" });
|
|
7224
|
+
if (this.#disposed) throw new SpaceClientError({ code: "connection.invalid_state", recoverable: false, message: "The SpaceClient has been disposed. Rejoin the call." });
|
|
7197
7225
|
}
|
|
7198
7226
|
};
|
|
7199
7227
|
function liveClock(dependencies) {
|
|
@@ -238,6 +238,7 @@ var INVALID_PUBLICATION_SIGNATURE = "\0invalid";
|
|
|
238
238
|
var CONNECTION_TIMEOUT_MS = 8e3;
|
|
239
239
|
var NEGOTIATION_TIMEOUT_MS = 35e3;
|
|
240
240
|
var PEER_OPERATION_TIMEOUT_MS = 8e3;
|
|
241
|
+
var CAPTURE_REUSE_WINDOW_MS = 2e4;
|
|
241
242
|
var CloudflareSFUClient = class {
|
|
242
243
|
#localListeners = /* @__PURE__ */ new Set();
|
|
243
244
|
#onError;
|
|
@@ -250,7 +251,7 @@ var CloudflareSFUClient = class {
|
|
|
250
251
|
#remoteListeners = /* @__PURE__ */ new Set();
|
|
251
252
|
#snapshotListeners = /* @__PURE__ */ new Set();
|
|
252
253
|
#failureOwners = /* @__PURE__ */ new WeakMap();
|
|
253
|
-
#locallyPausedSources = /* @__PURE__ */ new
|
|
254
|
+
#locallyPausedSources = /* @__PURE__ */ new Map();
|
|
254
255
|
#localTracks = /* @__PURE__ */ new Map();
|
|
255
256
|
#ownedPublications = /* @__PURE__ */ new Set();
|
|
256
257
|
#remoteTracks = /* @__PURE__ */ new Map();
|
|
@@ -259,9 +260,11 @@ var CloudflareSFUClient = class {
|
|
|
259
260
|
#disposeConnectionObservation;
|
|
260
261
|
#connectionEpoch = 0;
|
|
261
262
|
#retiredLocalConnection = null;
|
|
263
|
+
#retiredLocalCause = "forced_pause";
|
|
264
|
+
#replacementCause;
|
|
262
265
|
#cursor = null;
|
|
263
266
|
#remotePullIncomplete = false;
|
|
264
|
-
#
|
|
267
|
+
#remotePullRetries = /* @__PURE__ */ new Map();
|
|
265
268
|
#remoteParticipantIds = null;
|
|
266
269
|
#remoteParticipantRevision = 0;
|
|
267
270
|
#subscribedPublicationIds = /* @__PURE__ */ new Set();
|
|
@@ -308,7 +311,7 @@ var CloudflareSFUClient = class {
|
|
|
308
311
|
return subscribeSnapshot(this.#snapshotListeners, listener);
|
|
309
312
|
}
|
|
310
313
|
remotePublicationResumed(publicationId) {
|
|
311
|
-
this.#
|
|
314
|
+
this.#remotePullRetries.delete(publicationId);
|
|
312
315
|
this.remotePublicationsChanged();
|
|
313
316
|
}
|
|
314
317
|
remoteParticipantsChanged(participantIds) {
|
|
@@ -363,9 +366,9 @@ var CloudflareSFUClient = class {
|
|
|
363
366
|
}
|
|
364
367
|
async #clearPreparedTrack(state, stopTrack = true) {
|
|
365
368
|
const source = state.source;
|
|
366
|
-
if (source === "screen" || source === "screen_audio"
|
|
369
|
+
if (source === "screen" || source === "screen_audio") {
|
|
367
370
|
try {
|
|
368
|
-
this.#pauseLocalSource(source);
|
|
371
|
+
this.#pauseLocalSource(source, "capture_cleanup");
|
|
369
372
|
await this.#retireOwnedPublications();
|
|
370
373
|
} finally {
|
|
371
374
|
this.#removeOwnedLocalTrack(state, stopTrack);
|
|
@@ -431,6 +434,10 @@ var CloudflareSFUClient = class {
|
|
|
431
434
|
const resolved = resolveMediaTarget(this.#participantId, this.#stopped, this.#localTracks, target);
|
|
432
435
|
if (resolved.kind === "result" && resolved.result.outcome !== "satisfied") return resolved.result;
|
|
433
436
|
const outcome = resolved.kind === "state" && resolved.value.enabled ? "confirmed" : "satisfied";
|
|
437
|
+
if (target.source !== "screen" && this.#locallyPausedSources.get(target.source) === "capture_cleanup") {
|
|
438
|
+
await this.pauseLocalCapture(target.source);
|
|
439
|
+
return { outcome, errorCode: null };
|
|
440
|
+
}
|
|
434
441
|
this.#pauseLocalSource(target.source);
|
|
435
442
|
try {
|
|
436
443
|
await this.#retireOwnedPublications();
|
|
@@ -449,26 +456,31 @@ var CloudflareSFUClient = class {
|
|
|
449
456
|
await this.#retireUnavailableLocalPublication(state);
|
|
450
457
|
if (this.#locallyPausedSources.has(target.source)) return { outcome: "terminal_failure", errorCode: "local_source_paused" };
|
|
451
458
|
if (state.enabled) return { outcome: "satisfied", errorCode: null };
|
|
452
|
-
await this.#enablePreparedTrack(state);
|
|
459
|
+
if (!await this.#enablePreparedTrack(state)) return { outcome: "terminal_failure", errorCode: "local_source_paused" };
|
|
453
460
|
return { outcome: "confirmed", errorCode: null };
|
|
454
461
|
} catch (error) {
|
|
455
462
|
if (generation === this.#generation && !this.#stopped) this.#reportError(error);
|
|
456
463
|
return mediaTargetFailure(error);
|
|
457
464
|
}
|
|
458
465
|
}
|
|
459
|
-
|
|
466
|
+
async pauseLocalCapture(source) {
|
|
467
|
+
this.#locallyPausedSources.set(source, "capture_cleanup");
|
|
468
|
+
const state = this.#localTracks.get(source);
|
|
469
|
+
if (state) await this.#disablePreparedTrack(state);
|
|
470
|
+
}
|
|
471
|
+
setLocalSourceIntent(source, enabled, cause = "forced_pause") {
|
|
460
472
|
const sources = localSourceGroup(source);
|
|
461
473
|
for (const item of sources) {
|
|
462
474
|
if (enabled) this.#locallyPausedSources.delete(item);
|
|
463
|
-
else this.#locallyPausedSources.
|
|
475
|
+
else this.#locallyPausedSources.set(item, cause);
|
|
464
476
|
}
|
|
465
|
-
if (!enabled) this.#pauseLocalSource(source);
|
|
477
|
+
if (!enabled) this.#pauseLocalSource(source, cause);
|
|
466
478
|
return enabled ? Promise.resolve() : this.#retireOwnedPublications();
|
|
467
479
|
}
|
|
468
|
-
async closeForcedLocalPublication(source) {
|
|
480
|
+
async closeForcedLocalPublication(source, cause = "forced_pause") {
|
|
469
481
|
const sources = localSourceGroup(source);
|
|
470
482
|
const ownsPublication = [...this.#ownedPublications].some((item) => sources.includes(item.state.source));
|
|
471
|
-
await this.setLocalSourceIntent(source, false);
|
|
483
|
+
await this.setLocalSourceIntent(source, false, cause);
|
|
472
484
|
if (!ownsPublication || !this.#replaceMediaConnection) return;
|
|
473
485
|
const generation = this.#generation;
|
|
474
486
|
await this.#serializeSDP(async () => {
|
|
@@ -491,16 +503,15 @@ var CloudflareSFUClient = class {
|
|
|
491
503
|
const authoritative = await this.#signal(() => this.#requireTransport().listPublications());
|
|
492
504
|
if (authoritative.publications.some((publication) => publication.publicationId === publicationId)) return;
|
|
493
505
|
if (state.providerPublicationId === publicationId) {
|
|
494
|
-
this.#pauseLocalSource(state.source);
|
|
506
|
+
this.#pauseLocalSource(state.source, "discovery_absent");
|
|
495
507
|
await this.#retireOwnedPublications();
|
|
496
508
|
}
|
|
497
509
|
}
|
|
498
|
-
#pauseLocalSource(source) {
|
|
510
|
+
#pauseLocalSource(source, cause = "forced_pause") {
|
|
499
511
|
const sources = localSourceGroup(source);
|
|
500
512
|
for (const publication of this.#ownedPublications) {
|
|
501
513
|
if (!sources.includes(publication.state.source)) continue;
|
|
502
|
-
this.#markPublicationRetiring(publication);
|
|
503
|
-
this.#retiredLocalConnection = this.#connection;
|
|
514
|
+
this.#markPublicationRetiring(publication, cause);
|
|
504
515
|
}
|
|
505
516
|
for (const state of this.#localTracks.values()) {
|
|
506
517
|
if (!sources.includes(state.source)) continue;
|
|
@@ -510,7 +521,7 @@ var CloudflareSFUClient = class {
|
|
|
510
521
|
this.#publishSnapshot();
|
|
511
522
|
this.#emitLocal();
|
|
512
523
|
}
|
|
513
|
-
#retireOwnedPublications() {
|
|
524
|
+
#retireOwnedPublications(reportFailure = this.#reportError.bind(this)) {
|
|
514
525
|
const publications = [...this.#ownedPublications].filter((item) => item.lifecycle.kind === "retiring" && item.lifecycle.handle);
|
|
515
526
|
const retirement = (async () => {
|
|
516
527
|
const errors = [];
|
|
@@ -519,7 +530,7 @@ var CloudflareSFUClient = class {
|
|
|
519
530
|
await this.#serializeSDP(() => this.#closeOwnedPublication(publication), true);
|
|
520
531
|
} catch (error) {
|
|
521
532
|
errors.push(error);
|
|
522
|
-
if (!this.#stopped)
|
|
533
|
+
if (!this.#stopped) reportFailure(error);
|
|
523
534
|
}
|
|
524
535
|
}
|
|
525
536
|
if (this.#stopped && this.#ownedPublications.size === 0) this.#transport = void 0;
|
|
@@ -529,15 +540,24 @@ var CloudflareSFUClient = class {
|
|
|
529
540
|
});
|
|
530
541
|
return this.#retirement = retirement;
|
|
531
542
|
}
|
|
532
|
-
#markPublicationRetiring(publication) {
|
|
543
|
+
#markPublicationRetiring(publication, cause = "publish_failure") {
|
|
533
544
|
const phase = publication.lifecycle;
|
|
534
|
-
if (phase.kind === "pending" || phase.kind === "confirmed") publication.lifecycle = { kind: "retiring", handle: phase.kind === "confirmed" ? phase.handle : null, detached: false };
|
|
545
|
+
if (phase.kind === "pending" || phase.kind === "confirmed") publication.lifecycle = { kind: "retiring", handle: phase.kind === "confirmed" ? phase.handle : null, detached: false, cause };
|
|
546
|
+
}
|
|
547
|
+
#markBundledTransportRetiring(publication) {
|
|
548
|
+
const bundleTag = /(?:^|\r?\n)a=group:BUNDLE[ \t]+(\S+)/.exec(publication.connection.localDescription?.sdp ?? "")?.[1];
|
|
549
|
+
if (!bundleTag || publication.transceiver.mid !== null && publication.transceiver.mid === bundleTag) {
|
|
550
|
+
if (this.#retiredLocalConnection === publication.connection) return;
|
|
551
|
+
this.#retiredLocalConnection = publication.connection;
|
|
552
|
+
if (publication.lifecycle.kind === "retiring") this.#retiredLocalCause = publication.lifecycle.cause;
|
|
553
|
+
}
|
|
535
554
|
}
|
|
536
555
|
async #closeOwnedPublication(publication) {
|
|
537
556
|
const phase = publication.lifecycle;
|
|
538
557
|
if (phase.kind !== "retiring" || !phase.handle) return;
|
|
539
558
|
const handle = phase.handle;
|
|
540
559
|
const { connection, connectionId, transceiver, state } = publication;
|
|
560
|
+
this.#markBundledTransportRetiring(publication);
|
|
541
561
|
let closed = connection.connectionState === "closed";
|
|
542
562
|
try {
|
|
543
563
|
if (!phase.detached && !closed) {
|
|
@@ -574,13 +594,16 @@ var CloudflareSFUClient = class {
|
|
|
574
594
|
this.#requireActive();
|
|
575
595
|
const options = "connectionId" in input ? { bootstrap: input } : input;
|
|
576
596
|
validateBootstrap(options.bootstrap);
|
|
577
|
-
if (options.bootstrap.connectionId !== this.#bootstrap.connectionId)
|
|
597
|
+
if (options.bootstrap.connectionId !== this.#bootstrap.connectionId) {
|
|
598
|
+
this.#fenceLocalPublications();
|
|
599
|
+
this.#replacementCause = "access_refresh";
|
|
600
|
+
}
|
|
578
601
|
const generation = ++this.#generation;
|
|
579
602
|
const connectionEpoch = ++this.#connectionEpoch;
|
|
580
603
|
this.#polling = false;
|
|
581
604
|
this.#pollAfterCurrent = false;
|
|
582
605
|
this.#clearPoll();
|
|
583
|
-
this.#
|
|
606
|
+
this.#remotePullRetries.clear();
|
|
584
607
|
this.#disposeConnection(false);
|
|
585
608
|
for (const publication of this.#ownedPublications) this.#markPublicationRetiring(publication);
|
|
586
609
|
void this.#retireOwnedPublications();
|
|
@@ -622,7 +645,7 @@ var CloudflareSFUClient = class {
|
|
|
622
645
|
this.#generation++;
|
|
623
646
|
this.#connectionEpoch++;
|
|
624
647
|
this.#clearPoll();
|
|
625
|
-
this.#
|
|
648
|
+
this.#remotePullRetries.clear();
|
|
626
649
|
this.#polling = false;
|
|
627
650
|
this.#pollAfterCurrent = false;
|
|
628
651
|
this.#disposeConnection(true);
|
|
@@ -645,35 +668,64 @@ var CloudflareSFUClient = class {
|
|
|
645
668
|
}
|
|
646
669
|
async #enablePreparedTrack(state) {
|
|
647
670
|
const generation = this.#generation;
|
|
648
|
-
state.desiredEnabled = true;
|
|
649
|
-
state.track.enabled = true;
|
|
650
671
|
try {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (!state.enabled) {
|
|
657
|
-
this.#pauseLocalSource(state.source);
|
|
658
|
-
await this.#retireOwnedPublications();
|
|
659
|
-
}
|
|
660
|
-
this.#publishSnapshot();
|
|
661
|
-
this.#emitLocal();
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
672
|
+
await this.#retireExpiredCapture(state);
|
|
673
|
+
if (this.#locallyPausedSources.has(state.source)) return false;
|
|
674
|
+
state.desiredEnabled = true;
|
|
675
|
+
state.track.enabled = true;
|
|
676
|
+
if (state.providerPublicationId && state.transceiver) return await this.#reattachLocalTrack(state, state.transceiver);
|
|
664
677
|
await this.#publishPreparedTracks([state], generation);
|
|
665
678
|
if (this.#remoteRecoveryGeneration !== generation) this.#replacementAttemptedGeneration = null;
|
|
679
|
+
return state.enabled;
|
|
666
680
|
} catch (error) {
|
|
667
681
|
if (generation === this.#generation) {
|
|
668
|
-
this.#pauseLocalSource(state.source);
|
|
682
|
+
this.#pauseLocalSource(state.source, "enable_failure");
|
|
669
683
|
await this.#retireOwnedPublications().catch(() => {
|
|
670
684
|
});
|
|
671
685
|
}
|
|
672
686
|
throw error;
|
|
673
687
|
}
|
|
674
688
|
}
|
|
689
|
+
async #retireExpiredCapture(state) {
|
|
690
|
+
const publication = [...this.#ownedPublications].find((item) => item.transceiver === state.transceiver && item.lifecycle.kind === "confirmed");
|
|
691
|
+
if (!publication || publication.detachedAt === null || Date.now() - publication.detachedAt < CAPTURE_REUSE_WINDOW_MS) return;
|
|
692
|
+
this.#pauseLocalSource(state.source, "capture_cleanup");
|
|
693
|
+
try {
|
|
694
|
+
await this.#retireOwnedPublications((error) => {
|
|
695
|
+
if (!this.#isExpiredRetiredConnection(error)) this.#reportError(error);
|
|
696
|
+
});
|
|
697
|
+
} catch (error) {
|
|
698
|
+
if (!this.#isExpiredRetiredConnection(error)) throw error;
|
|
699
|
+
this.#fenceLocalPublications();
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
#isExpiredRetiredConnection(error) {
|
|
703
|
+
if (!this.#replaceMediaConnection || this.#retiredLocalConnection !== this.#connection) return false;
|
|
704
|
+
if (error instanceof AggregateError) return error.errors.every((cause) => this.#isRetryableConnectionFailure(cause));
|
|
705
|
+
return this.#isRetryableConnectionFailure(error);
|
|
706
|
+
}
|
|
707
|
+
async #reattachLocalTrack(state, transceiver) {
|
|
708
|
+
await this.#boundPeerOperation(transceiver.sender.replaceTrack(state.track));
|
|
709
|
+
const cause = this.#locallyPausedSources.get(state.source);
|
|
710
|
+
state.enabled = cause === void 0;
|
|
711
|
+
state.desiredEnabled = state.enabled;
|
|
712
|
+
state.track.enabled = state.enabled;
|
|
713
|
+
if (cause === "capture_cleanup") await this.#disablePreparedTrack(state);
|
|
714
|
+
else if (cause) {
|
|
715
|
+
this.#pauseLocalSource(state.source, cause);
|
|
716
|
+
await this.#retireOwnedPublications();
|
|
717
|
+
} else {
|
|
718
|
+
for (const publication of this.#ownedPublications) if (publication.transceiver === transceiver) publication.detachedAt = null;
|
|
719
|
+
}
|
|
720
|
+
this.#publishSnapshot();
|
|
721
|
+
this.#emitLocal();
|
|
722
|
+
return state.enabled;
|
|
723
|
+
}
|
|
675
724
|
async #disablePreparedTrack(state) {
|
|
676
725
|
const transceiver = state.transceiver;
|
|
726
|
+
for (const publication of this.#ownedPublications) {
|
|
727
|
+
if (publication.transceiver === transceiver && publication.lifecycle.kind === "confirmed") publication.detachedAt ??= Date.now();
|
|
728
|
+
}
|
|
677
729
|
state.desiredEnabled = false;
|
|
678
730
|
state.track.enabled = false;
|
|
679
731
|
const detach = transceiver ? this.#boundPeerOperation(transceiver.sender.replaceTrack(null)) : Promise.resolve();
|
|
@@ -691,7 +743,7 @@ var CloudflareSFUClient = class {
|
|
|
691
743
|
await this.#publishPreparedTracksSerialized(pending, generation, this.#connection, this.#bootstrap.connectionId);
|
|
692
744
|
} catch (error) {
|
|
693
745
|
if (!this.#replaceMediaConnection || !this.#isRetryableConnectionFailure(error) || this.#replacementAttemptedGeneration === generation) throw error;
|
|
694
|
-
await this.#replaceMediaConnectionForRecovery(generation);
|
|
746
|
+
await this.#replaceMediaConnectionForRecovery(generation, "local_signaling_failure");
|
|
695
747
|
await this.#publishPreparedTracksSerialized(
|
|
696
748
|
[...this.#localTracks.values()].filter((state) => (state.desiredEnabled || states.includes(state)) && state.track.readyState !== "ended"),
|
|
697
749
|
generation,
|
|
@@ -713,14 +765,14 @@ var CloudflareSFUClient = class {
|
|
|
713
765
|
const authoritative = await this.#signal(() => this.#requireTransport().listPublications());
|
|
714
766
|
this.#pauseUnavailableOtherSources(authoritative.publications, states);
|
|
715
767
|
this.#replacementAttemptedGeneration = null;
|
|
716
|
-
await this.#replaceMediaConnectionForRecovery(generation);
|
|
768
|
+
await this.#replaceMediaConnectionForRecovery(generation, this.#retiredLocalCause);
|
|
717
769
|
return [...this.#localTracks.values()].filter((state) => (state.desiredEnabled || states.includes(state)) && state.track.readyState !== "ended");
|
|
718
770
|
}
|
|
719
771
|
#pauseUnavailableOtherSources(publications, states) {
|
|
720
772
|
for (const state of this.#localTracks.values()) {
|
|
721
773
|
if (states.includes(state) || !state.providerPublicationId) continue;
|
|
722
774
|
if (publications.some((publication) => publication.publicationId === state.providerPublicationId)) continue;
|
|
723
|
-
this.#pauseLocalSource(state.source);
|
|
775
|
+
this.#pauseLocalSource(state.source, "discovery_absent");
|
|
724
776
|
}
|
|
725
777
|
void this.#retireOwnedPublications();
|
|
726
778
|
}
|
|
@@ -750,7 +802,7 @@ var CloudflareSFUClient = class {
|
|
|
750
802
|
}
|
|
751
803
|
async #prepareLocalPublication(connection, state, generation) {
|
|
752
804
|
const transceiver = state.transceiver ?? this.#addLocalTransceiver(connection, state);
|
|
753
|
-
const publication = { state, transceiver, trackName: `${state.source}-${globalThis.crypto.randomUUID()}`, connection, connectionId: this.#bootstrap.connectionId, lifecycle: { kind: "pending" } };
|
|
805
|
+
const publication = { state, transceiver, trackName: `${state.source}-${globalThis.crypto.randomUUID()}`, connection, connectionId: this.#bootstrap.connectionId, detachedAt: null, lifecycle: { kind: "pending" } };
|
|
754
806
|
this.#ownedPublications.add(publication);
|
|
755
807
|
if (state.transceiver) await this.#boundPeerOperation(transceiver.sender.replaceTrack(state.track));
|
|
756
808
|
this.#requireGeneration(generation);
|
|
@@ -798,7 +850,7 @@ var CloudflareSFUClient = class {
|
|
|
798
850
|
source: state.source
|
|
799
851
|
})
|
|
800
852
|
);
|
|
801
|
-
const response = await this.#signal(() => this.#
|
|
853
|
+
const response = await this.#signal(() => this.#addTracks({ connectionId, sessionDescription, tracks }), { generation, connectionId });
|
|
802
854
|
this.#recordLocalPublicationHandles(publications, response.tracks);
|
|
803
855
|
this.#requireGeneration(generation);
|
|
804
856
|
await this.#applyProviderDescription(response, connection);
|
|
@@ -824,7 +876,7 @@ var CloudflareSFUClient = class {
|
|
|
824
876
|
state.providerPublicationId = lifecycle.handle.publicationId;
|
|
825
877
|
continue;
|
|
826
878
|
}
|
|
827
|
-
this.#markPublicationRetiring(publication);
|
|
879
|
+
this.#markPublicationRetiring(publication, this.#locallyPausedSources.get(state.source) ?? "publish_failure");
|
|
828
880
|
if (state.transceiver === transceiver) {
|
|
829
881
|
this.#clearLocalPublication(state);
|
|
830
882
|
}
|
|
@@ -871,8 +923,8 @@ var CloudflareSFUClient = class {
|
|
|
871
923
|
}
|
|
872
924
|
#pendingRemotePulls(desired) {
|
|
873
925
|
const desiredIds = new Set([...desired.values()].map((publication) => publication.publicationId));
|
|
874
|
-
for (const publicationId of this.#
|
|
875
|
-
if (!desiredIds.has(publicationId)) this.#
|
|
926
|
+
for (const publicationId of this.#remotePullRetries.keys()) {
|
|
927
|
+
if (!desiredIds.has(publicationId)) this.#remotePullRetries.delete(publicationId);
|
|
876
928
|
}
|
|
877
929
|
for (const publicationId of this.#subscribedPublicationIds) {
|
|
878
930
|
if (!desiredIds.has(publicationId)) this.#subscribedPublicationIds.delete(publicationId);
|
|
@@ -880,7 +932,7 @@ var CloudflareSFUClient = class {
|
|
|
880
932
|
const now = Date.now();
|
|
881
933
|
return [...desired].filter(([key, publication]) => {
|
|
882
934
|
if (this.#remoteTracks.get(key)?.publicationId === publication.publicationId) return false;
|
|
883
|
-
const remaining = (this.#
|
|
935
|
+
const remaining = (this.#remotePullRetries.get(publication.publicationId)?.retryAfter ?? 0) - now;
|
|
884
936
|
return remaining <= 0;
|
|
885
937
|
}).map(([, publication]) => publication);
|
|
886
938
|
}
|
|
@@ -921,7 +973,8 @@ var CloudflareSFUClient = class {
|
|
|
921
973
|
}
|
|
922
974
|
}
|
|
923
975
|
#canReplaceConnectionAfterRemotePull(error, generation) {
|
|
924
|
-
|
|
976
|
+
const gone = error instanceof CloudflareSFUError && (error.options.status === 404 || error.options.status === 410);
|
|
977
|
+
return this.#isRetryableConnectionFailure(error) && (gone || !this.#hasLiveRemoteTracks()) && this.#replaceMediaConnection !== void 0 && this.#replacementAttemptedGeneration !== generation;
|
|
925
978
|
}
|
|
926
979
|
#hasLiveRemoteTracks() {
|
|
927
980
|
return [...this.#remoteTracks.values()].some((publication) => publication.track.readyState === "live");
|
|
@@ -929,7 +982,7 @@ var CloudflareSFUClient = class {
|
|
|
929
982
|
async #retryRemotePullOnReplacement(publications, cursor, generation) {
|
|
930
983
|
this.#remoteRecoveryGeneration = generation;
|
|
931
984
|
try {
|
|
932
|
-
await this.#replaceMediaConnectionForRecovery(generation);
|
|
985
|
+
await this.#replaceMediaConnectionForRecovery(generation, "remote_signaling_failure");
|
|
933
986
|
await this.#republishPreparedTracksAfterRecovery(generation);
|
|
934
987
|
return await this.#pull(publications, generation);
|
|
935
988
|
} catch (error) {
|
|
@@ -974,7 +1027,10 @@ var CloudflareSFUClient = class {
|
|
|
974
1027
|
}
|
|
975
1028
|
#deferRemotePulls(publications, error) {
|
|
976
1029
|
const retryAfter = retryableRemotePull(error) ? Date.now() + 15e3 : Number.POSITIVE_INFINITY;
|
|
977
|
-
for (const publication of publications)
|
|
1030
|
+
for (const publication of publications) {
|
|
1031
|
+
const failures = this.#remotePullRetries.get(publication.publicationId)?.failures ?? 0;
|
|
1032
|
+
this.#remotePullRetries.set(publication.publicationId, { retryAfter, failures });
|
|
1033
|
+
}
|
|
978
1034
|
}
|
|
979
1035
|
async #pullBatch(publications, generation) {
|
|
980
1036
|
if (publications.length === 0) return [];
|
|
@@ -1000,12 +1056,12 @@ var CloudflareSFUClient = class {
|
|
|
1000
1056
|
connection.addEventListener("track", onTrack);
|
|
1001
1057
|
try {
|
|
1002
1058
|
this.#requireGeneration(generation);
|
|
1003
|
-
const response = await this.#signal(() => this.#
|
|
1059
|
+
const response = await this.#signal(() => this.#addTracks({ connectionId, tracks: requested, allowPartialRemoteTracks: true }), { generation, connectionId });
|
|
1004
1060
|
this.#requireGeneration(generation);
|
|
1005
1061
|
this.#recordRemotePullErrors(publications, response.trackErrors ?? []);
|
|
1006
1062
|
const responseTracks = response.tracks ?? [];
|
|
1007
1063
|
const matched = matchRemotePullTracks(publications, responseTracks);
|
|
1008
|
-
for (const { publication } of matched) this.#
|
|
1064
|
+
for (const { publication } of matched) this.#remotePullRetries.delete(publication.publicationId);
|
|
1009
1065
|
await this.#completeRenegotiation(response, connection, connectionId, generation);
|
|
1010
1066
|
if (matched.length === 0) return [];
|
|
1011
1067
|
await this.#waitForConnection(connection, generation);
|
|
@@ -1035,8 +1091,10 @@ var CloudflareSFUClient = class {
|
|
|
1035
1091
|
for (const failure of failures) {
|
|
1036
1092
|
const publication = requested.get(`${failure.connectionId}\0${failure.trackName}`);
|
|
1037
1093
|
if (!publication) throw new CloudflareSFUError("Cloudflare SFU returned an unrequested remote track error", "invalid_publication");
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1094
|
+
const previous = this.#remotePullRetries.get(publication.publicationId) ?? { retryAfter: 0, failures: 0 };
|
|
1095
|
+
const failureCount = Math.min(previous.failures + 1, 6);
|
|
1096
|
+
const delay = Math.min(15e3, 750 * 2 ** (failureCount - 1));
|
|
1097
|
+
this.#remotePullRetries.set(publication.publicationId, { retryAfter: Date.now() + delay, failures: failureCount });
|
|
1040
1098
|
this.#reportError(new CloudflareSFUError(`Cloudflare SFU remote track failed: ${failure.code}`, "media_failed", { providerCode: failure.code }));
|
|
1041
1099
|
}
|
|
1042
1100
|
}
|
|
@@ -1187,9 +1245,14 @@ var CloudflareSFUClient = class {
|
|
|
1187
1245
|
}
|
|
1188
1246
|
async #replaceDormantConnectionBeforeNegotiation(generation) {
|
|
1189
1247
|
if (!this.#started || this.#negotiatedGeneration === generation || this.#replacementAttemptedGeneration === generation || !this.#replaceMediaConnection) return;
|
|
1190
|
-
await this.#replaceMediaConnectionForRecovery(generation);
|
|
1248
|
+
await this.#replaceMediaConnectionForRecovery(generation, "dormant_connection");
|
|
1249
|
+
}
|
|
1250
|
+
#addTracks(input) {
|
|
1251
|
+
const replacementCause = this.#replacementCause;
|
|
1252
|
+
this.#replacementCause = void 0;
|
|
1253
|
+
return this.#requireTransport().addTracks({ ...input, replacementCause });
|
|
1191
1254
|
}
|
|
1192
|
-
async #replaceMediaConnectionForRecovery(generation) {
|
|
1255
|
+
async #replaceMediaConnectionForRecovery(generation, cause) {
|
|
1193
1256
|
this.#requireGeneration(generation);
|
|
1194
1257
|
if (!this.#replaceMediaConnection || this.#replacementAttemptedGeneration === generation) {
|
|
1195
1258
|
throw new CloudflareSFUError("A fresh Cloudflare SFU connection is unavailable", "signaling_failed");
|
|
@@ -1209,6 +1272,7 @@ var CloudflareSFUClient = class {
|
|
|
1209
1272
|
this.#cursor = null;
|
|
1210
1273
|
this.#bootstrap = bootstrap;
|
|
1211
1274
|
this.#rebuildPeerConnection(bootstrap, generation, connectionEpoch);
|
|
1275
|
+
this.#replacementCause = cause;
|
|
1212
1276
|
this.#retiredLocalConnection = null;
|
|
1213
1277
|
this.#negotiatedGeneration = null;
|
|
1214
1278
|
for (const state of this.#localTracks.values()) {
|
|
@@ -1557,7 +1621,10 @@ function createCloudflareSFUHTTPTransport(options) {
|
|
|
1557
1621
|
};
|
|
1558
1622
|
return {
|
|
1559
1623
|
addTracks: async (input) => {
|
|
1560
|
-
const
|
|
1624
|
+
const query = new URLSearchParams();
|
|
1625
|
+
if (input.allowPartialRemoteTracks) query.set("allow_partial_remote_tracks", "true");
|
|
1626
|
+
if (input.replacementCause) query.set("replacement_cause", input.replacementCause);
|
|
1627
|
+
const path = query.size ? `tracks?${query}` : "tracks";
|
|
1561
1628
|
const response = await request(path, {
|
|
1562
1629
|
method: "POST",
|
|
1563
1630
|
body: JSON.stringify({ connection_id: input.connectionId, session_description: input.sessionDescription, tracks: input.tracks })
|
package/dist/client.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare const createChalkEffectClient: (options: ChalkEffectClientOptions
|
|
|
23
23
|
readonly tenant_id: import("effect/Schema").brand<import("effect/Schema").String, "TenantId">;
|
|
24
24
|
}>>, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
25
25
|
readonly allow_partial_remote_tracks: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
26
|
+
readonly replacement_cause: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["user_pause", "moderation_pause", "sync_recovery_loss", "discovery_absent", "capture_cleanup", "consent_revoked", "publish_failure", "enable_failure", "projection_mismatch", "forced_pause", "local_signaling_failure", "remote_signaling_failure", "dormant_connection", "access_refresh"]>>;
|
|
26
27
|
}>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
27
28
|
readonly connection_id: import("effect/Schema").String;
|
|
28
29
|
readonly session_description: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
@@ -10,6 +10,6 @@ export declare const makeProductionConnectionPlatformLayer: (options: {
|
|
|
10
10
|
readonly syncURL: string;
|
|
11
11
|
readonly whiteboardURL?: string | null;
|
|
12
12
|
}) => import("effect/Layer").Layer<import("./dependencies").ConnectionPlatformService, never, never>;
|
|
13
|
-
type BoundMediaClient = Pick<ConnectionMediaClient, "setLocalPublicationTarget" | "setLocalSourceIntent" | "closeForcedLocalPublication" | "observeLocalPublications" | "observeRemotePublications" | "remotePublicationsChanged" | "remoteParticipantsChanged" | "remotePublicationResumed" | "start" | "stop" | "prepareLocalTrack" | "clearPreparedLocalTrack" | "getSnapshot" | "subscribe">;
|
|
13
|
+
type BoundMediaClient = Pick<ConnectionMediaClient, "setLocalPublicationTarget" | "pauseLocalCapture" | "setLocalSourceIntent" | "closeForcedLocalPublication" | "observeLocalPublications" | "observeRemotePublications" | "remotePublicationsChanged" | "remoteParticipantsChanged" | "remotePublicationResumed" | "start" | "stop" | "prepareLocalTrack" | "clearPreparedLocalTrack" | "getSnapshot" | "subscribe">;
|
|
14
14
|
export declare function bindConnectionMediaClient(client: BoundMediaClient, restart: ConnectionMediaClient["restart"]): ConnectionMediaClient;
|
|
15
15
|
export {};
|
package/dist/effect.js
CHANGED
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
makeSpaceClientCoreLayer,
|
|
18
18
|
makeSpaceClientCoreLayerFromServices,
|
|
19
19
|
makeSpaceStoreLayer
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-I7Y4HR2P.js";
|
|
21
|
+
import "./chunk-YIDKETOX.js";
|
|
22
22
|
import {
|
|
23
23
|
APIKeyListSchema,
|
|
24
24
|
APIKeyWithSecretSchema,
|
|
@@ -841,7 +841,7 @@ import {
|
|
|
841
841
|
WhiteboardStorageUnavailableErrorSchema,
|
|
842
842
|
WhiteboardStorageUnavailableErrorWireSchema,
|
|
843
843
|
createChalkEffectClient
|
|
844
|
-
} from "./chunk-
|
|
844
|
+
} from "./chunk-CJVLTSIO.js";
|
|
845
845
|
import "./chunk-5YPQNMJE.js";
|
|
846
846
|
|
|
847
847
|
// src/space-client/effect.ts
|
|
@@ -7,6 +7,7 @@ declare const ChalkApi_base: HttpApi.HttpApi<"ChalkApi", HttpApiGroup.HttpApiGro
|
|
|
7
7
|
readonly tenant_id: import("effect/Schema").brand<import("effect/Schema").String, "TenantId">;
|
|
8
8
|
}>>, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
9
9
|
readonly allow_partial_remote_tracks: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
10
|
+
readonly replacement_cause: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["user_pause", "moderation_pause", "sync_recovery_loss", "discovery_absent", "capture_cleanup", "consent_revoked", "publish_failure", "enable_failure", "projection_mismatch", "forced_pause", "local_signaling_failure", "remote_signaling_failure", "dormant_connection", "access_refresh"]>>;
|
|
10
11
|
}>>, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
11
12
|
readonly connection_id: import("effect/Schema").String;
|
|
12
13
|
readonly session_description: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
@@ -1358,6 +1358,7 @@ export declare const AddCloudflareSFUTracksPathParamsSchema: Schema.Struct<{
|
|
|
1358
1358
|
export type AddCloudflareSFUTracksPathParams = typeof AddCloudflareSFUTracksPathParamsSchema.Type;
|
|
1359
1359
|
export declare const AddCloudflareSFUTracksQueryParamsSchema: Schema.Struct<{
|
|
1360
1360
|
readonly allow_partial_remote_tracks: Schema.optional<Schema.Boolean>;
|
|
1361
|
+
readonly replacement_cause: Schema.optional<Schema.Literals<readonly ["user_pause", "moderation_pause", "sync_recovery_loss", "discovery_absent", "capture_cleanup", "consent_revoked", "publish_failure", "enable_failure", "projection_mismatch", "forced_pause", "local_signaling_failure", "remote_signaling_failure", "dormant_connection", "access_refresh"]>>;
|
|
1361
1362
|
}>;
|
|
1362
1363
|
export type AddCloudflareSFUTracksQueryParams = typeof AddCloudflareSFUTracksQueryParamsSchema.Type;
|
|
1363
1364
|
export declare const AddCloudflareSFUTracksRequestBodySchema: Schema.Struct<{
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
createSpaceClient,
|
|
16
16
|
decodeWhiteboardV1ServerFrame,
|
|
17
17
|
encodeWhiteboardV1ClientFrame
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-I7Y4HR2P.js";
|
|
19
19
|
import {
|
|
20
20
|
CloudflareSFUClient,
|
|
21
21
|
CloudflareSFUError,
|
|
@@ -28,19 +28,19 @@ import {
|
|
|
28
28
|
requireSFUDescription,
|
|
29
29
|
validatePublicationSnapshot,
|
|
30
30
|
waitFor
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-YIDKETOX.js";
|
|
32
32
|
import {
|
|
33
33
|
createChalkPublicClient,
|
|
34
34
|
createChalkPublicInviteClient,
|
|
35
35
|
parseInviteCode
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-WFRTUBN6.js";
|
|
37
37
|
import {
|
|
38
38
|
ArrivalInvalidHandleError,
|
|
39
39
|
ArrivalUnavailableError,
|
|
40
40
|
SpacePublicInviteUnavailableError,
|
|
41
41
|
parseParsedAccessGrant,
|
|
42
42
|
requireAccessGrant
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-CJVLTSIO.js";
|
|
44
44
|
import {
|
|
45
45
|
AsyncStorageV1PendingTargetStore,
|
|
46
46
|
InMemoryV1PendingTargetStore,
|
package/dist/media/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientMediaPlane, MediaPlaneResult, MediaPlaneTarget, MediaPublication, MediaPublicationSource } from "./plane";
|
|
1
|
+
import type { ClientMediaPlane, LocalPublicationRetirementCause, MediaPlaneResult, MediaPlaneTarget, MediaPublication, MediaPublicationSource } from "./plane";
|
|
2
2
|
import type { CloudflareSFUBootstrap, CloudflareSFUClientOptions, CloudflareSFURestartOptions, CloudflareSFUSnapshot } from "./types";
|
|
3
3
|
export declare class CloudflareSFUClient implements ClientMediaPlane {
|
|
4
4
|
#private;
|
|
@@ -15,8 +15,9 @@ export declare class CloudflareSFUClient implements ClientMediaPlane {
|
|
|
15
15
|
start(localMedia: MediaStream): Promise<void>;
|
|
16
16
|
refreshRemotePublications(): Promise<void>;
|
|
17
17
|
setLocalPublicationTarget(target: MediaPlaneTarget): Promise<MediaPlaneResult>;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
pauseLocalCapture(source: "microphone" | "camera"): Promise<void>;
|
|
19
|
+
setLocalSourceIntent(source: MediaPublicationSource, enabled: boolean, cause?: LocalPublicationRetirementCause): Promise<void>;
|
|
20
|
+
closeForcedLocalPublication(source: MediaPublicationSource, cause?: LocalPublicationRetirementCause): Promise<void>;
|
|
20
21
|
observeLocalPublications(listener: (publications: readonly MediaPublication[]) => void): () => void;
|
|
21
22
|
observeRemotePublications(listener: (publications: readonly MediaPublication[]) => void): () => void;
|
|
22
23
|
restart(input: CloudflareSFUBootstrap | CloudflareSFURestartOptions): Promise<void>;
|
package/dist/media/index.js
CHANGED
package/dist/media/plane.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { operations } from "../generated/openapi-types";
|
|
2
|
+
export type MediaConnectionReplacementCause = NonNullable<NonNullable<operations["addCloudflareSFUTracks"]["parameters"]["query"]>["replacement_cause"]>;
|
|
3
|
+
export type LocalPublicationRetirementCause = Exclude<MediaConnectionReplacementCause, "local_signaling_failure" | "remote_signaling_failure" | "dormant_connection" | "access_refresh">;
|
|
1
4
|
export type ScreenShareSurface = "monitor" | "window" | "browser";
|
|
2
5
|
export type ScreenShareOptions = {
|
|
3
6
|
readonly surface?: ScreenShareSurface;
|
|
@@ -54,8 +57,9 @@ export type MediaPlaneResult = {
|
|
|
54
57
|
};
|
|
55
58
|
export type ClientMediaPlane = {
|
|
56
59
|
readonly setLocalPublicationTarget: (target: MediaPlaneTarget) => Promise<MediaPlaneResult>;
|
|
57
|
-
readonly
|
|
58
|
-
readonly
|
|
60
|
+
readonly pauseLocalCapture?: (source: "microphone" | "camera") => Promise<void>;
|
|
61
|
+
readonly setLocalSourceIntent?: (source: MediaSource, enabled: boolean, cause?: LocalPublicationRetirementCause) => void;
|
|
62
|
+
readonly closeForcedLocalPublication?: (source: MediaSource, cause?: LocalPublicationRetirementCause) => Promise<void>;
|
|
59
63
|
readonly observeLocalPublications: (listener: (publications: readonly MediaPublication[]) => void) => () => void;
|
|
60
64
|
readonly observeRemotePublications: (listener: (publications: readonly MediaPublication[]) => void) => () => void;
|
|
61
65
|
readonly remotePublicationsChanged?: () => void;
|
package/dist/media/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConnectionReportTransportEvent } from "../transport-diagnostics";
|
|
2
|
-
import type { MediaPublication, MediaPublicationSource } from "./plane";
|
|
2
|
+
import type { MediaConnectionReplacementCause, MediaPublication, MediaPublicationSource } from "./plane";
|
|
3
3
|
export type CloudflareSFUBootstrap = {
|
|
4
4
|
readonly connectionId: string;
|
|
5
5
|
readonly stunServer: string;
|
|
@@ -52,6 +52,7 @@ export type CloudflareSFUSignalingTransport = {
|
|
|
52
52
|
readonly sessionDescription?: CloudflareSFUSessionDescription;
|
|
53
53
|
readonly tracks: readonly CloudflareSFUTrackRequest[];
|
|
54
54
|
readonly allowPartialRemoteTracks?: boolean;
|
|
55
|
+
readonly replacementCause?: MediaConnectionReplacementCause;
|
|
55
56
|
}) => Promise<CloudflareSFUTracksResponse>;
|
|
56
57
|
readonly closeTracks: (input: {
|
|
57
58
|
readonly connectionId: string;
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
createChalkPublicClient,
|
|
3
3
|
createChalkPublicInviteClient,
|
|
4
4
|
parseInviteCode
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-WFRTUBN6.js";
|
|
6
6
|
import {
|
|
7
7
|
ArrivalInvalidHandleError,
|
|
8
8
|
ArrivalUnavailableError,
|
|
9
9
|
SpacePublicInviteUnavailableError
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-CJVLTSIO.js";
|
|
11
11
|
export {
|
|
12
12
|
ArrivalInvalidHandleError,
|
|
13
13
|
ArrivalUnavailableError,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
type ControllerCommand = (...args:
|
|
3
|
-
type PromiseRuntime = {
|
|
4
|
-
readonly runPromise: (effect: Effect.Effect<
|
|
2
|
+
type ControllerCommand = (...args: never[]) => unknown;
|
|
3
|
+
type PromiseRuntime<Services> = {
|
|
4
|
+
readonly runPromise: (effect: Effect.Effect<unknown, unknown, Services>) => Promise<unknown>;
|
|
5
5
|
};
|
|
6
|
-
export type PromiseCommand<Command> = Command extends (...args: infer Arguments) => infer Result ? (Result extends Effect.Effect<infer Success,
|
|
6
|
+
export type PromiseCommand<Command> = Command extends (...args: infer Arguments) => infer Result ? (Result extends Effect.Effect<infer Success, unknown, unknown> ? (...args: Arguments) => Promise<Success> : (...args: Arguments) => Result) : never;
|
|
7
7
|
export type PromiseController<Controller extends Record<string, ControllerCommand>> = {
|
|
8
8
|
readonly [Key in keyof Controller]: PromiseCommand<Controller[Key]>;
|
|
9
9
|
};
|
|
10
|
-
export declare function toPromiseController<Controller extends Record<string, ControllerCommand>>(runtime: PromiseRuntime
|
|
10
|
+
export declare function toPromiseController<Services, Controller extends Record<string, ControllerCommand>>(runtime: PromiseRuntime<Services>, controller: Controller): PromiseController<Omit<Controller, "configure" | "dispose">>;
|
|
11
11
|
export {};
|