@stream-io/video-client 1.60.0 → 1.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/index.browser.es.js +253 -55
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +253 -54
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +253 -55
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +11 -5
- package/dist/src/rtc/Publisher.d.ts +18 -0
- package/dist/src/rtc/TransceiverCache.d.ts +10 -0
- package/dist/src/rtc/e2ee/E2EEManager.d.ts +10 -1
- package/dist/src/types.d.ts +30 -1
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +180 -48
- package/src/__tests__/Call.lifecycle.test.ts +479 -9
- package/src/__tests__/Call.ringingLifecycle.test.ts +242 -0
- package/src/rtc/Publisher.ts +66 -12
- package/src/rtc/TransceiverCache.ts +17 -0
- package/src/rtc/__tests__/Publisher.test.ts +209 -0
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -0
- package/src/rtc/e2ee/E2EEManager.ts +10 -1
- package/src/rtc/e2ee/EncryptionManager.ts +1 -1
- package/src/rtc/e2ee/SPEC.md +1 -1
- package/src/types.ts +37 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.61.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.60.0...@stream-io/video-client-1.61.0) (2026-09-28)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **rn:** add end-to-end encryption support ([#2427](https://github.com/GetStream/stream-video-js/issues/2427)) ([fcd9722](https://github.com/GetStream/stream-video-js/commit/fcd97225597c26ec14bde100b6a4ed14da0ccf93))
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- **client:** abandon retries when leave supersedes join ([#2441](https://github.com/GetStream/stream-video-js/issues/2441)) ([342bea6](https://github.com/GetStream/stream-video-js/commit/342bea6923aef8511228fc7f547a85e2191ca013)), closes [#2422](https://github.com/GetStream/stream-video-js/issues/2422)
|
|
14
|
+
- **client:** never publish unencrypted media before the E2EE transform is attached ([#2460](https://github.com/GetStream/stream-video-js/issues/2460)) ([6d5d0bc](https://github.com/GetStream/stream-video-js/commit/6d5d0bc5085c20f1ab3e379a8e0e13cabe6c2ccc))
|
|
15
|
+
|
|
5
16
|
## [1.60.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.59.1...@stream-io/video-client-1.60.0) (2026-09-11)
|
|
6
17
|
|
|
7
18
|
### Features
|
package/dist/index.browser.es.js
CHANGED
|
@@ -5081,7 +5081,7 @@ const toIceCandidate = (iceTrickle) => {
|
|
|
5081
5081
|
}
|
|
5082
5082
|
};
|
|
5083
5083
|
|
|
5084
|
-
const version = "1.
|
|
5084
|
+
const version = "1.61.0";
|
|
5085
5085
|
const [major, minor, patch] = version.split('.');
|
|
5086
5086
|
let sdkInfo = {
|
|
5087
5087
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -9146,6 +9146,25 @@ class TransceiverCache {
|
|
|
9146
9146
|
this.cache.push(bundle);
|
|
9147
9147
|
this.transceiverOrder.push(bundle.transceiver);
|
|
9148
9148
|
};
|
|
9149
|
+
/**
|
|
9150
|
+
* Retires a failed sender from the reusable cache.
|
|
9151
|
+
*
|
|
9152
|
+
* A transceiver that never reached an m-section also leaves the creation
|
|
9153
|
+
* history: once stopped, it is left out of every later offer, so counting
|
|
9154
|
+
* it would shift the `mid` index of every transceiver added after it. One
|
|
9155
|
+
* that is already associated keeps its slot, since its m-section survives
|
|
9156
|
+
* as a rejected one.
|
|
9157
|
+
*/
|
|
9158
|
+
remove = (transceiver) => {
|
|
9159
|
+
const index = this.cache.findIndex((b) => b.transceiver === transceiver);
|
|
9160
|
+
if (index !== -1)
|
|
9161
|
+
this.cache.splice(index, 1);
|
|
9162
|
+
if (transceiver.mid)
|
|
9163
|
+
return;
|
|
9164
|
+
const orderIndex = this.transceiverOrder.indexOf(transceiver);
|
|
9165
|
+
if (orderIndex !== -1)
|
|
9166
|
+
this.transceiverOrder.splice(orderIndex, 1);
|
|
9167
|
+
};
|
|
9149
9168
|
/**
|
|
9150
9169
|
* Gets the transceiver for the given publish option.
|
|
9151
9170
|
*/
|
|
@@ -9317,7 +9336,9 @@ class Publisher extends BasePeerConnection {
|
|
|
9317
9336
|
const sendEncodings = isSvcCodec(publishOption.codec?.name)
|
|
9318
9337
|
? toSvcEncodings(encodings)
|
|
9319
9338
|
: encodings;
|
|
9320
|
-
|
|
9339
|
+
// An E2EE sender must never have media before its transform is attached,
|
|
9340
|
+
// including when another track or an ICE restart negotiates this PC.
|
|
9341
|
+
const transceiver = this.pc.addTransceiver(this.e2ee ? track.kind : track, {
|
|
9321
9342
|
direction: 'sendonly',
|
|
9322
9343
|
sendEncodings,
|
|
9323
9344
|
});
|
|
@@ -9327,16 +9348,63 @@ class Publisher extends BasePeerConnection {
|
|
|
9327
9348
|
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
9328
9349
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
9329
9350
|
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
9330
|
-
|
|
9351
|
+
if (this.e2ee) {
|
|
9352
|
+
await this.initEncryptedSender(transceiver, track, publishOption);
|
|
9353
|
+
}
|
|
9354
|
+
else {
|
|
9355
|
+
await this.setDegradationPreference(transceiver.sender, publishOption);
|
|
9356
|
+
}
|
|
9357
|
+
await this.negotiate();
|
|
9358
|
+
};
|
|
9359
|
+
/**
|
|
9360
|
+
* Attaches the encryptor, applies sender parameters, then attaches the track.
|
|
9361
|
+
*
|
|
9362
|
+
* `encrypt` must run before the first await: Chrome short-circuits a sender
|
|
9363
|
+
* whose encoded streams aren't created in the task that created it, and the
|
|
9364
|
+
* track goes on last so the sender has no media until both steps succeed.
|
|
9365
|
+
*/
|
|
9366
|
+
initEncryptedSender = async (transceiver, track, publishOption) => {
|
|
9367
|
+
try {
|
|
9368
|
+
const { sender } = transceiver;
|
|
9369
|
+
const codec = publishOption.codec?.name.toLowerCase();
|
|
9370
|
+
this.e2ee.encrypt(sender, codec, TrackType[publishOption.trackType]);
|
|
9371
|
+
this.logger.debug('E2EE encryptor attached to sender');
|
|
9372
|
+
await this.setDegradationPreference(sender, publishOption);
|
|
9373
|
+
await sender.replaceTrack(track);
|
|
9374
|
+
}
|
|
9375
|
+
catch (err) {
|
|
9376
|
+
this.retireTransceiver(transceiver, track);
|
|
9377
|
+
throw err;
|
|
9378
|
+
}
|
|
9379
|
+
};
|
|
9380
|
+
setDegradationPreference = async (sender, publishOption) => {
|
|
9381
|
+
const params = sender.getParameters();
|
|
9331
9382
|
params.degradationPreference =
|
|
9332
9383
|
toRTCDegradationPreference(publishOption.degradationPreference) ??
|
|
9333
9384
|
'maintain-framerate';
|
|
9334
|
-
await
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9385
|
+
await sender.setParameters(params);
|
|
9386
|
+
};
|
|
9387
|
+
/**
|
|
9388
|
+
* Discards a sender whose E2EE initialization failed.
|
|
9389
|
+
*
|
|
9390
|
+
* A partially initialized sender may carry no transform, so publish() must
|
|
9391
|
+
* not reuse it: retiring it from the cache is what keeps a later publish or
|
|
9392
|
+
* ICE restart from putting cleartext on the wire. Cleanup cannot throw, so
|
|
9393
|
+
* the initialization failure is what reaches the caller.
|
|
9394
|
+
*/
|
|
9395
|
+
retireTransceiver = (transceiver, track) => {
|
|
9396
|
+
this.transceiverCache.remove(transceiver);
|
|
9397
|
+
this.trackIdToTrackType.delete(track.id);
|
|
9398
|
+
// on React Native a clone shares its native source, so stopping it here
|
|
9399
|
+
// would stop the track the caller passed in. dispose() releases it.
|
|
9400
|
+
if (!isReactNative())
|
|
9401
|
+
this.stopTrack(track);
|
|
9402
|
+
try {
|
|
9403
|
+
transceiver.stop();
|
|
9404
|
+
}
|
|
9405
|
+
catch (err) {
|
|
9406
|
+
this.logger.debug('Failed to stop a retired transceiver', err);
|
|
9338
9407
|
}
|
|
9339
|
-
await this.negotiate();
|
|
9340
9408
|
};
|
|
9341
9409
|
/**
|
|
9342
9410
|
* Updates the transceiver with the given track and track type.
|
|
@@ -15902,6 +15970,11 @@ class Call {
|
|
|
15902
15970
|
this.audioBindingsWatchdog?.dispose();
|
|
15903
15971
|
await this.dynascaleManager?.dispose();
|
|
15904
15972
|
this.state.setCallingState(CallingState.LEFT);
|
|
15973
|
+
// `ringingSubject` is cleared further down, so this still reads true for
|
|
15974
|
+
// a call that was ringing.
|
|
15975
|
+
if (this.ringing) {
|
|
15976
|
+
globalThis.streamRNVideoSDK?.ringingCallLifecycle?.onLeave(this);
|
|
15977
|
+
}
|
|
15905
15978
|
this.state.setParticipants([]);
|
|
15906
15979
|
this.state.dispose();
|
|
15907
15980
|
// Reset reconnect-related accumulators so a future `call.join()` on the
|
|
@@ -16070,8 +16143,11 @@ class Call {
|
|
|
16070
16143
|
* @param params.video if set to true, in a ringing scenario, mobile SDKs will show "incoming video call", audio only otherwise.
|
|
16071
16144
|
*/
|
|
16072
16145
|
get = async (params) => {
|
|
16146
|
+
const getLeaveGeneration = this.leaveGeneration;
|
|
16073
16147
|
await this.setup();
|
|
16074
16148
|
const response = await this.streamClient.get(this.streamClientBasePath, params);
|
|
16149
|
+
if (this.leaveGeneration !== getLeaveGeneration)
|
|
16150
|
+
return response;
|
|
16075
16151
|
this.updateFromCallStateResponse(response);
|
|
16076
16152
|
if (params?.ring) {
|
|
16077
16153
|
this.ringingSubject.next(true);
|
|
@@ -16176,42 +16252,80 @@ class Call {
|
|
|
16176
16252
|
/**
|
|
16177
16253
|
* Will start to watch for call related WebSocket events and initiate a call session with the server.
|
|
16178
16254
|
*
|
|
16255
|
+
* One instance is one call flow: discard it after leaving, cancelling, or a
|
|
16256
|
+
* failed join, and create a fresh one for a later flow. Reconnection inside a
|
|
16257
|
+
* live call is handled here and needs no new instance.
|
|
16258
|
+
*
|
|
16179
16259
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
16180
16260
|
*/
|
|
16181
16261
|
join = singleFlight(async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, allowOwnTracksLoopback = false, joinSource, ...data } = {}) => {
|
|
16182
16262
|
const callingState = this.state.callingState;
|
|
16263
|
+
// Ahead of the failure boundary below on purpose: a duplicate join on a
|
|
16264
|
+
// live call is refused without tearing that call down.
|
|
16183
16265
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
16184
16266
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
16185
16267
|
}
|
|
16268
|
+
// snapshot before the first await: a leave() landing at any point from
|
|
16269
|
+
// here on supersedes this join, and the retry loop must bail out instead
|
|
16270
|
+
// of resurrecting a call that leave() already tore down.
|
|
16271
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
16272
|
+
const supersededByLeave = () => this.leaveGeneration !== joinLeaveGeneration;
|
|
16186
16273
|
// we need this to be set before the callingx.joinCall() is
|
|
16187
16274
|
// called to avoid registering the test call in the CallKit/Telecom
|
|
16188
16275
|
this.allowOwnTracksLoopback = allowOwnTracksLoopback;
|
|
16189
16276
|
if (data?.ring) {
|
|
16190
16277
|
this.ringingSubject.next(true);
|
|
16191
16278
|
}
|
|
16279
|
+
// A ringing call is joined by the SDK rather than by app code, so React
|
|
16280
|
+
// Native prepares it from in here - there is no earlier point at which the
|
|
16281
|
+
// app holds the call. Read after `data.ring` above, which is what makes an
|
|
16282
|
+
// outgoing call ringing in the first place.
|
|
16283
|
+
const ringingLifecycle = this.ringing
|
|
16284
|
+
? globalThis.streamRNVideoSDK?.ringingCallLifecycle
|
|
16285
|
+
: undefined;
|
|
16192
16286
|
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
16193
|
-
if (callingX) {
|
|
16194
|
-
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
16195
|
-
await callingX.joinCall(this, this.clientStore.calls);
|
|
16196
|
-
}
|
|
16197
|
-
await this.setup();
|
|
16198
|
-
this.clientEventReporter.registerCall(this.cid, {
|
|
16199
|
-
callType: this.type,
|
|
16200
|
-
callId: this.id,
|
|
16201
|
-
getCallSessionId: () => this.state.session?.id ?? '',
|
|
16202
|
-
getSfuId: () => this.credentials?.server.edge_name ?? '',
|
|
16203
|
-
});
|
|
16204
|
-
this.joinResponseTimeout = joinResponseTimeout;
|
|
16205
|
-
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
16206
|
-
// we will count the number of join failures per SFU.
|
|
16207
|
-
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
16208
|
-
// field to force the coordinator to provide us another SFU
|
|
16209
|
-
const sfuJoinFailures = new Map();
|
|
16210
|
-
const joinData = data;
|
|
16211
|
-
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
16212
16287
|
try {
|
|
16288
|
+
if (ringingLifecycle) {
|
|
16289
|
+
await ringingLifecycle.beforeJoin(this);
|
|
16290
|
+
if (supersededByLeave()) {
|
|
16291
|
+
this.logger.debug('Join superseded by leave; not joining');
|
|
16292
|
+
return;
|
|
16293
|
+
}
|
|
16294
|
+
}
|
|
16295
|
+
if (callingX) {
|
|
16296
|
+
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
16297
|
+
await callingX.joinCall(this, this.clientStore.calls, supersededByLeave);
|
|
16298
|
+
if (supersededByLeave()) {
|
|
16299
|
+
this.logger.debug('Join superseded by leave; not setting up');
|
|
16300
|
+
return;
|
|
16301
|
+
}
|
|
16302
|
+
}
|
|
16303
|
+
await this.setup();
|
|
16304
|
+
if (supersededByLeave()) {
|
|
16305
|
+
this.logger.debug('Join superseded by leave; not registering');
|
|
16306
|
+
return;
|
|
16307
|
+
}
|
|
16308
|
+
this.clientEventReporter.registerCall(this.cid, {
|
|
16309
|
+
callType: this.type,
|
|
16310
|
+
callId: this.id,
|
|
16311
|
+
getCallSessionId: () => this.state.session?.id ?? '',
|
|
16312
|
+
getSfuId: () => this.credentials?.server.edge_name ?? '',
|
|
16313
|
+
});
|
|
16314
|
+
this.joinResponseTimeout = joinResponseTimeout;
|
|
16315
|
+
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
16316
|
+
// we will count the number of join failures per SFU.
|
|
16317
|
+
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
16318
|
+
// field to force the coordinator to provide us another SFU
|
|
16319
|
+
const sfuJoinFailures = new Map();
|
|
16320
|
+
const joinData = data;
|
|
16321
|
+
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
16213
16322
|
await this.clientEventReporter.withJoinLifecycle(this.cid, { joinReason: 'first-attempt', joinSource }, async () => {
|
|
16214
16323
|
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
16324
|
+
// Also covers a leave that lands during the backoff below.
|
|
16325
|
+
if (supersededByLeave()) {
|
|
16326
|
+
this.logger.debug('Join superseded by leave; not attempting');
|
|
16327
|
+
return;
|
|
16328
|
+
}
|
|
16215
16329
|
try {
|
|
16216
16330
|
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
16217
16331
|
await this.doJoin(data);
|
|
@@ -16220,6 +16334,10 @@ class Call {
|
|
|
16220
16334
|
return;
|
|
16221
16335
|
}
|
|
16222
16336
|
catch (err) {
|
|
16337
|
+
if (supersededByLeave()) {
|
|
16338
|
+
this.logger.debug('Join superseded by leave; not retrying');
|
|
16339
|
+
return;
|
|
16340
|
+
}
|
|
16223
16341
|
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
16224
16342
|
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
16225
16343
|
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
@@ -16249,6 +16367,9 @@ class Call {
|
|
|
16249
16367
|
}
|
|
16250
16368
|
catch (error) {
|
|
16251
16369
|
callingX?.endCall(this, 'error');
|
|
16370
|
+
// Ends the failed ringing flow and releases what its setup installed.
|
|
16371
|
+
// Never rejects, so `error` is what the caller sees.
|
|
16372
|
+
await ringingLifecycle?.onJoinFailed(this);
|
|
16252
16373
|
throw error;
|
|
16253
16374
|
}
|
|
16254
16375
|
});
|
|
@@ -16271,6 +16392,10 @@ class Call {
|
|
|
16271
16392
|
// globals resolve to the call's factory. Idempotent across
|
|
16272
16393
|
// reconnect/migration attempts.
|
|
16273
16394
|
await this.ensureMediaFactory();
|
|
16395
|
+
if (supersededByLeave()) {
|
|
16396
|
+
this.logger.debug('Join superseded by leave; not wiring media');
|
|
16397
|
+
return;
|
|
16398
|
+
}
|
|
16274
16399
|
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
16275
16400
|
if (callingX) {
|
|
16276
16401
|
callingX.wireAudioEngineSubscription();
|
|
@@ -16286,11 +16411,17 @@ class Call {
|
|
|
16286
16411
|
data?.migrating_from) {
|
|
16287
16412
|
try {
|
|
16288
16413
|
const joinResponse = await this.clientEventReporter.track(this.cid, 'CoordinatorJoin', () => this.doJoinRequest(data));
|
|
16414
|
+
if (!joinResponse || supersededByLeave()) {
|
|
16415
|
+
this.logger.debug('Join superseded by leave; not creating SFU client');
|
|
16416
|
+
return;
|
|
16417
|
+
}
|
|
16289
16418
|
this.credentials = joinResponse.credentials;
|
|
16290
16419
|
statsOptions = joinResponse.stats_options;
|
|
16291
16420
|
this.lastStatsOptions = statsOptions;
|
|
16292
16421
|
}
|
|
16293
16422
|
catch (error) {
|
|
16423
|
+
if (supersededByLeave())
|
|
16424
|
+
return;
|
|
16294
16425
|
// prevent triggering reconnect flow if the state is OFFLINE
|
|
16295
16426
|
const avoidRestoreState = this.state.callingState === CallingState.OFFLINE;
|
|
16296
16427
|
if (!avoidRestoreState) {
|
|
@@ -16338,6 +16469,10 @@ class Call {
|
|
|
16338
16469
|
getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
|
|
16339
16470
|
getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
|
|
16340
16471
|
]);
|
|
16472
|
+
if (supersededByLeave()) {
|
|
16473
|
+
this.logger.debug('Join superseded by leave; not joining SFU');
|
|
16474
|
+
return;
|
|
16475
|
+
}
|
|
16341
16476
|
const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
|
|
16342
16477
|
const reconnectDetails = isReconnecting
|
|
16343
16478
|
? this.getReconnectDetails(previousSfuClient?.edgeName, previousSessionId)
|
|
@@ -16363,6 +16498,10 @@ class Call {
|
|
|
16363
16498
|
capabilities,
|
|
16364
16499
|
source: ParticipantSource.WEBRTC_UNSPECIFIED,
|
|
16365
16500
|
}));
|
|
16501
|
+
if (supersededByLeave()) {
|
|
16502
|
+
this.logger.debug('Join superseded by leave; not applying SFU response');
|
|
16503
|
+
return;
|
|
16504
|
+
}
|
|
16366
16505
|
this.currentPublishOptions = publishOptions;
|
|
16367
16506
|
this.fastReconnectDeadlineSeconds = fastReconnectDeadlineSeconds;
|
|
16368
16507
|
if (callState) {
|
|
@@ -16370,6 +16509,10 @@ class Call {
|
|
|
16370
16509
|
}
|
|
16371
16510
|
}
|
|
16372
16511
|
catch (error) {
|
|
16512
|
+
if (supersededByLeave()) {
|
|
16513
|
+
this.logger.debug('Join superseded by leave; ignoring SFU failure');
|
|
16514
|
+
return;
|
|
16515
|
+
}
|
|
16373
16516
|
this.logger.warn('Join SFU request failed', error);
|
|
16374
16517
|
sfuClient.close(StreamSfuClient.JOIN_FAILED, 'Join request failed, connection considered unhealthy');
|
|
16375
16518
|
// restore the previous call state if the join-flow fails
|
|
@@ -16377,8 +16520,8 @@ class Call {
|
|
|
16377
16520
|
throw error;
|
|
16378
16521
|
}
|
|
16379
16522
|
}
|
|
16380
|
-
// If the user left while this join was in flight, bail before re-setting JOINED
|
|
16381
|
-
// peer-connection setup below
|
|
16523
|
+
// If the user left while this join was in flight, bail before re-setting JOINED
|
|
16524
|
+
// or starting peer-connection setup below.
|
|
16382
16525
|
if (supersededByLeave()) {
|
|
16383
16526
|
this.logger.debug('Join superseded by leave; aborting join flow');
|
|
16384
16527
|
return;
|
|
@@ -16407,6 +16550,10 @@ class Call {
|
|
|
16407
16550
|
unifiedSessionId: this.unifiedSessionId,
|
|
16408
16551
|
});
|
|
16409
16552
|
}
|
|
16553
|
+
if (supersededByLeave()) {
|
|
16554
|
+
this.logger.debug('Join superseded by leave; not completing join flow');
|
|
16555
|
+
return;
|
|
16556
|
+
}
|
|
16410
16557
|
// make sure we only track connection timing if we are not calling this method as part of a reconnection flow
|
|
16411
16558
|
if (!performingRejoin && !performingFastReconnect && !performingMigration) {
|
|
16412
16559
|
this.sfuStatsReporter?.sendConnectionTime((Date.now() - connectStartTime) / 1000);
|
|
@@ -16552,15 +16699,20 @@ class Call {
|
|
|
16552
16699
|
* @internal
|
|
16553
16700
|
*/
|
|
16554
16701
|
initPublisherAndSubscriber = async (opts) => {
|
|
16702
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
16555
16703
|
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
|
|
16556
16704
|
const { enable_rtc_stats: enableTracing, reporting_interval_ms: reportingIntervalMs, } = statsOptions;
|
|
16557
16705
|
// Flush the previous reporter's final sample while its peer connections are
|
|
16558
16706
|
// still alive, before we dispose them below. Awaits only the sampling step.
|
|
16559
16707
|
await this.sfuStatsReporter?.flush();
|
|
16708
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16709
|
+
return;
|
|
16560
16710
|
this.sfuStatsReporter?.stop();
|
|
16561
16711
|
this.sfuStatsReporter = undefined;
|
|
16562
16712
|
if (closePreviousInstances && this.subscriber) {
|
|
16563
16713
|
await this.subscriber.dispose();
|
|
16714
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16715
|
+
return;
|
|
16564
16716
|
this.state.removeAllOrphanedTracks();
|
|
16565
16717
|
}
|
|
16566
16718
|
const basePeerConnectionOptions = {
|
|
@@ -16603,6 +16755,8 @@ class Call {
|
|
|
16603
16755
|
if (!isAnonymous) {
|
|
16604
16756
|
if (closePreviousInstances && this.publisher) {
|
|
16605
16757
|
await this.publisher.dispose();
|
|
16758
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16759
|
+
return;
|
|
16606
16760
|
}
|
|
16607
16761
|
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions, {
|
|
16608
16762
|
selfSubEnabled: this.allowOwnTracksLoopback,
|
|
@@ -16639,12 +16793,22 @@ class Call {
|
|
|
16639
16793
|
*
|
|
16640
16794
|
* @internal
|
|
16641
16795
|
* @param data the join call data.
|
|
16796
|
+
* @returns The coordinator response, or undefined if leave superseded the request before it was sent.
|
|
16642
16797
|
*/
|
|
16643
16798
|
doJoinRequest = async (data) => {
|
|
16799
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
16644
16800
|
const location = await this.streamClient.getLocationHint();
|
|
16801
|
+
if (this.leaveGeneration !== joinLeaveGeneration) {
|
|
16802
|
+
this.logger.debug('Join superseded by leave; not sending coordinator request');
|
|
16803
|
+
return;
|
|
16804
|
+
}
|
|
16645
16805
|
const e2ee = !!this.e2eeManager;
|
|
16646
16806
|
const request = { ...data, location, e2ee };
|
|
16647
16807
|
const joinResponse = await this.streamClient.post(`${this.streamClientBasePath}/join`, request);
|
|
16808
|
+
if (this.leaveGeneration !== joinLeaveGeneration) {
|
|
16809
|
+
this.logger.debug('Join superseded by leave; not applying coordinator response');
|
|
16810
|
+
return joinResponse;
|
|
16811
|
+
}
|
|
16648
16812
|
this.state.updateFromCallResponse(joinResponse.call);
|
|
16649
16813
|
this.state.setMembers(joinResponse.members);
|
|
16650
16814
|
this.state.setOwnCapabilities(joinResponse.own_capabilities);
|
|
@@ -16655,6 +16819,8 @@ class Call {
|
|
|
16655
16819
|
if (!isReconnecting && this.ringing && !this.isCreatedByMe) {
|
|
16656
16820
|
// signals other users that I have accepted the incoming call.
|
|
16657
16821
|
await this.accept();
|
|
16822
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16823
|
+
return joinResponse;
|
|
16658
16824
|
}
|
|
16659
16825
|
if (this.streamClient._hasConnectionID()) {
|
|
16660
16826
|
this.watching = true;
|
|
@@ -16744,6 +16910,7 @@ class Call {
|
|
|
16744
16910
|
if (callingState === CallingState.JOINING ||
|
|
16745
16911
|
callingState === CallingState.RECONNECTING ||
|
|
16746
16912
|
callingState === CallingState.MIGRATING ||
|
|
16913
|
+
callingState === CallingState.LEFT ||
|
|
16747
16914
|
callingState === CallingState.RECONNECTING_FAILED)
|
|
16748
16915
|
return;
|
|
16749
16916
|
// Drop redundant reconnect calls. If a reconnect is already queued or
|
|
@@ -16752,19 +16919,27 @@ class Call {
|
|
|
16752
16919
|
// `POST /join` per entry) once the call is already healthy again.
|
|
16753
16920
|
if (hasPending(this.reconnectConcurrencyTag))
|
|
16754
16921
|
return;
|
|
16922
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16923
|
+
const supersededByLeave = () => this.leaveGeneration !== reconnectLeaveGeneration;
|
|
16755
16924
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
16925
|
+
if (supersededByLeave())
|
|
16926
|
+
return;
|
|
16756
16927
|
const reconnectStartTime = Date.now();
|
|
16757
16928
|
this.reconnectStrategy = strategy;
|
|
16758
16929
|
this.reconnectReason = reason;
|
|
16759
16930
|
const sfuRejoinFailures = new Map();
|
|
16760
16931
|
const markAsReconnectingFailed = async () => {
|
|
16932
|
+
if (supersededByLeave())
|
|
16933
|
+
return;
|
|
16761
16934
|
try {
|
|
16762
16935
|
// attempt to fetch the call data from the server, as the call
|
|
16763
16936
|
// state might have changed while we were reconnecting or were offline
|
|
16764
16937
|
await this.get();
|
|
16765
16938
|
}
|
|
16766
16939
|
finally {
|
|
16767
|
-
|
|
16940
|
+
if (!supersededByLeave()) {
|
|
16941
|
+
this.state.setCallingState(CallingState.RECONNECTING_FAILED);
|
|
16942
|
+
}
|
|
16768
16943
|
}
|
|
16769
16944
|
};
|
|
16770
16945
|
const giveUpAndLeave = async (message) => {
|
|
@@ -16820,6 +16995,8 @@ class Call {
|
|
|
16820
16995
|
try {
|
|
16821
16996
|
// wait until the network is available
|
|
16822
16997
|
await this.networkAvailableTask?.promise;
|
|
16998
|
+
if (supersededByLeave())
|
|
16999
|
+
return;
|
|
16823
17000
|
this.logger.info(`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
|
|
16824
17001
|
switch (this.reconnectStrategy) {
|
|
16825
17002
|
case WebsocketReconnectStrategy.UNSPECIFIED:
|
|
@@ -16859,6 +17036,8 @@ class Call {
|
|
|
16859
17036
|
break; // do-while loop, reconnection worked, exit the loop
|
|
16860
17037
|
}
|
|
16861
17038
|
catch (error) {
|
|
17039
|
+
if (supersededByLeave())
|
|
17040
|
+
return;
|
|
16862
17041
|
if (attemptedStrategy === WebsocketReconnectStrategy.REJOIN) {
|
|
16863
17042
|
const failedSfu = this.credentials?.server.edge_name;
|
|
16864
17043
|
if (failedSfu) {
|
|
@@ -16889,6 +17068,8 @@ class Call {
|
|
|
16889
17068
|
}
|
|
16890
17069
|
// exponential backoff with jitter, capped at 5 s
|
|
16891
17070
|
await sleep(retryInterval(attempt));
|
|
17071
|
+
if (supersededByLeave())
|
|
17072
|
+
return;
|
|
16892
17073
|
const wasMigrating = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
|
|
16893
17074
|
const mustPerformRejoin = (Date.now() - reconnectStartTime) / 1000 >
|
|
16894
17075
|
this.fastReconnectDeadlineSeconds;
|
|
@@ -16907,7 +17088,8 @@ class Call {
|
|
|
16907
17088
|
this.reconnectStrategy = nextStrategy;
|
|
16908
17089
|
this.logger.info(`[Reconnect] ${currentStrategy} (${this.reconnectAttempts}) failed. Attempting with ${WebsocketReconnectStrategy[nextStrategy]}`, error);
|
|
16909
17090
|
}
|
|
16910
|
-
} while (
|
|
17091
|
+
} while (!supersededByLeave() &&
|
|
17092
|
+
this.state.callingState !== CallingState.JOINED &&
|
|
16911
17093
|
this.state.callingState !== CallingState.RECONNECTING_FAILED &&
|
|
16912
17094
|
this.state.callingState !== CallingState.LEFT);
|
|
16913
17095
|
this.logger.info('[Reconnect] Reconnection flow finished');
|
|
@@ -16918,10 +17100,13 @@ class Call {
|
|
|
16918
17100
|
* @internal
|
|
16919
17101
|
*/
|
|
16920
17102
|
reconnectFast = async () => {
|
|
17103
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16921
17104
|
const reconnectStartTime = Date.now();
|
|
16922
17105
|
this.reconnectStrategy = WebsocketReconnectStrategy.FAST;
|
|
16923
17106
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
16924
17107
|
await this.doJoin(this.joinCallData);
|
|
17108
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17109
|
+
return;
|
|
16925
17110
|
await this.get(); // fetch the latest call state, as it might have changed
|
|
16926
17111
|
this.sfuStatsReporter?.sendReconnectionTime(WebsocketReconnectStrategy.FAST, (Date.now() - reconnectStartTime) / 1000);
|
|
16927
17112
|
};
|
|
@@ -16930,6 +17115,7 @@ class Call {
|
|
|
16930
17115
|
* @internal
|
|
16931
17116
|
*/
|
|
16932
17117
|
reconnectRejoin = async () => {
|
|
17118
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16933
17119
|
const reconnectStartTime = Date.now();
|
|
16934
17120
|
this.reconnectStrategy = WebsocketReconnectStrategy.REJOIN;
|
|
16935
17121
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
@@ -16937,7 +17123,11 @@ class Call {
|
|
|
16937
17123
|
? 'network-available'
|
|
16938
17124
|
: 'full-rejoin';
|
|
16939
17125
|
await this.clientEventReporter.withJoinLifecycle(this.cid, { joinReason }, () => this.doJoin(this.joinCallData));
|
|
17126
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17127
|
+
return;
|
|
16940
17128
|
await this.restorePublishedTracks();
|
|
17129
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17130
|
+
return;
|
|
16941
17131
|
this.restoreSubscribedTracks();
|
|
16942
17132
|
this.sfuStatsReporter?.sendReconnectionTime(WebsocketReconnectStrategy.REJOIN, (Date.now() - reconnectStartTime) / 1000);
|
|
16943
17133
|
};
|
|
@@ -16946,6 +17136,7 @@ class Call {
|
|
|
16946
17136
|
* @internal
|
|
16947
17137
|
*/
|
|
16948
17138
|
reconnectMigrate = async () => {
|
|
17139
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16949
17140
|
const reconnectStartTime = Date.now();
|
|
16950
17141
|
const currentSfuClient = this.sfuClient;
|
|
16951
17142
|
if (!currentSfuClient) {
|
|
@@ -16959,26 +17150,32 @@ class Call {
|
|
|
16959
17150
|
currentPublisher?.detachEventHandlers();
|
|
16960
17151
|
const migrationTask = makeSafePromise(currentSfuClient.enterMigration());
|
|
16961
17152
|
try {
|
|
16962
|
-
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
16966
|
-
|
|
16967
|
-
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
17153
|
+
try {
|
|
17154
|
+
const currentSfu = currentSfuClient.edgeName;
|
|
17155
|
+
await this.clientEventReporter.withJoinLifecycle(this.cid, { joinReason: 'migration' }, () => this.doJoin({
|
|
17156
|
+
...this.joinCallData,
|
|
17157
|
+
migrating_from: currentSfu,
|
|
17158
|
+
migrating_from_list: [currentSfu],
|
|
17159
|
+
}));
|
|
17160
|
+
}
|
|
17161
|
+
finally {
|
|
17162
|
+
// cleanup the migration_from field after the migration is complete or failed
|
|
17163
|
+
// as we don't want to keep dirty data in the join call data
|
|
17164
|
+
delete this.joinCallData?.migrating_from;
|
|
17165
|
+
delete this.joinCallData?.migrating_from_list;
|
|
17166
|
+
}
|
|
17167
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17168
|
+
return;
|
|
17169
|
+
await this.restorePublishedTracks();
|
|
17170
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17171
|
+
return;
|
|
17172
|
+
this.restoreSubscribedTracks();
|
|
16978
17173
|
// Wait for the migration to complete, then close the previous SFU client
|
|
16979
17174
|
// and the peer connection instances. In case of failure, the migration
|
|
16980
17175
|
// task would throw an error and REJOIN would be attempted.
|
|
16981
17176
|
await migrationTask();
|
|
17177
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17178
|
+
return;
|
|
16982
17179
|
// in MIGRATE, we can consider the call as joined only after
|
|
16983
17180
|
// `participantMigrationComplete` event is received, signaled by
|
|
16984
17181
|
// the `migrationTask`
|
|
@@ -17263,10 +17460,11 @@ class Call {
|
|
|
17263
17460
|
* Must be called before {@link join} so the RTCPeerConnection can be
|
|
17264
17461
|
* configured for E2EE.
|
|
17265
17462
|
*
|
|
17266
|
-
* The
|
|
17267
|
-
*
|
|
17268
|
-
*
|
|
17269
|
-
*
|
|
17463
|
+
* The application owns the manager's lifetime - the SDK never disposes it, and
|
|
17464
|
+
* closing the peer connections does not release it. Discard the manager
|
|
17465
|
+
* together with this call instance once its flow has ended, and create a fresh
|
|
17466
|
+
* pair for a later flow: a disposed manager throws from `encrypt`/`decrypt`
|
|
17467
|
+
* rather than silently publishing nothing.
|
|
17270
17468
|
*
|
|
17271
17469
|
* @param e2ee - Any `E2EEManager`. Use `EncryptionManager.create()` for the
|
|
17272
17470
|
* built-in AES-GCM scheme, or pass your own implementation.
|
|
@@ -19435,11 +19633,11 @@ class StreamClient {
|
|
|
19435
19633
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
19436
19634
|
};
|
|
19437
19635
|
getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
|
|
19438
|
-
"1.
|
|
19636
|
+
"1.61.0";
|
|
19439
19637
|
getUserAgent = () => {
|
|
19440
19638
|
if (!this.cachedUserAgent) {
|
|
19441
19639
|
const { clientAppIdentifier = {} } = this.options;
|
|
19442
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
19640
|
+
const { sdkName = 'js', sdkVersion = "1.61.0", ...extras } = clientAppIdentifier;
|
|
19443
19641
|
this.cachedUserAgent = [
|
|
19444
19642
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
19445
19643
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -21148,9 +21346,9 @@ class EncryptionManager extends TypedEventEmitter {
|
|
|
21148
21346
|
}
|
|
21149
21347
|
if ((this.piped ??= new WeakSet()).has(target))
|
|
21150
21348
|
return;
|
|
21151
|
-
this.piped.add(target);
|
|
21152
21349
|
// @ts-expect-error createEncodedStreams is not in the standard typedefs
|
|
21153
21350
|
const { readable, writable } = target.createEncodedStreams();
|
|
21351
|
+
this.piped.add(target);
|
|
21154
21352
|
this.worker.postMessage({ type: 'cmd.setup_transform', ...options, readable, writable }, [readable, writable]);
|
|
21155
21353
|
};
|
|
21156
21354
|
/**
|
|
@@ -21206,5 +21404,5 @@ class EncryptionManager extends TypedEventEmitter {
|
|
|
21206
21404
|
};
|
|
21207
21405
|
}
|
|
21208
21406
|
|
|
21209
|
-
export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DEFAULT_LOOPBACK_RECORDING_DURATION_MS, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, EncryptionManager, EncryptionSettingsRequestModeEnum, EncryptionSettingsResponseModeEnum, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, HLSSettingsRequestQualityTracksEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsRequestOutputTypesEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LOOPBACK_STREAMS_WAIT_TIMEOUT_MS, LayoutSettingsRequestNameEnum, LoopbackStreamsTimeoutError, MAX_LOOPBACK_RECORDING_DURATION_MS, MIN_LOOPBACK_RECORDING_DURATION_MS, MediaStreamRecorder, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, RequestPermissionRequestPermissionsEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SfuTimeoutError, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, TranslationSettingsLanguagesEnum, UpdateUserPermissionsRequestGrantPermissionsEnum, UpdateUserPermissionsRequestRevokePermissionsEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, WSAuthMessageProductsEnum, checkIfAudioOutputChangeSupported, clampLoopbackRecordingDuration, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, enableLoopbackAudio, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getCallMediaEngineProvider, getClientDetails, getDeviceState, getLoopbackStreams, getLoopbackTracks, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio$1 as hasAudio, hasInterruptedTrack, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishing, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setCallMediaEngineProvider, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, waitForLoopbackStreams, withLoopbackAudioEnabled, withParticipantSource };
|
|
21407
|
+
export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DEFAULT_LOOPBACK_RECORDING_DURATION_MS, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, EncryptionManager, EncryptionSettingsRequestModeEnum, EncryptionSettingsResponseModeEnum, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, HLSSettingsRequestQualityTracksEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsRequestOutputTypesEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LOOPBACK_STREAMS_WAIT_TIMEOUT_MS, LayoutSettingsRequestNameEnum, LoopbackStreamsTimeoutError, MAX_LOOPBACK_RECORDING_DURATION_MS, MIN_LOOPBACK_RECORDING_DURATION_MS, MediaStreamRecorder, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, RequestPermissionRequestPermissionsEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SfuTimeoutError, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, TranslationSettingsLanguagesEnum, TypedEventEmitter, UpdateUserPermissionsRequestGrantPermissionsEnum, UpdateUserPermissionsRequestRevokePermissionsEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, WSAuthMessageProductsEnum, checkIfAudioOutputChangeSupported, clampLoopbackRecordingDuration, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, enableLoopbackAudio, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getCallMediaEngineProvider, getClientDetails, getDeviceState, getLoopbackStreams, getLoopbackTracks, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio$1 as hasAudio, hasInterruptedTrack, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishing, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setCallMediaEngineProvider, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, waitForLoopbackStreams, withLoopbackAudioEnabled, withParticipantSource };
|
|
21210
21408
|
//# sourceMappingURL=index.browser.es.js.map
|