@stream-io/video-client 1.55.1 → 1.56.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 +18 -0
- package/dist/index.browser.es.js +192 -103
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +192 -103
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +192 -103
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +4 -0
- package/dist/src/helpers/BlockedAudioTracker.d.ts +16 -1
- package/dist/src/reporting/ClientEventReporter.d.ts +1 -0
- package/dist/src/rtc/Publisher.d.ts +1 -0
- package/dist/src/rtc/types.d.ts +9 -2
- package/dist/src/store/CallState.d.ts +4 -0
- package/dist/src/store/rxUtils.d.ts +5 -0
- package/package.json +2 -2
- package/src/Call.ts +8 -0
- package/src/helpers/BlockedAudioTracker.ts +71 -13
- package/src/helpers/DynascaleManager.ts +14 -3
- package/src/helpers/__tests__/BlockedAudioTracker.test.ts +54 -6
- package/src/helpers/__tests__/DynascaleManager.test.ts +45 -0
- package/src/reporting/ClientEventReporter.ts +37 -31
- package/src/reporting/__tests__/ClientEventReporter.test.ts +159 -44
- package/src/rtc/BasePeerConnection.ts +1 -0
- package/src/rtc/Publisher.ts +78 -58
- package/src/rtc/__tests__/Publisher.test.ts +297 -0
- package/src/rtc/types.ts +13 -11
- package/src/store/CallState.ts +12 -17
- package/src/store/rxUtils.ts +15 -0
package/dist/index.cjs.js
CHANGED
|
@@ -4946,7 +4946,7 @@ const toIceCandidate = (iceTrickle) => {
|
|
|
4946
4946
|
}
|
|
4947
4947
|
};
|
|
4948
4948
|
|
|
4949
|
-
const version = "1.
|
|
4949
|
+
const version = "1.56.0";
|
|
4950
4950
|
const [major, minor, patch] = version.split('.');
|
|
4951
4951
|
let sdkInfo = {
|
|
4952
4952
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5275,6 +5275,23 @@ function wrapWithCancellation(tag, cb) {
|
|
|
5275
5275
|
return { cb: wrapped, onContinued };
|
|
5276
5276
|
}
|
|
5277
5277
|
|
|
5278
|
+
/**
|
|
5279
|
+
* Performs shallow comparison of two arrays.
|
|
5280
|
+
* Expects primitive values: [1, 2, 3] is equal to [2, 1, 3].
|
|
5281
|
+
*/
|
|
5282
|
+
const isShallowArrayEqual = (a, b) => {
|
|
5283
|
+
if (a.length !== b.length)
|
|
5284
|
+
return false;
|
|
5285
|
+
for (const item of a) {
|
|
5286
|
+
if (!b.includes(item))
|
|
5287
|
+
return false;
|
|
5288
|
+
}
|
|
5289
|
+
for (const item of b) {
|
|
5290
|
+
if (!a.includes(item))
|
|
5291
|
+
return false;
|
|
5292
|
+
}
|
|
5293
|
+
return true;
|
|
5294
|
+
};
|
|
5278
5295
|
/**
|
|
5279
5296
|
* Checks if the provided update is a function patch.
|
|
5280
5297
|
*
|
|
@@ -5387,6 +5404,7 @@ var rxUtils = /*#__PURE__*/Object.freeze({
|
|
|
5387
5404
|
createSafeAsyncSubscription: createSafeAsyncSubscription,
|
|
5388
5405
|
createSubscription: createSubscription,
|
|
5389
5406
|
getCurrentValue: getCurrentValue,
|
|
5407
|
+
isShallowArrayEqual: isShallowArrayEqual,
|
|
5390
5408
|
setCurrentValue: setCurrentValue,
|
|
5391
5409
|
setCurrentValueAsync: setCurrentValueAsync,
|
|
5392
5410
|
updateValue: updateValue
|
|
@@ -6127,23 +6145,6 @@ class CallState {
|
|
|
6127
6145
|
this.endedBy$ = this.endedBySubject.asObservable();
|
|
6128
6146
|
this.thumbnails$ = this.thumbnailsSubject.asObservable();
|
|
6129
6147
|
this.closedCaptions$ = this.closedCaptionsSubject.asObservable();
|
|
6130
|
-
/**
|
|
6131
|
-
* Performs shallow comparison of two arrays.
|
|
6132
|
-
* Expects primitive values: [1, 2, 3] is equal to [2, 1, 3].
|
|
6133
|
-
*/
|
|
6134
|
-
const isShallowEqual = (a, b) => {
|
|
6135
|
-
if (a.length !== b.length)
|
|
6136
|
-
return false;
|
|
6137
|
-
for (const item of a) {
|
|
6138
|
-
if (!b.includes(item))
|
|
6139
|
-
return false;
|
|
6140
|
-
}
|
|
6141
|
-
for (const item of b) {
|
|
6142
|
-
if (!a.includes(item))
|
|
6143
|
-
return false;
|
|
6144
|
-
}
|
|
6145
|
-
return true;
|
|
6146
|
-
};
|
|
6147
6148
|
/**
|
|
6148
6149
|
* Creates an Observable from the given subject by piping to the
|
|
6149
6150
|
* `distinctUntilChanged()` operator.
|
|
@@ -6151,7 +6152,7 @@ class CallState {
|
|
|
6151
6152
|
const duc = (subject, comparator) => subject.pipe(rxjs.distinctUntilChanged(comparator));
|
|
6152
6153
|
// primitive values should only emit once the value they hold changes
|
|
6153
6154
|
this.anonymousParticipantCount$ = duc(this.anonymousParticipantCountSubject);
|
|
6154
|
-
this.blockedUserIds$ = duc(this.blockedUserIdsSubject,
|
|
6155
|
+
this.blockedUserIds$ = duc(this.blockedUserIdsSubject, isShallowArrayEqual);
|
|
6155
6156
|
this.backstage$ = duc(this.backstageSubject);
|
|
6156
6157
|
this.callingState$ = duc(this.callingStateSubject);
|
|
6157
6158
|
this.ownCapabilities$ = rxjs.combineLatest([
|
|
@@ -6180,7 +6181,7 @@ class CallState {
|
|
|
6180
6181
|
}
|
|
6181
6182
|
}
|
|
6182
6183
|
return nextCapabilities;
|
|
6183
|
-
}), rxjs.distinctUntilChanged(
|
|
6184
|
+
}), rxjs.distinctUntilChanged(isShallowArrayEqual), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
|
|
6184
6185
|
this.participantCount$ = duc(this.participantCountSubject);
|
|
6185
6186
|
this.recording$ = duc(this.recordingSubject);
|
|
6186
6187
|
this.individualRecording$ = duc(this.individualRecordingSubject);
|
|
@@ -6301,6 +6302,12 @@ class CallState {
|
|
|
6301
6302
|
// trigger re-sorting of participants
|
|
6302
6303
|
this.setCurrentValue(this.participantsSubject, (ps) => ps);
|
|
6303
6304
|
};
|
|
6305
|
+
/**
|
|
6306
|
+
* Returns the comparator currently used to sort the participants.
|
|
6307
|
+
*/
|
|
6308
|
+
getSortParticipantsBy = () => {
|
|
6309
|
+
return this.sortParticipantsBy;
|
|
6310
|
+
};
|
|
6304
6311
|
/**
|
|
6305
6312
|
* Gets the current value of an observable, or undefined if the observable has
|
|
6306
6313
|
* not emitted a value yet.
|
|
@@ -8485,6 +8492,7 @@ class BasePeerConnection {
|
|
|
8485
8492
|
try {
|
|
8486
8493
|
this.onPeerConnectionStateChange?.({
|
|
8487
8494
|
peerType: this.peerType,
|
|
8495
|
+
iceConnectionState: this.pc.iceConnectionState,
|
|
8488
8496
|
...event,
|
|
8489
8497
|
});
|
|
8490
8498
|
}
|
|
@@ -8939,6 +8947,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8939
8947
|
clonedTracks = new Set();
|
|
8940
8948
|
publishOptions;
|
|
8941
8949
|
selfSubEnabled;
|
|
8950
|
+
transceiverLockKey = `pub.tx.${this.lock}`;
|
|
8942
8951
|
/**
|
|
8943
8952
|
* Constructs a new `Publisher` instance.
|
|
8944
8953
|
*/
|
|
@@ -8994,32 +9003,34 @@ class Publisher extends BasePeerConnection {
|
|
|
8994
9003
|
* @param options the publish options to use.
|
|
8995
9004
|
*/
|
|
8996
9005
|
publish = async (track, trackType, options = {}) => {
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
if (publishOption.trackType !== trackType)
|
|
9002
|
-
continue;
|
|
9003
|
-
// create a clone of the track as otherwise the same trackId will
|
|
9004
|
-
// appear in the SDP in multiple transceivers
|
|
9005
|
-
const trackToPublish = this.cloneTrack(track);
|
|
9006
|
-
const bundle = this.transceiverCache.get(publishOption);
|
|
9007
|
-
if (!bundle) {
|
|
9008
|
-
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
9006
|
+
return withoutConcurrency(this.transceiverLockKey, async () => {
|
|
9007
|
+
const publishOptions = this.publishOptions.filter((o) => o.trackType === trackType);
|
|
9008
|
+
if (!publishOptions.length) {
|
|
9009
|
+
throw new Error(`No publish options found for ${TrackType[trackType]}`);
|
|
9009
9010
|
}
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9011
|
+
for (const publishOption of publishOptions) {
|
|
9012
|
+
// create a clone of the track as otherwise the same trackId will
|
|
9013
|
+
// appear in the SDP in multiple transceivers
|
|
9014
|
+
const trackToPublish = this.cloneTrack(track);
|
|
9015
|
+
const bundle = this.transceiverCache.get(publishOption);
|
|
9016
|
+
if (!bundle) {
|
|
9017
|
+
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
9018
|
+
}
|
|
9019
|
+
else {
|
|
9020
|
+
const previousTrack = bundle.transceiver.sender.track;
|
|
9021
|
+
await this.updateTransceiver(bundle, trackToPublish, options);
|
|
9022
|
+
if (!isReactNative()) {
|
|
9023
|
+
this.stopTrack(previousTrack);
|
|
9024
|
+
}
|
|
9015
9025
|
}
|
|
9016
9026
|
}
|
|
9017
|
-
}
|
|
9027
|
+
});
|
|
9018
9028
|
};
|
|
9019
9029
|
/**
|
|
9020
9030
|
* Adds a new transceiver carrying the given track to the peer connection.
|
|
9021
9031
|
*/
|
|
9022
9032
|
addTransceiver = async (track, publishOption, options) => {
|
|
9033
|
+
const trackType = publishOption.trackType;
|
|
9023
9034
|
const encodings = isAudioTrackType(publishOption.trackType)
|
|
9024
9035
|
? computeAudioLayers(publishOption, options)
|
|
9025
9036
|
: computeVideoLayers(track, publishOption);
|
|
@@ -9030,15 +9041,17 @@ class Publisher extends BasePeerConnection {
|
|
|
9030
9041
|
direction: 'sendonly',
|
|
9031
9042
|
sendEncodings,
|
|
9032
9043
|
});
|
|
9044
|
+
// claim the cache entry in the same task that created the transceiver:
|
|
9045
|
+
// an await in between would let a concurrent caller add a second one
|
|
9046
|
+
// for the same publish option
|
|
9047
|
+
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
9048
|
+
this.trackIdToTrackType.set(track.id, trackType);
|
|
9049
|
+
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
9033
9050
|
const params = transceiver.sender.getParameters();
|
|
9034
9051
|
params.degradationPreference =
|
|
9035
9052
|
toRTCDegradationPreference(publishOption.degradationPreference) ??
|
|
9036
9053
|
'maintain-framerate';
|
|
9037
9054
|
await transceiver.sender.setParameters(params);
|
|
9038
|
-
const trackType = publishOption.trackType;
|
|
9039
|
-
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
9040
|
-
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
9041
|
-
this.trackIdToTrackType.set(track.id, trackType);
|
|
9042
9055
|
await this.negotiate();
|
|
9043
9056
|
};
|
|
9044
9057
|
/**
|
|
@@ -9095,33 +9108,35 @@ class Publisher extends BasePeerConnection {
|
|
|
9095
9108
|
* Synchronizes the current Publisher state with the provided publish options.
|
|
9096
9109
|
*/
|
|
9097
9110
|
syncPublishOptions = async () => {
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
const
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
i.
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
const
|
|
9117
|
-
|
|
9118
|
-
option.
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9111
|
+
return withoutConcurrency(this.transceiverLockKey, async () => {
|
|
9112
|
+
// enable publishing with new options -> [av1, vp9]
|
|
9113
|
+
for (const publishOption of this.publishOptions) {
|
|
9114
|
+
const { trackType } = publishOption;
|
|
9115
|
+
if (!this.isPublishing(trackType))
|
|
9116
|
+
continue;
|
|
9117
|
+
if (this.transceiverCache.has(publishOption))
|
|
9118
|
+
continue;
|
|
9119
|
+
const item = this.transceiverCache.find((i) => !!i.transceiver.sender.track &&
|
|
9120
|
+
i.publishOption.trackType === trackType);
|
|
9121
|
+
if (!item)
|
|
9122
|
+
continue;
|
|
9123
|
+
// take the track from the existing transceiver for the same track type,
|
|
9124
|
+
// clone it and publish it with the new publish options
|
|
9125
|
+
const track = this.cloneTrack(item.transceiver.sender.track);
|
|
9126
|
+
await this.addTransceiver(track, publishOption, item.options);
|
|
9127
|
+
}
|
|
9128
|
+
// stop publishing with options not required anymore -> [vp9]
|
|
9129
|
+
for (const item of this.transceiverCache.items()) {
|
|
9130
|
+
const { publishOption, transceiver } = item;
|
|
9131
|
+
const hasPublishOption = this.publishOptions.some((option) => option.id === publishOption.id &&
|
|
9132
|
+
option.trackType === publishOption.trackType);
|
|
9133
|
+
if (hasPublishOption)
|
|
9134
|
+
continue;
|
|
9135
|
+
// it is safe to stop the track here, it is a clone
|
|
9136
|
+
this.stopTrack(transceiver.sender.track);
|
|
9137
|
+
await this.updateTransceiver(item, null);
|
|
9138
|
+
}
|
|
9139
|
+
});
|
|
9125
9140
|
};
|
|
9126
9141
|
/**
|
|
9127
9142
|
* Returns true if the given track type is currently being published to the SFU.
|
|
@@ -9289,12 +9304,23 @@ class Publisher extends BasePeerConnection {
|
|
|
9289
9304
|
negotiate = async (options) => {
|
|
9290
9305
|
return withoutConcurrency(`publisher.negotiate.${this.lock}`, async () => {
|
|
9291
9306
|
const offer = await this.pc.createOffer(options);
|
|
9292
|
-
const tracks = this.getAnnouncedTracks(offer.sdp);
|
|
9293
|
-
if (!tracks.length)
|
|
9294
|
-
throw new Error(`Can't negotiate without any tracks`);
|
|
9295
9307
|
try {
|
|
9296
9308
|
this.isIceRestarting = options?.iceRestart ?? false;
|
|
9297
9309
|
await this.pc.setLocalDescription(offer);
|
|
9310
|
+
// the announced tracks have to be read after `setLocalDescription`.
|
|
9311
|
+
// `transceiver.mid` is null until the offer is applied, and reading the
|
|
9312
|
+
// tracks earlier makes `extractMid` guess the mid from the offer SDP.
|
|
9313
|
+
// That guess goes stale after a rolled-back negotiation, and a mid that
|
|
9314
|
+
// doesn't match the m-section carrying the track stops the SFU from
|
|
9315
|
+
// correlating the two: it then answers with every offered codec instead
|
|
9316
|
+
// of the announced one, and we publish the first codec of that list.
|
|
9317
|
+
// Per https://www.w3.org/TR/webrtc/#set-description, applying a local
|
|
9318
|
+
// offer associates the transceiver with its m-section and sets
|
|
9319
|
+
// `[[Mid]]` to `[[JsepMid]]`; a rollback disassociates a transceiver
|
|
9320
|
+
// that wasn't associated before and resets both slots to null.
|
|
9321
|
+
const tracks = this.getAnnouncedTracks(offer.sdp);
|
|
9322
|
+
if (!tracks.length)
|
|
9323
|
+
throw new Error(`Can't negotiate without any tracks`);
|
|
9298
9324
|
const { sdp: baseSdp = '' } = offer;
|
|
9299
9325
|
const { dangerouslyForceCodec, dangerouslySetStartBitrateFactor, fmtpLine, } = this.clientPublishOptions || {};
|
|
9300
9326
|
let sdp = dangerouslyForceCodec
|
|
@@ -10919,34 +10945,67 @@ class AudioBindingsWatchdog {
|
|
|
10919
10945
|
class BlockedAudioTracker {
|
|
10920
10946
|
logger = videoLoggerSystem.getLogger('BlockedAudioTracker');
|
|
10921
10947
|
tracer;
|
|
10922
|
-
blockedElementsSubject = new rxjs.BehaviorSubject(
|
|
10948
|
+
blockedElementsSubject = new rxjs.BehaviorSubject([]);
|
|
10923
10949
|
/**
|
|
10924
10950
|
* Whether the browser's autoplay policy is blocking audio playback.
|
|
10925
10951
|
* Will be `true` when at least one audio element is currently blocked.
|
|
10926
10952
|
* Use {@link resumeAudio} within a user gesture to unblock.
|
|
10927
10953
|
*/
|
|
10928
|
-
autoplayBlocked$ = this.blockedElementsSubject.pipe(rxjs.map((elements) => elements.
|
|
10954
|
+
autoplayBlocked$ = this.blockedElementsSubject.pipe(rxjs.map((elements) => elements.length > 0), rxjs.distinctUntilChanged());
|
|
10955
|
+
/**
|
|
10956
|
+
* The list of participant `sessionId`s whose audio element is currently
|
|
10957
|
+
* blocked by the browser's autoplay policy. Only some participants may be
|
|
10958
|
+
* blocked (e.g. one joined while a gesture was active and a later one was
|
|
10959
|
+
* not), so use this to render a per-participant affordance rather than a
|
|
10960
|
+
* call-wide one. Call {@link resumeAudio} within a user gesture to unblock
|
|
10961
|
+
* all of them.
|
|
10962
|
+
*
|
|
10963
|
+
* Session ids are registered together with the audio element.
|
|
10964
|
+
*/
|
|
10965
|
+
blockedSessionIds$ = this.blockedElementsSubject.pipe(rxjs.map((elements) => {
|
|
10966
|
+
const ids = [];
|
|
10967
|
+
elements.forEach(({ sessionId }) => {
|
|
10968
|
+
if (sessionId && !ids.includes(sessionId))
|
|
10969
|
+
ids.push(sessionId);
|
|
10970
|
+
});
|
|
10971
|
+
return ids;
|
|
10972
|
+
}), rxjs.distinctUntilChanged(isShallowArrayEqual), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
|
|
10929
10973
|
constructor(tracer) {
|
|
10930
10974
|
this.tracer = tracer;
|
|
10931
10975
|
}
|
|
10932
10976
|
/**
|
|
10933
10977
|
* Registers an audio element as blocked by the browser's autoplay policy.
|
|
10934
10978
|
*/
|
|
10935
|
-
markBlocked = (audioElement, blocked) => {
|
|
10979
|
+
markBlocked = (audioElement, blocked, sessionId) => {
|
|
10936
10980
|
setCurrentValue(this.blockedElementsSubject, (elements) => {
|
|
10937
|
-
if (blocked)
|
|
10938
|
-
elements.
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10981
|
+
if (!blocked) {
|
|
10982
|
+
return elements.filter(({ element }) => element !== audioElement);
|
|
10983
|
+
}
|
|
10984
|
+
const existing = elements.find(({ element }) => element === audioElement);
|
|
10985
|
+
if (existing) {
|
|
10986
|
+
return elements.map((entry) => entry.element === audioElement
|
|
10987
|
+
? { element: audioElement, sessionId: sessionId ?? entry.sessionId }
|
|
10988
|
+
: entry);
|
|
10989
|
+
}
|
|
10990
|
+
return [...elements, { element: audioElement, sessionId }];
|
|
10942
10991
|
});
|
|
10943
10992
|
};
|
|
10993
|
+
/**
|
|
10994
|
+
* Clears all tracked elements. The tracker stays usable afterwards.
|
|
10995
|
+
*/
|
|
10996
|
+
reset = () => {
|
|
10997
|
+
if (this.blockedElementsSubject.getValue().length > 0) {
|
|
10998
|
+
this.blockedElementsSubject.next([]);
|
|
10999
|
+
}
|
|
11000
|
+
};
|
|
10944
11001
|
/**
|
|
10945
11002
|
* Returns whether the given audio element is currently flagged as blocked
|
|
10946
11003
|
* by the browser's autoplay policy.
|
|
10947
11004
|
*/
|
|
10948
11005
|
isBlocked = (audioElement) => {
|
|
10949
|
-
return this.blockedElementsSubject
|
|
11006
|
+
return this.blockedElementsSubject
|
|
11007
|
+
.getValue()
|
|
11008
|
+
.some(({ element }) => element === audioElement);
|
|
10950
11009
|
};
|
|
10951
11010
|
/**
|
|
10952
11011
|
* Plays all audio elements blocked by the browser's autoplay policy.
|
|
@@ -10955,17 +11014,18 @@ class BlockedAudioTracker {
|
|
|
10955
11014
|
resumeAudio = async () => {
|
|
10956
11015
|
this.tracer.trace('resumeAudio', null);
|
|
10957
11016
|
await setCurrentValueAsync(this.blockedElementsSubject, async (elements) => {
|
|
10958
|
-
|
|
11017
|
+
let next = elements;
|
|
11018
|
+
await Promise.all(elements.map(async ({ element }) => {
|
|
10959
11019
|
try {
|
|
10960
11020
|
if (element.srcObject)
|
|
10961
11021
|
await timeboxed([element.play()], 2000);
|
|
10962
|
-
|
|
11022
|
+
next = next.filter((entry) => entry.element !== element);
|
|
10963
11023
|
}
|
|
10964
11024
|
catch (err) {
|
|
10965
11025
|
this.logger.warn(`Can't resume audio for element`, element, err);
|
|
10966
11026
|
}
|
|
10967
11027
|
}));
|
|
10968
|
-
return
|
|
11028
|
+
return next;
|
|
10969
11029
|
});
|
|
10970
11030
|
};
|
|
10971
11031
|
}
|
|
@@ -11497,6 +11557,12 @@ class DynascaleManager {
|
|
|
11497
11557
|
let sourceNode = undefined;
|
|
11498
11558
|
let gainNode = undefined;
|
|
11499
11559
|
let audioWatchdog = undefined;
|
|
11560
|
+
const clearBlockedAudio = () => {
|
|
11561
|
+
if (!this.blockedAudioTracker.isBlocked(audioElement))
|
|
11562
|
+
return;
|
|
11563
|
+
this.blockedAudioTracker.markBlocked(audioElement, false);
|
|
11564
|
+
};
|
|
11565
|
+
audioElement.addEventListener('playing', clearBlockedAudio);
|
|
11500
11566
|
const isAudioTrack = trackType === 'audioTrack';
|
|
11501
11567
|
const trackKey = isAudioTrack ? 'audioStream' : 'screenShareAudioStream';
|
|
11502
11568
|
const updateMediaStreamSubscription = participant$
|
|
@@ -11510,7 +11576,7 @@ class DynascaleManager {
|
|
|
11510
11576
|
audioWatchdog?.dispose();
|
|
11511
11577
|
audioWatchdog = undefined;
|
|
11512
11578
|
if (!source) {
|
|
11513
|
-
|
|
11579
|
+
clearBlockedAudio();
|
|
11514
11580
|
return;
|
|
11515
11581
|
}
|
|
11516
11582
|
// Safari has a special quirk that prevents playing audio until the user
|
|
@@ -11538,7 +11604,7 @@ class DynascaleManager {
|
|
|
11538
11604
|
this.tracer.trace('audioPlaybackError', e.message);
|
|
11539
11605
|
if (e.name === 'NotAllowedError') {
|
|
11540
11606
|
this.tracer.trace('audioPlaybackBlocked', null);
|
|
11541
|
-
this.blockedAudioTracker.markBlocked(audioElement, true);
|
|
11607
|
+
this.blockedAudioTracker.markBlocked(audioElement, true, sessionId);
|
|
11542
11608
|
}
|
|
11543
11609
|
this.logger.warn(`Failed to play audio stream`, e);
|
|
11544
11610
|
});
|
|
@@ -11571,7 +11637,8 @@ class DynascaleManager {
|
|
|
11571
11637
|
});
|
|
11572
11638
|
audioElement.autoplay = true;
|
|
11573
11639
|
return () => {
|
|
11574
|
-
|
|
11640
|
+
audioElement.removeEventListener('playing', clearBlockedAudio);
|
|
11641
|
+
clearBlockedAudio();
|
|
11575
11642
|
sinkIdSubscription?.unsubscribe();
|
|
11576
11643
|
volumeSubscription.unsubscribe();
|
|
11577
11644
|
updateMediaStreamSubscription.unsubscribe();
|
|
@@ -14746,6 +14813,7 @@ class Call {
|
|
|
14746
14813
|
this.screenShare = new ScreenShareManager(this);
|
|
14747
14814
|
this.trackSubscriptionManager = new TrackSubscriptionManager(this.state, this.tracer);
|
|
14748
14815
|
this.blockedAudioTracker = new BlockedAudioTracker(this.tracer);
|
|
14816
|
+
this.leaveCallHooks.add(this.blockedAudioTracker.reset);
|
|
14749
14817
|
if (typeof document !== 'undefined') {
|
|
14750
14818
|
this.audioBindingsWatchdog = new AudioBindingsWatchdog(this.state, this.tracer);
|
|
14751
14819
|
this.viewportTracker = new ViewportTracker(this.state);
|
|
@@ -16369,6 +16437,12 @@ class Call {
|
|
|
16369
16437
|
setSortParticipantsBy = (criteria) => {
|
|
16370
16438
|
return this.state.setSortParticipantsBy(criteria);
|
|
16371
16439
|
};
|
|
16440
|
+
/**
|
|
16441
|
+
* Returns the comparator currently used to sort the participants.
|
|
16442
|
+
*/
|
|
16443
|
+
getSortParticipantsBy = () => {
|
|
16444
|
+
return this.state.getSortParticipantsBy();
|
|
16445
|
+
};
|
|
16372
16446
|
/**
|
|
16373
16447
|
* Sends a reaction to the other call participants.
|
|
16374
16448
|
*
|
|
@@ -18436,11 +18510,11 @@ class StreamClient {
|
|
|
18436
18510
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
18437
18511
|
};
|
|
18438
18512
|
getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
|
|
18439
|
-
"1.
|
|
18513
|
+
"1.56.0";
|
|
18440
18514
|
getUserAgent = () => {
|
|
18441
18515
|
if (!this.cachedUserAgent) {
|
|
18442
18516
|
const { clientAppIdentifier = {} } = this.options;
|
|
18443
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
18517
|
+
const { sdkName = 'js', sdkVersion = "1.56.0", ...extras } = clientAppIdentifier;
|
|
18444
18518
|
this.cachedUserAgent = [
|
|
18445
18519
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
18446
18520
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -18777,8 +18851,8 @@ class ClientEventReporter {
|
|
|
18777
18851
|
applyErrorIfAbsent(this.wsPairs.get(cid), stageError);
|
|
18778
18852
|
this.failCoordinator(cid);
|
|
18779
18853
|
this.failWs(cid);
|
|
18780
|
-
this.emitPeerConnectionFailure(cid, 'publish', code, reason
|
|
18781
|
-
this.emitPeerConnectionFailure(cid, 'subscribe', code, reason
|
|
18854
|
+
this.emitPeerConnectionFailure(cid, 'publish', code, reason);
|
|
18855
|
+
this.emitPeerConnectionFailure(cid, 'subscribe', code, reason);
|
|
18782
18856
|
}
|
|
18783
18857
|
catch (err) {
|
|
18784
18858
|
this.logger.warn('Failed to report abort', err);
|
|
@@ -18790,7 +18864,7 @@ class ClientEventReporter {
|
|
|
18790
18864
|
if (this.wsPairs.get(cid))
|
|
18791
18865
|
this.failWs(cid);
|
|
18792
18866
|
for (const role of ['publish', 'subscribe']) {
|
|
18793
|
-
this.emitPeerConnectionFailure(cid, role, 'CLIENT_ABORTED', 'superseded by a new join attempt'
|
|
18867
|
+
this.emitPeerConnectionFailure(cid, role, 'CLIENT_ABORTED', 'superseded by a new join attempt');
|
|
18794
18868
|
}
|
|
18795
18869
|
};
|
|
18796
18870
|
emitJoinInitiated = (cid) => {
|
|
@@ -18952,21 +19026,24 @@ class ClientEventReporter {
|
|
|
18952
19026
|
};
|
|
18953
19027
|
onPeerConnectionStateChange = (cid, event) => {
|
|
18954
19028
|
const role = event.peerType === PeerType.SUBSCRIBER ? 'subscribe' : 'publish';
|
|
18955
|
-
|
|
18956
|
-
|
|
19029
|
+
const pair = this.peerConnectionPairs.get(pcKey(cid, role));
|
|
19030
|
+
if (pair)
|
|
19031
|
+
pair.lastIceState = event.iceConnectionState;
|
|
19032
|
+
if (event.stateType === 'ice') {
|
|
19033
|
+
if (event.state === 'failed') {
|
|
19034
|
+
this.emitPeerConnectionFailure(cid, role, 'ICE_CONNECTIVITY_FAILED', 'ICE connectivity checks failed');
|
|
19035
|
+
}
|
|
18957
19036
|
return;
|
|
18958
19037
|
}
|
|
18959
|
-
if (event.
|
|
18960
|
-
this.emitPeerConnectionFailure(cid, role, 'DTLS_CONNECTIVITY_FAILED', 'DTLS connectivity checks failed'
|
|
19038
|
+
if (event.state === 'failed') {
|
|
19039
|
+
this.emitPeerConnectionFailure(cid, role, 'DTLS_CONNECTIVITY_FAILED', 'DTLS connectivity checks failed');
|
|
18961
19040
|
return;
|
|
18962
19041
|
}
|
|
18963
|
-
if (event.stateType !== 'peerConnection')
|
|
18964
|
-
return;
|
|
18965
19042
|
switch (event.state) {
|
|
18966
19043
|
case 'connecting':
|
|
18967
|
-
if (
|
|
19044
|
+
if (pair)
|
|
18968
19045
|
return;
|
|
18969
|
-
this.openPeerConnectionPair(cid, role);
|
|
19046
|
+
this.openPeerConnectionPair(cid, role, event.iceConnectionState);
|
|
18970
19047
|
break;
|
|
18971
19048
|
case 'connected':
|
|
18972
19049
|
this.emitPeerConnectionSuccess(cid, role);
|
|
@@ -18974,7 +19051,7 @@ class ClientEventReporter {
|
|
|
18974
19051
|
break;
|
|
18975
19052
|
}
|
|
18976
19053
|
};
|
|
18977
|
-
openPeerConnectionPair = (cid, role) => {
|
|
19054
|
+
openPeerConnectionPair = (cid, role, iceConnectionState) => {
|
|
18978
19055
|
const key = pcKey(cid, role);
|
|
18979
19056
|
const pair = {
|
|
18980
19057
|
sid: generateUUIDv4(),
|
|
@@ -18984,6 +19061,7 @@ class ClientEventReporter {
|
|
|
18984
19061
|
sfuId: this.getSfuId(cid),
|
|
18985
19062
|
userSessionId: this.getUserSessionId(cid),
|
|
18986
19063
|
wasPreviouslyConnected: this.pcEverConnected.get(key) === true,
|
|
19064
|
+
lastIceState: iceConnectionState,
|
|
18987
19065
|
};
|
|
18988
19066
|
this.peerConnectionPairs.set(key, pair);
|
|
18989
19067
|
this.sendForCall(cid, {
|
|
@@ -19019,7 +19097,7 @@ class ClientEventReporter {
|
|
|
19019
19097
|
});
|
|
19020
19098
|
this.peerConnectionPairs.delete(key);
|
|
19021
19099
|
};
|
|
19022
|
-
emitPeerConnectionFailure = (cid, role, code, reason
|
|
19100
|
+
emitPeerConnectionFailure = (cid, role, code, reason) => {
|
|
19023
19101
|
const key = pcKey(cid, role);
|
|
19024
19102
|
const pair = this.peerConnectionPairs.get(key);
|
|
19025
19103
|
if (!pair)
|
|
@@ -19037,7 +19115,7 @@ class ClientEventReporter {
|
|
|
19037
19115
|
outcome: 'failure',
|
|
19038
19116
|
retry_count_attempt: 0,
|
|
19039
19117
|
elapsed_time: Date.now() - pair.startedAt,
|
|
19040
|
-
ice_state:
|
|
19118
|
+
ice_state: toReportedIceState(pair.lastIceState),
|
|
19041
19119
|
retry_failure_reason: reason,
|
|
19042
19120
|
retry_failure_code: code,
|
|
19043
19121
|
});
|
|
@@ -19103,6 +19181,17 @@ class ClientEventReporter {
|
|
|
19103
19181
|
return false;
|
|
19104
19182
|
};
|
|
19105
19183
|
}
|
|
19184
|
+
const toReportedIceState = (state) => {
|
|
19185
|
+
switch (state) {
|
|
19186
|
+
case 'connected':
|
|
19187
|
+
case 'completed':
|
|
19188
|
+
return 'CONNECTED';
|
|
19189
|
+
case 'failed':
|
|
19190
|
+
return 'FAILED';
|
|
19191
|
+
default:
|
|
19192
|
+
return 'NOT_CONNECTED';
|
|
19193
|
+
}
|
|
19194
|
+
};
|
|
19106
19195
|
const readPermissionStatus = (permission) => {
|
|
19107
19196
|
const state = getCurrentValue(permission.asStateObservable());
|
|
19108
19197
|
switch (state) {
|