@stream-io/video-client 1.60.0 → 1.61.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.browser.es.js +264 -57
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +264 -56
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +264 -57
- 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/reporting/ClientEventReporter.ts +7 -2
- package/src/reporting/__tests__/ClientEventReporter.test.ts +2 -2
- package/src/rtc/BasePeerConnection.ts +6 -1
- package/src/rtc/Publisher.ts +66 -12
- package/src/rtc/TransceiverCache.ts +17 -0
- package/src/rtc/__tests__/Publisher.test.ts +259 -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/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './src/helpers/sound-detector';
|
|
|
22
22
|
export * from './src/helpers/loopback';
|
|
23
23
|
export * from './src/helpers/MediaStreamRecorder';
|
|
24
24
|
export * from './src/helpers/participantUtils';
|
|
25
|
+
export * from './src/helpers/TypedEventEmitter';
|
|
25
26
|
export * from './src/rtc/e2ee/E2EEManager';
|
|
26
27
|
export * from './src/rtc/e2ee/EncryptionManager';
|
|
27
28
|
export * as Browsers from './src/helpers/browsers';
|
package/dist/index.es.js
CHANGED
|
@@ -5082,7 +5082,7 @@ const toIceCandidate = (iceTrickle) => {
|
|
|
5082
5082
|
}
|
|
5083
5083
|
};
|
|
5084
5084
|
|
|
5085
|
-
const version = "1.
|
|
5085
|
+
const version = "1.61.1";
|
|
5086
5086
|
const [major, minor, patch] = version.split('.');
|
|
5087
5087
|
let sdkInfo = {
|
|
5088
5088
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8755,7 +8755,12 @@ class BasePeerConnection {
|
|
|
8755
8755
|
}
|
|
8756
8756
|
// we can't recover from a failed connection state (contrary to ICE)
|
|
8757
8757
|
if (state === 'failed') {
|
|
8758
|
-
|
|
8758
|
+
const reason = this.iceHasEverConnected
|
|
8759
|
+
? ReconnectReason.CONNECTION_FAILED
|
|
8760
|
+
: ReconnectReason.ICE_NEVER_CONNECTED;
|
|
8761
|
+
clearTimeout(this.preConnectStuckTimeout);
|
|
8762
|
+
this.preConnectStuckTimeout = undefined;
|
|
8763
|
+
this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, reason, this.peerType);
|
|
8759
8764
|
return;
|
|
8760
8765
|
}
|
|
8761
8766
|
this.handleConnectionStateUpdate(state);
|
|
@@ -9147,6 +9152,25 @@ class TransceiverCache {
|
|
|
9147
9152
|
this.cache.push(bundle);
|
|
9148
9153
|
this.transceiverOrder.push(bundle.transceiver);
|
|
9149
9154
|
};
|
|
9155
|
+
/**
|
|
9156
|
+
* Retires a failed sender from the reusable cache.
|
|
9157
|
+
*
|
|
9158
|
+
* A transceiver that never reached an m-section also leaves the creation
|
|
9159
|
+
* history: once stopped, it is left out of every later offer, so counting
|
|
9160
|
+
* it would shift the `mid` index of every transceiver added after it. One
|
|
9161
|
+
* that is already associated keeps its slot, since its m-section survives
|
|
9162
|
+
* as a rejected one.
|
|
9163
|
+
*/
|
|
9164
|
+
remove = (transceiver) => {
|
|
9165
|
+
const index = this.cache.findIndex((b) => b.transceiver === transceiver);
|
|
9166
|
+
if (index !== -1)
|
|
9167
|
+
this.cache.splice(index, 1);
|
|
9168
|
+
if (transceiver.mid)
|
|
9169
|
+
return;
|
|
9170
|
+
const orderIndex = this.transceiverOrder.indexOf(transceiver);
|
|
9171
|
+
if (orderIndex !== -1)
|
|
9172
|
+
this.transceiverOrder.splice(orderIndex, 1);
|
|
9173
|
+
};
|
|
9150
9174
|
/**
|
|
9151
9175
|
* Gets the transceiver for the given publish option.
|
|
9152
9176
|
*/
|
|
@@ -9318,7 +9342,9 @@ class Publisher extends BasePeerConnection {
|
|
|
9318
9342
|
const sendEncodings = isSvcCodec(publishOption.codec?.name)
|
|
9319
9343
|
? toSvcEncodings(encodings)
|
|
9320
9344
|
: encodings;
|
|
9321
|
-
|
|
9345
|
+
// An E2EE sender must never have media before its transform is attached,
|
|
9346
|
+
// including when another track or an ICE restart negotiates this PC.
|
|
9347
|
+
const transceiver = this.pc.addTransceiver(this.e2ee ? track.kind : track, {
|
|
9322
9348
|
direction: 'sendonly',
|
|
9323
9349
|
sendEncodings,
|
|
9324
9350
|
});
|
|
@@ -9328,16 +9354,63 @@ class Publisher extends BasePeerConnection {
|
|
|
9328
9354
|
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
9329
9355
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
9330
9356
|
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
9331
|
-
|
|
9357
|
+
if (this.e2ee) {
|
|
9358
|
+
await this.initEncryptedSender(transceiver, track, publishOption);
|
|
9359
|
+
}
|
|
9360
|
+
else {
|
|
9361
|
+
await this.setDegradationPreference(transceiver.sender, publishOption);
|
|
9362
|
+
}
|
|
9363
|
+
await this.negotiate();
|
|
9364
|
+
};
|
|
9365
|
+
/**
|
|
9366
|
+
* Attaches the encryptor, applies sender parameters, then attaches the track.
|
|
9367
|
+
*
|
|
9368
|
+
* `encrypt` must run before the first await: Chrome short-circuits a sender
|
|
9369
|
+
* whose encoded streams aren't created in the task that created it, and the
|
|
9370
|
+
* track goes on last so the sender has no media until both steps succeed.
|
|
9371
|
+
*/
|
|
9372
|
+
initEncryptedSender = async (transceiver, track, publishOption) => {
|
|
9373
|
+
try {
|
|
9374
|
+
const { sender } = transceiver;
|
|
9375
|
+
const codec = publishOption.codec?.name.toLowerCase();
|
|
9376
|
+
this.e2ee.encrypt(sender, codec, TrackType[publishOption.trackType]);
|
|
9377
|
+
this.logger.debug('E2EE encryptor attached to sender');
|
|
9378
|
+
await this.setDegradationPreference(sender, publishOption);
|
|
9379
|
+
await sender.replaceTrack(track);
|
|
9380
|
+
}
|
|
9381
|
+
catch (err) {
|
|
9382
|
+
this.retireTransceiver(transceiver, track);
|
|
9383
|
+
throw err;
|
|
9384
|
+
}
|
|
9385
|
+
};
|
|
9386
|
+
setDegradationPreference = async (sender, publishOption) => {
|
|
9387
|
+
const params = sender.getParameters();
|
|
9332
9388
|
params.degradationPreference =
|
|
9333
9389
|
toRTCDegradationPreference(publishOption.degradationPreference) ??
|
|
9334
9390
|
'maintain-framerate';
|
|
9335
|
-
await
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9391
|
+
await sender.setParameters(params);
|
|
9392
|
+
};
|
|
9393
|
+
/**
|
|
9394
|
+
* Discards a sender whose E2EE initialization failed.
|
|
9395
|
+
*
|
|
9396
|
+
* A partially initialized sender may carry no transform, so publish() must
|
|
9397
|
+
* not reuse it: retiring it from the cache is what keeps a later publish or
|
|
9398
|
+
* ICE restart from putting cleartext on the wire. Cleanup cannot throw, so
|
|
9399
|
+
* the initialization failure is what reaches the caller.
|
|
9400
|
+
*/
|
|
9401
|
+
retireTransceiver = (transceiver, track) => {
|
|
9402
|
+
this.transceiverCache.remove(transceiver);
|
|
9403
|
+
this.trackIdToTrackType.delete(track.id);
|
|
9404
|
+
// on React Native a clone shares its native source, so stopping it here
|
|
9405
|
+
// would stop the track the caller passed in. dispose() releases it.
|
|
9406
|
+
if (!isReactNative())
|
|
9407
|
+
this.stopTrack(track);
|
|
9408
|
+
try {
|
|
9409
|
+
transceiver.stop();
|
|
9410
|
+
}
|
|
9411
|
+
catch (err) {
|
|
9412
|
+
this.logger.debug('Failed to stop a retired transceiver', err);
|
|
9339
9413
|
}
|
|
9340
|
-
await this.negotiate();
|
|
9341
9414
|
};
|
|
9342
9415
|
/**
|
|
9343
9416
|
* Updates the transceiver with the given track and track type.
|
|
@@ -15903,6 +15976,11 @@ class Call {
|
|
|
15903
15976
|
this.audioBindingsWatchdog?.dispose();
|
|
15904
15977
|
await this.dynascaleManager?.dispose();
|
|
15905
15978
|
this.state.setCallingState(CallingState.LEFT);
|
|
15979
|
+
// `ringingSubject` is cleared further down, so this still reads true for
|
|
15980
|
+
// a call that was ringing.
|
|
15981
|
+
if (this.ringing) {
|
|
15982
|
+
globalThis.streamRNVideoSDK?.ringingCallLifecycle?.onLeave(this);
|
|
15983
|
+
}
|
|
15906
15984
|
this.state.setParticipants([]);
|
|
15907
15985
|
this.state.dispose();
|
|
15908
15986
|
// Reset reconnect-related accumulators so a future `call.join()` on the
|
|
@@ -16071,8 +16149,11 @@ class Call {
|
|
|
16071
16149
|
* @param params.video if set to true, in a ringing scenario, mobile SDKs will show "incoming video call", audio only otherwise.
|
|
16072
16150
|
*/
|
|
16073
16151
|
get = async (params) => {
|
|
16152
|
+
const getLeaveGeneration = this.leaveGeneration;
|
|
16074
16153
|
await this.setup();
|
|
16075
16154
|
const response = await this.streamClient.get(this.streamClientBasePath, params);
|
|
16155
|
+
if (this.leaveGeneration !== getLeaveGeneration)
|
|
16156
|
+
return response;
|
|
16076
16157
|
this.updateFromCallStateResponse(response);
|
|
16077
16158
|
if (params?.ring) {
|
|
16078
16159
|
this.ringingSubject.next(true);
|
|
@@ -16177,42 +16258,80 @@ class Call {
|
|
|
16177
16258
|
/**
|
|
16178
16259
|
* Will start to watch for call related WebSocket events and initiate a call session with the server.
|
|
16179
16260
|
*
|
|
16261
|
+
* One instance is one call flow: discard it after leaving, cancelling, or a
|
|
16262
|
+
* failed join, and create a fresh one for a later flow. Reconnection inside a
|
|
16263
|
+
* live call is handled here and needs no new instance.
|
|
16264
|
+
*
|
|
16180
16265
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
16181
16266
|
*/
|
|
16182
16267
|
join = singleFlight(async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, allowOwnTracksLoopback = false, joinSource, ...data } = {}) => {
|
|
16183
16268
|
const callingState = this.state.callingState;
|
|
16269
|
+
// Ahead of the failure boundary below on purpose: a duplicate join on a
|
|
16270
|
+
// live call is refused without tearing that call down.
|
|
16184
16271
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
16185
16272
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
16186
16273
|
}
|
|
16274
|
+
// snapshot before the first await: a leave() landing at any point from
|
|
16275
|
+
// here on supersedes this join, and the retry loop must bail out instead
|
|
16276
|
+
// of resurrecting a call that leave() already tore down.
|
|
16277
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
16278
|
+
const supersededByLeave = () => this.leaveGeneration !== joinLeaveGeneration;
|
|
16187
16279
|
// we need this to be set before the callingx.joinCall() is
|
|
16188
16280
|
// called to avoid registering the test call in the CallKit/Telecom
|
|
16189
16281
|
this.allowOwnTracksLoopback = allowOwnTracksLoopback;
|
|
16190
16282
|
if (data?.ring) {
|
|
16191
16283
|
this.ringingSubject.next(true);
|
|
16192
16284
|
}
|
|
16285
|
+
// A ringing call is joined by the SDK rather than by app code, so React
|
|
16286
|
+
// Native prepares it from in here - there is no earlier point at which the
|
|
16287
|
+
// app holds the call. Read after `data.ring` above, which is what makes an
|
|
16288
|
+
// outgoing call ringing in the first place.
|
|
16289
|
+
const ringingLifecycle = this.ringing
|
|
16290
|
+
? globalThis.streamRNVideoSDK?.ringingCallLifecycle
|
|
16291
|
+
: undefined;
|
|
16193
16292
|
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
16194
|
-
if (callingX) {
|
|
16195
|
-
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
16196
|
-
await callingX.joinCall(this, this.clientStore.calls);
|
|
16197
|
-
}
|
|
16198
|
-
await this.setup();
|
|
16199
|
-
this.clientEventReporter.registerCall(this.cid, {
|
|
16200
|
-
callType: this.type,
|
|
16201
|
-
callId: this.id,
|
|
16202
|
-
getCallSessionId: () => this.state.session?.id ?? '',
|
|
16203
|
-
getSfuId: () => this.credentials?.server.edge_name ?? '',
|
|
16204
|
-
});
|
|
16205
|
-
this.joinResponseTimeout = joinResponseTimeout;
|
|
16206
|
-
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
16207
|
-
// we will count the number of join failures per SFU.
|
|
16208
|
-
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
16209
|
-
// field to force the coordinator to provide us another SFU
|
|
16210
|
-
const sfuJoinFailures = new Map();
|
|
16211
|
-
const joinData = data;
|
|
16212
|
-
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
16213
16293
|
try {
|
|
16294
|
+
if (ringingLifecycle) {
|
|
16295
|
+
await ringingLifecycle.beforeJoin(this);
|
|
16296
|
+
if (supersededByLeave()) {
|
|
16297
|
+
this.logger.debug('Join superseded by leave; not joining');
|
|
16298
|
+
return;
|
|
16299
|
+
}
|
|
16300
|
+
}
|
|
16301
|
+
if (callingX) {
|
|
16302
|
+
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
16303
|
+
await callingX.joinCall(this, this.clientStore.calls, supersededByLeave);
|
|
16304
|
+
if (supersededByLeave()) {
|
|
16305
|
+
this.logger.debug('Join superseded by leave; not setting up');
|
|
16306
|
+
return;
|
|
16307
|
+
}
|
|
16308
|
+
}
|
|
16309
|
+
await this.setup();
|
|
16310
|
+
if (supersededByLeave()) {
|
|
16311
|
+
this.logger.debug('Join superseded by leave; not registering');
|
|
16312
|
+
return;
|
|
16313
|
+
}
|
|
16314
|
+
this.clientEventReporter.registerCall(this.cid, {
|
|
16315
|
+
callType: this.type,
|
|
16316
|
+
callId: this.id,
|
|
16317
|
+
getCallSessionId: () => this.state.session?.id ?? '',
|
|
16318
|
+
getSfuId: () => this.credentials?.server.edge_name ?? '',
|
|
16319
|
+
});
|
|
16320
|
+
this.joinResponseTimeout = joinResponseTimeout;
|
|
16321
|
+
this.rpcRequestTimeout = rpcRequestTimeout;
|
|
16322
|
+
// we will count the number of join failures per SFU.
|
|
16323
|
+
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
16324
|
+
// field to force the coordinator to provide us another SFU
|
|
16325
|
+
const sfuJoinFailures = new Map();
|
|
16326
|
+
const joinData = data;
|
|
16327
|
+
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
16214
16328
|
await this.clientEventReporter.withJoinLifecycle(this.cid, { joinReason: 'first-attempt', joinSource }, async () => {
|
|
16215
16329
|
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
16330
|
+
// Also covers a leave that lands during the backoff below.
|
|
16331
|
+
if (supersededByLeave()) {
|
|
16332
|
+
this.logger.debug('Join superseded by leave; not attempting');
|
|
16333
|
+
return;
|
|
16334
|
+
}
|
|
16216
16335
|
try {
|
|
16217
16336
|
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
16218
16337
|
await this.doJoin(data);
|
|
@@ -16221,6 +16340,10 @@ class Call {
|
|
|
16221
16340
|
return;
|
|
16222
16341
|
}
|
|
16223
16342
|
catch (err) {
|
|
16343
|
+
if (supersededByLeave()) {
|
|
16344
|
+
this.logger.debug('Join superseded by leave; not retrying');
|
|
16345
|
+
return;
|
|
16346
|
+
}
|
|
16224
16347
|
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
16225
16348
|
if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
|
|
16226
16349
|
(err instanceof SfuJoinError && err.unrecoverable)) {
|
|
@@ -16250,6 +16373,9 @@ class Call {
|
|
|
16250
16373
|
}
|
|
16251
16374
|
catch (error) {
|
|
16252
16375
|
callingX?.endCall(this, 'error');
|
|
16376
|
+
// Ends the failed ringing flow and releases what its setup installed.
|
|
16377
|
+
// Never rejects, so `error` is what the caller sees.
|
|
16378
|
+
await ringingLifecycle?.onJoinFailed(this);
|
|
16253
16379
|
throw error;
|
|
16254
16380
|
}
|
|
16255
16381
|
});
|
|
@@ -16272,6 +16398,10 @@ class Call {
|
|
|
16272
16398
|
// globals resolve to the call's factory. Idempotent across
|
|
16273
16399
|
// reconnect/migration attempts.
|
|
16274
16400
|
await this.ensureMediaFactory();
|
|
16401
|
+
if (supersededByLeave()) {
|
|
16402
|
+
this.logger.debug('Join superseded by leave; not wiring media');
|
|
16403
|
+
return;
|
|
16404
|
+
}
|
|
16275
16405
|
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
16276
16406
|
if (callingX) {
|
|
16277
16407
|
callingX.wireAudioEngineSubscription();
|
|
@@ -16287,11 +16417,17 @@ class Call {
|
|
|
16287
16417
|
data?.migrating_from) {
|
|
16288
16418
|
try {
|
|
16289
16419
|
const joinResponse = await this.clientEventReporter.track(this.cid, 'CoordinatorJoin', () => this.doJoinRequest(data));
|
|
16420
|
+
if (!joinResponse || supersededByLeave()) {
|
|
16421
|
+
this.logger.debug('Join superseded by leave; not creating SFU client');
|
|
16422
|
+
return;
|
|
16423
|
+
}
|
|
16290
16424
|
this.credentials = joinResponse.credentials;
|
|
16291
16425
|
statsOptions = joinResponse.stats_options;
|
|
16292
16426
|
this.lastStatsOptions = statsOptions;
|
|
16293
16427
|
}
|
|
16294
16428
|
catch (error) {
|
|
16429
|
+
if (supersededByLeave())
|
|
16430
|
+
return;
|
|
16295
16431
|
// prevent triggering reconnect flow if the state is OFFLINE
|
|
16296
16432
|
const avoidRestoreState = this.state.callingState === CallingState.OFFLINE;
|
|
16297
16433
|
if (!avoidRestoreState) {
|
|
@@ -16339,6 +16475,10 @@ class Call {
|
|
|
16339
16475
|
getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
|
|
16340
16476
|
getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
|
|
16341
16477
|
]);
|
|
16478
|
+
if (supersededByLeave()) {
|
|
16479
|
+
this.logger.debug('Join superseded by leave; not joining SFU');
|
|
16480
|
+
return;
|
|
16481
|
+
}
|
|
16342
16482
|
const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
|
|
16343
16483
|
const reconnectDetails = isReconnecting
|
|
16344
16484
|
? this.getReconnectDetails(previousSfuClient?.edgeName, previousSessionId)
|
|
@@ -16364,6 +16504,10 @@ class Call {
|
|
|
16364
16504
|
capabilities,
|
|
16365
16505
|
source: ParticipantSource.WEBRTC_UNSPECIFIED,
|
|
16366
16506
|
}));
|
|
16507
|
+
if (supersededByLeave()) {
|
|
16508
|
+
this.logger.debug('Join superseded by leave; not applying SFU response');
|
|
16509
|
+
return;
|
|
16510
|
+
}
|
|
16367
16511
|
this.currentPublishOptions = publishOptions;
|
|
16368
16512
|
this.fastReconnectDeadlineSeconds = fastReconnectDeadlineSeconds;
|
|
16369
16513
|
if (callState) {
|
|
@@ -16371,6 +16515,10 @@ class Call {
|
|
|
16371
16515
|
}
|
|
16372
16516
|
}
|
|
16373
16517
|
catch (error) {
|
|
16518
|
+
if (supersededByLeave()) {
|
|
16519
|
+
this.logger.debug('Join superseded by leave; ignoring SFU failure');
|
|
16520
|
+
return;
|
|
16521
|
+
}
|
|
16374
16522
|
this.logger.warn('Join SFU request failed', error);
|
|
16375
16523
|
sfuClient.close(StreamSfuClient.JOIN_FAILED, 'Join request failed, connection considered unhealthy');
|
|
16376
16524
|
// restore the previous call state if the join-flow fails
|
|
@@ -16378,8 +16526,8 @@ class Call {
|
|
|
16378
16526
|
throw error;
|
|
16379
16527
|
}
|
|
16380
16528
|
}
|
|
16381
|
-
// If the user left while this join was in flight, bail before re-setting JOINED
|
|
16382
|
-
// peer-connection setup below
|
|
16529
|
+
// If the user left while this join was in flight, bail before re-setting JOINED
|
|
16530
|
+
// or starting peer-connection setup below.
|
|
16383
16531
|
if (supersededByLeave()) {
|
|
16384
16532
|
this.logger.debug('Join superseded by leave; aborting join flow');
|
|
16385
16533
|
return;
|
|
@@ -16408,6 +16556,10 @@ class Call {
|
|
|
16408
16556
|
unifiedSessionId: this.unifiedSessionId,
|
|
16409
16557
|
});
|
|
16410
16558
|
}
|
|
16559
|
+
if (supersededByLeave()) {
|
|
16560
|
+
this.logger.debug('Join superseded by leave; not completing join flow');
|
|
16561
|
+
return;
|
|
16562
|
+
}
|
|
16411
16563
|
// make sure we only track connection timing if we are not calling this method as part of a reconnection flow
|
|
16412
16564
|
if (!performingRejoin && !performingFastReconnect && !performingMigration) {
|
|
16413
16565
|
this.sfuStatsReporter?.sendConnectionTime((Date.now() - connectStartTime) / 1000);
|
|
@@ -16553,15 +16705,20 @@ class Call {
|
|
|
16553
16705
|
* @internal
|
|
16554
16706
|
*/
|
|
16555
16707
|
initPublisherAndSubscriber = async (opts) => {
|
|
16708
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
16556
16709
|
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
|
|
16557
16710
|
const { enable_rtc_stats: enableTracing, reporting_interval_ms: reportingIntervalMs, } = statsOptions;
|
|
16558
16711
|
// Flush the previous reporter's final sample while its peer connections are
|
|
16559
16712
|
// still alive, before we dispose them below. Awaits only the sampling step.
|
|
16560
16713
|
await this.sfuStatsReporter?.flush();
|
|
16714
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16715
|
+
return;
|
|
16561
16716
|
this.sfuStatsReporter?.stop();
|
|
16562
16717
|
this.sfuStatsReporter = undefined;
|
|
16563
16718
|
if (closePreviousInstances && this.subscriber) {
|
|
16564
16719
|
await this.subscriber.dispose();
|
|
16720
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16721
|
+
return;
|
|
16565
16722
|
this.state.removeAllOrphanedTracks();
|
|
16566
16723
|
}
|
|
16567
16724
|
const basePeerConnectionOptions = {
|
|
@@ -16604,6 +16761,8 @@ class Call {
|
|
|
16604
16761
|
if (!isAnonymous) {
|
|
16605
16762
|
if (closePreviousInstances && this.publisher) {
|
|
16606
16763
|
await this.publisher.dispose();
|
|
16764
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16765
|
+
return;
|
|
16607
16766
|
}
|
|
16608
16767
|
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions, {
|
|
16609
16768
|
selfSubEnabled: this.allowOwnTracksLoopback,
|
|
@@ -16640,12 +16799,22 @@ class Call {
|
|
|
16640
16799
|
*
|
|
16641
16800
|
* @internal
|
|
16642
16801
|
* @param data the join call data.
|
|
16802
|
+
* @returns The coordinator response, or undefined if leave superseded the request before it was sent.
|
|
16643
16803
|
*/
|
|
16644
16804
|
doJoinRequest = async (data) => {
|
|
16805
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
16645
16806
|
const location = await this.streamClient.getLocationHint();
|
|
16807
|
+
if (this.leaveGeneration !== joinLeaveGeneration) {
|
|
16808
|
+
this.logger.debug('Join superseded by leave; not sending coordinator request');
|
|
16809
|
+
return;
|
|
16810
|
+
}
|
|
16646
16811
|
const e2ee = !!this.e2eeManager;
|
|
16647
16812
|
const request = { ...data, location, e2ee };
|
|
16648
16813
|
const joinResponse = await this.streamClient.post(`${this.streamClientBasePath}/join`, request);
|
|
16814
|
+
if (this.leaveGeneration !== joinLeaveGeneration) {
|
|
16815
|
+
this.logger.debug('Join superseded by leave; not applying coordinator response');
|
|
16816
|
+
return joinResponse;
|
|
16817
|
+
}
|
|
16649
16818
|
this.state.updateFromCallResponse(joinResponse.call);
|
|
16650
16819
|
this.state.setMembers(joinResponse.members);
|
|
16651
16820
|
this.state.setOwnCapabilities(joinResponse.own_capabilities);
|
|
@@ -16656,6 +16825,8 @@ class Call {
|
|
|
16656
16825
|
if (!isReconnecting && this.ringing && !this.isCreatedByMe) {
|
|
16657
16826
|
// signals other users that I have accepted the incoming call.
|
|
16658
16827
|
await this.accept();
|
|
16828
|
+
if (this.leaveGeneration !== joinLeaveGeneration)
|
|
16829
|
+
return joinResponse;
|
|
16659
16830
|
}
|
|
16660
16831
|
if (this.streamClient._hasConnectionID()) {
|
|
16661
16832
|
this.watching = true;
|
|
@@ -16745,6 +16916,7 @@ class Call {
|
|
|
16745
16916
|
if (callingState === CallingState.JOINING ||
|
|
16746
16917
|
callingState === CallingState.RECONNECTING ||
|
|
16747
16918
|
callingState === CallingState.MIGRATING ||
|
|
16919
|
+
callingState === CallingState.LEFT ||
|
|
16748
16920
|
callingState === CallingState.RECONNECTING_FAILED)
|
|
16749
16921
|
return;
|
|
16750
16922
|
// Drop redundant reconnect calls. If a reconnect is already queued or
|
|
@@ -16753,19 +16925,27 @@ class Call {
|
|
|
16753
16925
|
// `POST /join` per entry) once the call is already healthy again.
|
|
16754
16926
|
if (hasPending(this.reconnectConcurrencyTag))
|
|
16755
16927
|
return;
|
|
16928
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16929
|
+
const supersededByLeave = () => this.leaveGeneration !== reconnectLeaveGeneration;
|
|
16756
16930
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
16931
|
+
if (supersededByLeave())
|
|
16932
|
+
return;
|
|
16757
16933
|
const reconnectStartTime = Date.now();
|
|
16758
16934
|
this.reconnectStrategy = strategy;
|
|
16759
16935
|
this.reconnectReason = reason;
|
|
16760
16936
|
const sfuRejoinFailures = new Map();
|
|
16761
16937
|
const markAsReconnectingFailed = async () => {
|
|
16938
|
+
if (supersededByLeave())
|
|
16939
|
+
return;
|
|
16762
16940
|
try {
|
|
16763
16941
|
// attempt to fetch the call data from the server, as the call
|
|
16764
16942
|
// state might have changed while we were reconnecting or were offline
|
|
16765
16943
|
await this.get();
|
|
16766
16944
|
}
|
|
16767
16945
|
finally {
|
|
16768
|
-
|
|
16946
|
+
if (!supersededByLeave()) {
|
|
16947
|
+
this.state.setCallingState(CallingState.RECONNECTING_FAILED);
|
|
16948
|
+
}
|
|
16769
16949
|
}
|
|
16770
16950
|
};
|
|
16771
16951
|
const giveUpAndLeave = async (message) => {
|
|
@@ -16821,6 +17001,8 @@ class Call {
|
|
|
16821
17001
|
try {
|
|
16822
17002
|
// wait until the network is available
|
|
16823
17003
|
await this.networkAvailableTask?.promise;
|
|
17004
|
+
if (supersededByLeave())
|
|
17005
|
+
return;
|
|
16824
17006
|
this.logger.info(`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
|
|
16825
17007
|
switch (this.reconnectStrategy) {
|
|
16826
17008
|
case WebsocketReconnectStrategy.UNSPECIFIED:
|
|
@@ -16860,6 +17042,8 @@ class Call {
|
|
|
16860
17042
|
break; // do-while loop, reconnection worked, exit the loop
|
|
16861
17043
|
}
|
|
16862
17044
|
catch (error) {
|
|
17045
|
+
if (supersededByLeave())
|
|
17046
|
+
return;
|
|
16863
17047
|
if (attemptedStrategy === WebsocketReconnectStrategy.REJOIN) {
|
|
16864
17048
|
const failedSfu = this.credentials?.server.edge_name;
|
|
16865
17049
|
if (failedSfu) {
|
|
@@ -16890,6 +17074,8 @@ class Call {
|
|
|
16890
17074
|
}
|
|
16891
17075
|
// exponential backoff with jitter, capped at 5 s
|
|
16892
17076
|
await sleep(retryInterval(attempt));
|
|
17077
|
+
if (supersededByLeave())
|
|
17078
|
+
return;
|
|
16893
17079
|
const wasMigrating = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
|
|
16894
17080
|
const mustPerformRejoin = (Date.now() - reconnectStartTime) / 1000 >
|
|
16895
17081
|
this.fastReconnectDeadlineSeconds;
|
|
@@ -16908,7 +17094,8 @@ class Call {
|
|
|
16908
17094
|
this.reconnectStrategy = nextStrategy;
|
|
16909
17095
|
this.logger.info(`[Reconnect] ${currentStrategy} (${this.reconnectAttempts}) failed. Attempting with ${WebsocketReconnectStrategy[nextStrategy]}`, error);
|
|
16910
17096
|
}
|
|
16911
|
-
} while (
|
|
17097
|
+
} while (!supersededByLeave() &&
|
|
17098
|
+
this.state.callingState !== CallingState.JOINED &&
|
|
16912
17099
|
this.state.callingState !== CallingState.RECONNECTING_FAILED &&
|
|
16913
17100
|
this.state.callingState !== CallingState.LEFT);
|
|
16914
17101
|
this.logger.info('[Reconnect] Reconnection flow finished');
|
|
@@ -16919,10 +17106,13 @@ class Call {
|
|
|
16919
17106
|
* @internal
|
|
16920
17107
|
*/
|
|
16921
17108
|
reconnectFast = async () => {
|
|
17109
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16922
17110
|
const reconnectStartTime = Date.now();
|
|
16923
17111
|
this.reconnectStrategy = WebsocketReconnectStrategy.FAST;
|
|
16924
17112
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
16925
17113
|
await this.doJoin(this.joinCallData);
|
|
17114
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17115
|
+
return;
|
|
16926
17116
|
await this.get(); // fetch the latest call state, as it might have changed
|
|
16927
17117
|
this.sfuStatsReporter?.sendReconnectionTime(WebsocketReconnectStrategy.FAST, (Date.now() - reconnectStartTime) / 1000);
|
|
16928
17118
|
};
|
|
@@ -16931,6 +17121,7 @@ class Call {
|
|
|
16931
17121
|
* @internal
|
|
16932
17122
|
*/
|
|
16933
17123
|
reconnectRejoin = async () => {
|
|
17124
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16934
17125
|
const reconnectStartTime = Date.now();
|
|
16935
17126
|
this.reconnectStrategy = WebsocketReconnectStrategy.REJOIN;
|
|
16936
17127
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
@@ -16938,7 +17129,11 @@ class Call {
|
|
|
16938
17129
|
? 'network-available'
|
|
16939
17130
|
: 'full-rejoin';
|
|
16940
17131
|
await this.clientEventReporter.withJoinLifecycle(this.cid, { joinReason }, () => this.doJoin(this.joinCallData));
|
|
17132
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17133
|
+
return;
|
|
16941
17134
|
await this.restorePublishedTracks();
|
|
17135
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17136
|
+
return;
|
|
16942
17137
|
this.restoreSubscribedTracks();
|
|
16943
17138
|
this.sfuStatsReporter?.sendReconnectionTime(WebsocketReconnectStrategy.REJOIN, (Date.now() - reconnectStartTime) / 1000);
|
|
16944
17139
|
};
|
|
@@ -16947,6 +17142,7 @@ class Call {
|
|
|
16947
17142
|
* @internal
|
|
16948
17143
|
*/
|
|
16949
17144
|
reconnectMigrate = async () => {
|
|
17145
|
+
const reconnectLeaveGeneration = this.leaveGeneration;
|
|
16950
17146
|
const reconnectStartTime = Date.now();
|
|
16951
17147
|
const currentSfuClient = this.sfuClient;
|
|
16952
17148
|
if (!currentSfuClient) {
|
|
@@ -16960,26 +17156,32 @@ class Call {
|
|
|
16960
17156
|
currentPublisher?.detachEventHandlers();
|
|
16961
17157
|
const migrationTask = makeSafePromise(currentSfuClient.enterMigration());
|
|
16962
17158
|
try {
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
16966
|
-
|
|
16967
|
-
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
17159
|
+
try {
|
|
17160
|
+
const currentSfu = currentSfuClient.edgeName;
|
|
17161
|
+
await this.clientEventReporter.withJoinLifecycle(this.cid, { joinReason: 'migration' }, () => this.doJoin({
|
|
17162
|
+
...this.joinCallData,
|
|
17163
|
+
migrating_from: currentSfu,
|
|
17164
|
+
migrating_from_list: [currentSfu],
|
|
17165
|
+
}));
|
|
17166
|
+
}
|
|
17167
|
+
finally {
|
|
17168
|
+
// cleanup the migration_from field after the migration is complete or failed
|
|
17169
|
+
// as we don't want to keep dirty data in the join call data
|
|
17170
|
+
delete this.joinCallData?.migrating_from;
|
|
17171
|
+
delete this.joinCallData?.migrating_from_list;
|
|
17172
|
+
}
|
|
17173
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17174
|
+
return;
|
|
17175
|
+
await this.restorePublishedTracks();
|
|
17176
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17177
|
+
return;
|
|
17178
|
+
this.restoreSubscribedTracks();
|
|
16979
17179
|
// Wait for the migration to complete, then close the previous SFU client
|
|
16980
17180
|
// and the peer connection instances. In case of failure, the migration
|
|
16981
17181
|
// task would throw an error and REJOIN would be attempted.
|
|
16982
17182
|
await migrationTask();
|
|
17183
|
+
if (this.leaveGeneration !== reconnectLeaveGeneration)
|
|
17184
|
+
return;
|
|
16983
17185
|
// in MIGRATE, we can consider the call as joined only after
|
|
16984
17186
|
// `participantMigrationComplete` event is received, signaled by
|
|
16985
17187
|
// the `migrationTask`
|
|
@@ -17264,10 +17466,11 @@ class Call {
|
|
|
17264
17466
|
* Must be called before {@link join} so the RTCPeerConnection can be
|
|
17265
17467
|
* configured for E2EE.
|
|
17266
17468
|
*
|
|
17267
|
-
* The
|
|
17268
|
-
*
|
|
17269
|
-
*
|
|
17270
|
-
*
|
|
17469
|
+
* The application owns the manager's lifetime - the SDK never disposes it, and
|
|
17470
|
+
* closing the peer connections does not release it. Discard the manager
|
|
17471
|
+
* together with this call instance once its flow has ended, and create a fresh
|
|
17472
|
+
* pair for a later flow: a disposed manager throws from `encrypt`/`decrypt`
|
|
17473
|
+
* rather than silently publishing nothing.
|
|
17271
17474
|
*
|
|
17272
17475
|
* @param e2ee - Any `E2EEManager`. Use `EncryptionManager.create()` for the
|
|
17273
17476
|
* built-in AES-GCM scheme, or pass your own implementation.
|
|
@@ -19434,11 +19637,11 @@ class StreamClient {
|
|
|
19434
19637
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
19435
19638
|
};
|
|
19436
19639
|
getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
|
|
19437
|
-
"1.
|
|
19640
|
+
"1.61.1";
|
|
19438
19641
|
getUserAgent = () => {
|
|
19439
19642
|
if (!this.cachedUserAgent) {
|
|
19440
19643
|
const { clientAppIdentifier = {} } = this.options;
|
|
19441
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
19644
|
+
const { sdkName = 'js', sdkVersion = "1.61.1", ...extras } = clientAppIdentifier;
|
|
19442
19645
|
this.cachedUserAgent = [
|
|
19443
19646
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
19444
19647
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -19973,7 +20176,11 @@ class ClientEventReporter {
|
|
|
19973
20176
|
return;
|
|
19974
20177
|
}
|
|
19975
20178
|
if (event.state === 'failed') {
|
|
19976
|
-
|
|
20179
|
+
const iceConnected = event.iceConnectionState === 'connected' ||
|
|
20180
|
+
event.iceConnectionState === 'completed';
|
|
20181
|
+
this.emitPeerConnectionFailure(cid, role, iceConnected ? 'DTLS_CONNECTIVITY_FAILED' : 'ICE_CONNECTIVITY_FAILED', iceConnected
|
|
20182
|
+
? 'DTLS connectivity checks failed'
|
|
20183
|
+
: 'ICE connectivity checks failed');
|
|
19977
20184
|
return;
|
|
19978
20185
|
}
|
|
19979
20186
|
switch (event.state) {
|
|
@@ -21147,9 +21354,9 @@ class EncryptionManager extends TypedEventEmitter {
|
|
|
21147
21354
|
}
|
|
21148
21355
|
if ((this.piped ??= new WeakSet()).has(target))
|
|
21149
21356
|
return;
|
|
21150
|
-
this.piped.add(target);
|
|
21151
21357
|
// @ts-expect-error createEncodedStreams is not in the standard typedefs
|
|
21152
21358
|
const { readable, writable } = target.createEncodedStreams();
|
|
21359
|
+
this.piped.add(target);
|
|
21153
21360
|
this.worker.postMessage({ type: 'cmd.setup_transform', ...options, readable, writable }, [readable, writable]);
|
|
21154
21361
|
};
|
|
21155
21362
|
/**
|
|
@@ -21238,5 +21445,5 @@ var e2eeWorker$1 = /*#__PURE__*/Object.freeze({
|
|
|
21238
21445
|
e2eeWorker: e2eeWorker
|
|
21239
21446
|
});
|
|
21240
21447
|
|
|
21241
|
-
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 };
|
|
21448
|
+
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 };
|
|
21242
21449
|
//# sourceMappingURL=index.es.js.map
|