@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.es.js
CHANGED
|
@@ -4927,7 +4927,7 @@ const toIceCandidate = (iceTrickle) => {
|
|
|
4927
4927
|
}
|
|
4928
4928
|
};
|
|
4929
4929
|
|
|
4930
|
-
const version = "1.
|
|
4930
|
+
const version = "1.56.0";
|
|
4931
4931
|
const [major, minor, patch] = version.split('.');
|
|
4932
4932
|
let sdkInfo = {
|
|
4933
4933
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5256,6 +5256,23 @@ function wrapWithCancellation(tag, cb) {
|
|
|
5256
5256
|
return { cb: wrapped, onContinued };
|
|
5257
5257
|
}
|
|
5258
5258
|
|
|
5259
|
+
/**
|
|
5260
|
+
* Performs shallow comparison of two arrays.
|
|
5261
|
+
* Expects primitive values: [1, 2, 3] is equal to [2, 1, 3].
|
|
5262
|
+
*/
|
|
5263
|
+
const isShallowArrayEqual = (a, b) => {
|
|
5264
|
+
if (a.length !== b.length)
|
|
5265
|
+
return false;
|
|
5266
|
+
for (const item of a) {
|
|
5267
|
+
if (!b.includes(item))
|
|
5268
|
+
return false;
|
|
5269
|
+
}
|
|
5270
|
+
for (const item of b) {
|
|
5271
|
+
if (!a.includes(item))
|
|
5272
|
+
return false;
|
|
5273
|
+
}
|
|
5274
|
+
return true;
|
|
5275
|
+
};
|
|
5259
5276
|
/**
|
|
5260
5277
|
* Checks if the provided update is a function patch.
|
|
5261
5278
|
*
|
|
@@ -5368,6 +5385,7 @@ var rxUtils = /*#__PURE__*/Object.freeze({
|
|
|
5368
5385
|
createSafeAsyncSubscription: createSafeAsyncSubscription,
|
|
5369
5386
|
createSubscription: createSubscription,
|
|
5370
5387
|
getCurrentValue: getCurrentValue,
|
|
5388
|
+
isShallowArrayEqual: isShallowArrayEqual,
|
|
5371
5389
|
setCurrentValue: setCurrentValue,
|
|
5372
5390
|
setCurrentValueAsync: setCurrentValueAsync,
|
|
5373
5391
|
updateValue: updateValue
|
|
@@ -6108,23 +6126,6 @@ class CallState {
|
|
|
6108
6126
|
this.endedBy$ = this.endedBySubject.asObservable();
|
|
6109
6127
|
this.thumbnails$ = this.thumbnailsSubject.asObservable();
|
|
6110
6128
|
this.closedCaptions$ = this.closedCaptionsSubject.asObservable();
|
|
6111
|
-
/**
|
|
6112
|
-
* Performs shallow comparison of two arrays.
|
|
6113
|
-
* Expects primitive values: [1, 2, 3] is equal to [2, 1, 3].
|
|
6114
|
-
*/
|
|
6115
|
-
const isShallowEqual = (a, b) => {
|
|
6116
|
-
if (a.length !== b.length)
|
|
6117
|
-
return false;
|
|
6118
|
-
for (const item of a) {
|
|
6119
|
-
if (!b.includes(item))
|
|
6120
|
-
return false;
|
|
6121
|
-
}
|
|
6122
|
-
for (const item of b) {
|
|
6123
|
-
if (!a.includes(item))
|
|
6124
|
-
return false;
|
|
6125
|
-
}
|
|
6126
|
-
return true;
|
|
6127
|
-
};
|
|
6128
6129
|
/**
|
|
6129
6130
|
* Creates an Observable from the given subject by piping to the
|
|
6130
6131
|
* `distinctUntilChanged()` operator.
|
|
@@ -6132,7 +6133,7 @@ class CallState {
|
|
|
6132
6133
|
const duc = (subject, comparator) => subject.pipe(distinctUntilChanged(comparator));
|
|
6133
6134
|
// primitive values should only emit once the value they hold changes
|
|
6134
6135
|
this.anonymousParticipantCount$ = duc(this.anonymousParticipantCountSubject);
|
|
6135
|
-
this.blockedUserIds$ = duc(this.blockedUserIdsSubject,
|
|
6136
|
+
this.blockedUserIds$ = duc(this.blockedUserIdsSubject, isShallowArrayEqual);
|
|
6136
6137
|
this.backstage$ = duc(this.backstageSubject);
|
|
6137
6138
|
this.callingState$ = duc(this.callingStateSubject);
|
|
6138
6139
|
this.ownCapabilities$ = combineLatest([
|
|
@@ -6161,7 +6162,7 @@ class CallState {
|
|
|
6161
6162
|
}
|
|
6162
6163
|
}
|
|
6163
6164
|
return nextCapabilities;
|
|
6164
|
-
}), distinctUntilChanged(
|
|
6165
|
+
}), distinctUntilChanged(isShallowArrayEqual), shareReplay({ bufferSize: 1, refCount: true }));
|
|
6165
6166
|
this.participantCount$ = duc(this.participantCountSubject);
|
|
6166
6167
|
this.recording$ = duc(this.recordingSubject);
|
|
6167
6168
|
this.individualRecording$ = duc(this.individualRecordingSubject);
|
|
@@ -6282,6 +6283,12 @@ class CallState {
|
|
|
6282
6283
|
// trigger re-sorting of participants
|
|
6283
6284
|
this.setCurrentValue(this.participantsSubject, (ps) => ps);
|
|
6284
6285
|
};
|
|
6286
|
+
/**
|
|
6287
|
+
* Returns the comparator currently used to sort the participants.
|
|
6288
|
+
*/
|
|
6289
|
+
getSortParticipantsBy = () => {
|
|
6290
|
+
return this.sortParticipantsBy;
|
|
6291
|
+
};
|
|
6285
6292
|
/**
|
|
6286
6293
|
* Gets the current value of an observable, or undefined if the observable has
|
|
6287
6294
|
* not emitted a value yet.
|
|
@@ -8466,6 +8473,7 @@ class BasePeerConnection {
|
|
|
8466
8473
|
try {
|
|
8467
8474
|
this.onPeerConnectionStateChange?.({
|
|
8468
8475
|
peerType: this.peerType,
|
|
8476
|
+
iceConnectionState: this.pc.iceConnectionState,
|
|
8469
8477
|
...event,
|
|
8470
8478
|
});
|
|
8471
8479
|
}
|
|
@@ -8920,6 +8928,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8920
8928
|
clonedTracks = new Set();
|
|
8921
8929
|
publishOptions;
|
|
8922
8930
|
selfSubEnabled;
|
|
8931
|
+
transceiverLockKey = `pub.tx.${this.lock}`;
|
|
8923
8932
|
/**
|
|
8924
8933
|
* Constructs a new `Publisher` instance.
|
|
8925
8934
|
*/
|
|
@@ -8975,32 +8984,34 @@ class Publisher extends BasePeerConnection {
|
|
|
8975
8984
|
* @param options the publish options to use.
|
|
8976
8985
|
*/
|
|
8977
8986
|
publish = async (track, trackType, options = {}) => {
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
if (publishOption.trackType !== trackType)
|
|
8983
|
-
continue;
|
|
8984
|
-
// create a clone of the track as otherwise the same trackId will
|
|
8985
|
-
// appear in the SDP in multiple transceivers
|
|
8986
|
-
const trackToPublish = this.cloneTrack(track);
|
|
8987
|
-
const bundle = this.transceiverCache.get(publishOption);
|
|
8988
|
-
if (!bundle) {
|
|
8989
|
-
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
8987
|
+
return withoutConcurrency(this.transceiverLockKey, async () => {
|
|
8988
|
+
const publishOptions = this.publishOptions.filter((o) => o.trackType === trackType);
|
|
8989
|
+
if (!publishOptions.length) {
|
|
8990
|
+
throw new Error(`No publish options found for ${TrackType[trackType]}`);
|
|
8990
8991
|
}
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8992
|
+
for (const publishOption of publishOptions) {
|
|
8993
|
+
// create a clone of the track as otherwise the same trackId will
|
|
8994
|
+
// appear in the SDP in multiple transceivers
|
|
8995
|
+
const trackToPublish = this.cloneTrack(track);
|
|
8996
|
+
const bundle = this.transceiverCache.get(publishOption);
|
|
8997
|
+
if (!bundle) {
|
|
8998
|
+
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
8999
|
+
}
|
|
9000
|
+
else {
|
|
9001
|
+
const previousTrack = bundle.transceiver.sender.track;
|
|
9002
|
+
await this.updateTransceiver(bundle, trackToPublish, options);
|
|
9003
|
+
if (!isReactNative()) {
|
|
9004
|
+
this.stopTrack(previousTrack);
|
|
9005
|
+
}
|
|
8996
9006
|
}
|
|
8997
9007
|
}
|
|
8998
|
-
}
|
|
9008
|
+
});
|
|
8999
9009
|
};
|
|
9000
9010
|
/**
|
|
9001
9011
|
* Adds a new transceiver carrying the given track to the peer connection.
|
|
9002
9012
|
*/
|
|
9003
9013
|
addTransceiver = async (track, publishOption, options) => {
|
|
9014
|
+
const trackType = publishOption.trackType;
|
|
9004
9015
|
const encodings = isAudioTrackType(publishOption.trackType)
|
|
9005
9016
|
? computeAudioLayers(publishOption, options)
|
|
9006
9017
|
: computeVideoLayers(track, publishOption);
|
|
@@ -9011,15 +9022,17 @@ class Publisher extends BasePeerConnection {
|
|
|
9011
9022
|
direction: 'sendonly',
|
|
9012
9023
|
sendEncodings,
|
|
9013
9024
|
});
|
|
9025
|
+
// claim the cache entry in the same task that created the transceiver:
|
|
9026
|
+
// an await in between would let a concurrent caller add a second one
|
|
9027
|
+
// for the same publish option
|
|
9028
|
+
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
9029
|
+
this.trackIdToTrackType.set(track.id, trackType);
|
|
9030
|
+
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
9014
9031
|
const params = transceiver.sender.getParameters();
|
|
9015
9032
|
params.degradationPreference =
|
|
9016
9033
|
toRTCDegradationPreference(publishOption.degradationPreference) ??
|
|
9017
9034
|
'maintain-framerate';
|
|
9018
9035
|
await transceiver.sender.setParameters(params);
|
|
9019
|
-
const trackType = publishOption.trackType;
|
|
9020
|
-
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
9021
|
-
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
9022
|
-
this.trackIdToTrackType.set(track.id, trackType);
|
|
9023
9036
|
await this.negotiate();
|
|
9024
9037
|
};
|
|
9025
9038
|
/**
|
|
@@ -9076,33 +9089,35 @@ class Publisher extends BasePeerConnection {
|
|
|
9076
9089
|
* Synchronizes the current Publisher state with the provided publish options.
|
|
9077
9090
|
*/
|
|
9078
9091
|
syncPublishOptions = async () => {
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
const
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
i.
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
const
|
|
9098
|
-
|
|
9099
|
-
option.
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9092
|
+
return withoutConcurrency(this.transceiverLockKey, async () => {
|
|
9093
|
+
// enable publishing with new options -> [av1, vp9]
|
|
9094
|
+
for (const publishOption of this.publishOptions) {
|
|
9095
|
+
const { trackType } = publishOption;
|
|
9096
|
+
if (!this.isPublishing(trackType))
|
|
9097
|
+
continue;
|
|
9098
|
+
if (this.transceiverCache.has(publishOption))
|
|
9099
|
+
continue;
|
|
9100
|
+
const item = this.transceiverCache.find((i) => !!i.transceiver.sender.track &&
|
|
9101
|
+
i.publishOption.trackType === trackType);
|
|
9102
|
+
if (!item)
|
|
9103
|
+
continue;
|
|
9104
|
+
// take the track from the existing transceiver for the same track type,
|
|
9105
|
+
// clone it and publish it with the new publish options
|
|
9106
|
+
const track = this.cloneTrack(item.transceiver.sender.track);
|
|
9107
|
+
await this.addTransceiver(track, publishOption, item.options);
|
|
9108
|
+
}
|
|
9109
|
+
// stop publishing with options not required anymore -> [vp9]
|
|
9110
|
+
for (const item of this.transceiverCache.items()) {
|
|
9111
|
+
const { publishOption, transceiver } = item;
|
|
9112
|
+
const hasPublishOption = this.publishOptions.some((option) => option.id === publishOption.id &&
|
|
9113
|
+
option.trackType === publishOption.trackType);
|
|
9114
|
+
if (hasPublishOption)
|
|
9115
|
+
continue;
|
|
9116
|
+
// it is safe to stop the track here, it is a clone
|
|
9117
|
+
this.stopTrack(transceiver.sender.track);
|
|
9118
|
+
await this.updateTransceiver(item, null);
|
|
9119
|
+
}
|
|
9120
|
+
});
|
|
9106
9121
|
};
|
|
9107
9122
|
/**
|
|
9108
9123
|
* Returns true if the given track type is currently being published to the SFU.
|
|
@@ -9270,12 +9285,23 @@ class Publisher extends BasePeerConnection {
|
|
|
9270
9285
|
negotiate = async (options) => {
|
|
9271
9286
|
return withoutConcurrency(`publisher.negotiate.${this.lock}`, async () => {
|
|
9272
9287
|
const offer = await this.pc.createOffer(options);
|
|
9273
|
-
const tracks = this.getAnnouncedTracks(offer.sdp);
|
|
9274
|
-
if (!tracks.length)
|
|
9275
|
-
throw new Error(`Can't negotiate without any tracks`);
|
|
9276
9288
|
try {
|
|
9277
9289
|
this.isIceRestarting = options?.iceRestart ?? false;
|
|
9278
9290
|
await this.pc.setLocalDescription(offer);
|
|
9291
|
+
// the announced tracks have to be read after `setLocalDescription`.
|
|
9292
|
+
// `transceiver.mid` is null until the offer is applied, and reading the
|
|
9293
|
+
// tracks earlier makes `extractMid` guess the mid from the offer SDP.
|
|
9294
|
+
// That guess goes stale after a rolled-back negotiation, and a mid that
|
|
9295
|
+
// doesn't match the m-section carrying the track stops the SFU from
|
|
9296
|
+
// correlating the two: it then answers with every offered codec instead
|
|
9297
|
+
// of the announced one, and we publish the first codec of that list.
|
|
9298
|
+
// Per https://www.w3.org/TR/webrtc/#set-description, applying a local
|
|
9299
|
+
// offer associates the transceiver with its m-section and sets
|
|
9300
|
+
// `[[Mid]]` to `[[JsepMid]]`; a rollback disassociates a transceiver
|
|
9301
|
+
// that wasn't associated before and resets both slots to null.
|
|
9302
|
+
const tracks = this.getAnnouncedTracks(offer.sdp);
|
|
9303
|
+
if (!tracks.length)
|
|
9304
|
+
throw new Error(`Can't negotiate without any tracks`);
|
|
9279
9305
|
const { sdp: baseSdp = '' } = offer;
|
|
9280
9306
|
const { dangerouslyForceCodec, dangerouslySetStartBitrateFactor, fmtpLine, } = this.clientPublishOptions || {};
|
|
9281
9307
|
let sdp = dangerouslyForceCodec
|
|
@@ -10900,34 +10926,67 @@ class AudioBindingsWatchdog {
|
|
|
10900
10926
|
class BlockedAudioTracker {
|
|
10901
10927
|
logger = videoLoggerSystem.getLogger('BlockedAudioTracker');
|
|
10902
10928
|
tracer;
|
|
10903
|
-
blockedElementsSubject = new BehaviorSubject(
|
|
10929
|
+
blockedElementsSubject = new BehaviorSubject([]);
|
|
10904
10930
|
/**
|
|
10905
10931
|
* Whether the browser's autoplay policy is blocking audio playback.
|
|
10906
10932
|
* Will be `true` when at least one audio element is currently blocked.
|
|
10907
10933
|
* Use {@link resumeAudio} within a user gesture to unblock.
|
|
10908
10934
|
*/
|
|
10909
|
-
autoplayBlocked$ = this.blockedElementsSubject.pipe(map((elements) => elements.
|
|
10935
|
+
autoplayBlocked$ = this.blockedElementsSubject.pipe(map((elements) => elements.length > 0), distinctUntilChanged());
|
|
10936
|
+
/**
|
|
10937
|
+
* The list of participant `sessionId`s whose audio element is currently
|
|
10938
|
+
* blocked by the browser's autoplay policy. Only some participants may be
|
|
10939
|
+
* blocked (e.g. one joined while a gesture was active and a later one was
|
|
10940
|
+
* not), so use this to render a per-participant affordance rather than a
|
|
10941
|
+
* call-wide one. Call {@link resumeAudio} within a user gesture to unblock
|
|
10942
|
+
* all of them.
|
|
10943
|
+
*
|
|
10944
|
+
* Session ids are registered together with the audio element.
|
|
10945
|
+
*/
|
|
10946
|
+
blockedSessionIds$ = this.blockedElementsSubject.pipe(map((elements) => {
|
|
10947
|
+
const ids = [];
|
|
10948
|
+
elements.forEach(({ sessionId }) => {
|
|
10949
|
+
if (sessionId && !ids.includes(sessionId))
|
|
10950
|
+
ids.push(sessionId);
|
|
10951
|
+
});
|
|
10952
|
+
return ids;
|
|
10953
|
+
}), distinctUntilChanged(isShallowArrayEqual), shareReplay({ bufferSize: 1, refCount: true }));
|
|
10910
10954
|
constructor(tracer) {
|
|
10911
10955
|
this.tracer = tracer;
|
|
10912
10956
|
}
|
|
10913
10957
|
/**
|
|
10914
10958
|
* Registers an audio element as blocked by the browser's autoplay policy.
|
|
10915
10959
|
*/
|
|
10916
|
-
markBlocked = (audioElement, blocked) => {
|
|
10960
|
+
markBlocked = (audioElement, blocked, sessionId) => {
|
|
10917
10961
|
setCurrentValue(this.blockedElementsSubject, (elements) => {
|
|
10918
|
-
if (blocked)
|
|
10919
|
-
elements.
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10962
|
+
if (!blocked) {
|
|
10963
|
+
return elements.filter(({ element }) => element !== audioElement);
|
|
10964
|
+
}
|
|
10965
|
+
const existing = elements.find(({ element }) => element === audioElement);
|
|
10966
|
+
if (existing) {
|
|
10967
|
+
return elements.map((entry) => entry.element === audioElement
|
|
10968
|
+
? { element: audioElement, sessionId: sessionId ?? entry.sessionId }
|
|
10969
|
+
: entry);
|
|
10970
|
+
}
|
|
10971
|
+
return [...elements, { element: audioElement, sessionId }];
|
|
10923
10972
|
});
|
|
10924
10973
|
};
|
|
10974
|
+
/**
|
|
10975
|
+
* Clears all tracked elements. The tracker stays usable afterwards.
|
|
10976
|
+
*/
|
|
10977
|
+
reset = () => {
|
|
10978
|
+
if (this.blockedElementsSubject.getValue().length > 0) {
|
|
10979
|
+
this.blockedElementsSubject.next([]);
|
|
10980
|
+
}
|
|
10981
|
+
};
|
|
10925
10982
|
/**
|
|
10926
10983
|
* Returns whether the given audio element is currently flagged as blocked
|
|
10927
10984
|
* by the browser's autoplay policy.
|
|
10928
10985
|
*/
|
|
10929
10986
|
isBlocked = (audioElement) => {
|
|
10930
|
-
return this.blockedElementsSubject
|
|
10987
|
+
return this.blockedElementsSubject
|
|
10988
|
+
.getValue()
|
|
10989
|
+
.some(({ element }) => element === audioElement);
|
|
10931
10990
|
};
|
|
10932
10991
|
/**
|
|
10933
10992
|
* Plays all audio elements blocked by the browser's autoplay policy.
|
|
@@ -10936,17 +10995,18 @@ class BlockedAudioTracker {
|
|
|
10936
10995
|
resumeAudio = async () => {
|
|
10937
10996
|
this.tracer.trace('resumeAudio', null);
|
|
10938
10997
|
await setCurrentValueAsync(this.blockedElementsSubject, async (elements) => {
|
|
10939
|
-
|
|
10998
|
+
let next = elements;
|
|
10999
|
+
await Promise.all(elements.map(async ({ element }) => {
|
|
10940
11000
|
try {
|
|
10941
11001
|
if (element.srcObject)
|
|
10942
11002
|
await timeboxed([element.play()], 2000);
|
|
10943
|
-
|
|
11003
|
+
next = next.filter((entry) => entry.element !== element);
|
|
10944
11004
|
}
|
|
10945
11005
|
catch (err) {
|
|
10946
11006
|
this.logger.warn(`Can't resume audio for element`, element, err);
|
|
10947
11007
|
}
|
|
10948
11008
|
}));
|
|
10949
|
-
return
|
|
11009
|
+
return next;
|
|
10950
11010
|
});
|
|
10951
11011
|
};
|
|
10952
11012
|
}
|
|
@@ -11478,6 +11538,12 @@ class DynascaleManager {
|
|
|
11478
11538
|
let sourceNode = undefined;
|
|
11479
11539
|
let gainNode = undefined;
|
|
11480
11540
|
let audioWatchdog = undefined;
|
|
11541
|
+
const clearBlockedAudio = () => {
|
|
11542
|
+
if (!this.blockedAudioTracker.isBlocked(audioElement))
|
|
11543
|
+
return;
|
|
11544
|
+
this.blockedAudioTracker.markBlocked(audioElement, false);
|
|
11545
|
+
};
|
|
11546
|
+
audioElement.addEventListener('playing', clearBlockedAudio);
|
|
11481
11547
|
const isAudioTrack = trackType === 'audioTrack';
|
|
11482
11548
|
const trackKey = isAudioTrack ? 'audioStream' : 'screenShareAudioStream';
|
|
11483
11549
|
const updateMediaStreamSubscription = participant$
|
|
@@ -11491,7 +11557,7 @@ class DynascaleManager {
|
|
|
11491
11557
|
audioWatchdog?.dispose();
|
|
11492
11558
|
audioWatchdog = undefined;
|
|
11493
11559
|
if (!source) {
|
|
11494
|
-
|
|
11560
|
+
clearBlockedAudio();
|
|
11495
11561
|
return;
|
|
11496
11562
|
}
|
|
11497
11563
|
// Safari has a special quirk that prevents playing audio until the user
|
|
@@ -11519,7 +11585,7 @@ class DynascaleManager {
|
|
|
11519
11585
|
this.tracer.trace('audioPlaybackError', e.message);
|
|
11520
11586
|
if (e.name === 'NotAllowedError') {
|
|
11521
11587
|
this.tracer.trace('audioPlaybackBlocked', null);
|
|
11522
|
-
this.blockedAudioTracker.markBlocked(audioElement, true);
|
|
11588
|
+
this.blockedAudioTracker.markBlocked(audioElement, true, sessionId);
|
|
11523
11589
|
}
|
|
11524
11590
|
this.logger.warn(`Failed to play audio stream`, e);
|
|
11525
11591
|
});
|
|
@@ -11552,7 +11618,8 @@ class DynascaleManager {
|
|
|
11552
11618
|
});
|
|
11553
11619
|
audioElement.autoplay = true;
|
|
11554
11620
|
return () => {
|
|
11555
|
-
|
|
11621
|
+
audioElement.removeEventListener('playing', clearBlockedAudio);
|
|
11622
|
+
clearBlockedAudio();
|
|
11556
11623
|
sinkIdSubscription?.unsubscribe();
|
|
11557
11624
|
volumeSubscription.unsubscribe();
|
|
11558
11625
|
updateMediaStreamSubscription.unsubscribe();
|
|
@@ -14727,6 +14794,7 @@ class Call {
|
|
|
14727
14794
|
this.screenShare = new ScreenShareManager(this);
|
|
14728
14795
|
this.trackSubscriptionManager = new TrackSubscriptionManager(this.state, this.tracer);
|
|
14729
14796
|
this.blockedAudioTracker = new BlockedAudioTracker(this.tracer);
|
|
14797
|
+
this.leaveCallHooks.add(this.blockedAudioTracker.reset);
|
|
14730
14798
|
if (typeof document !== 'undefined') {
|
|
14731
14799
|
this.audioBindingsWatchdog = new AudioBindingsWatchdog(this.state, this.tracer);
|
|
14732
14800
|
this.viewportTracker = new ViewportTracker(this.state);
|
|
@@ -16350,6 +16418,12 @@ class Call {
|
|
|
16350
16418
|
setSortParticipantsBy = (criteria) => {
|
|
16351
16419
|
return this.state.setSortParticipantsBy(criteria);
|
|
16352
16420
|
};
|
|
16421
|
+
/**
|
|
16422
|
+
* Returns the comparator currently used to sort the participants.
|
|
16423
|
+
*/
|
|
16424
|
+
getSortParticipantsBy = () => {
|
|
16425
|
+
return this.state.getSortParticipantsBy();
|
|
16426
|
+
};
|
|
16353
16427
|
/**
|
|
16354
16428
|
* Sends a reaction to the other call participants.
|
|
16355
16429
|
*
|
|
@@ -18417,11 +18491,11 @@ class StreamClient {
|
|
|
18417
18491
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
18418
18492
|
};
|
|
18419
18493
|
getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
|
|
18420
|
-
"1.
|
|
18494
|
+
"1.56.0";
|
|
18421
18495
|
getUserAgent = () => {
|
|
18422
18496
|
if (!this.cachedUserAgent) {
|
|
18423
18497
|
const { clientAppIdentifier = {} } = this.options;
|
|
18424
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
18498
|
+
const { sdkName = 'js', sdkVersion = "1.56.0", ...extras } = clientAppIdentifier;
|
|
18425
18499
|
this.cachedUserAgent = [
|
|
18426
18500
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
18427
18501
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -18758,8 +18832,8 @@ class ClientEventReporter {
|
|
|
18758
18832
|
applyErrorIfAbsent(this.wsPairs.get(cid), stageError);
|
|
18759
18833
|
this.failCoordinator(cid);
|
|
18760
18834
|
this.failWs(cid);
|
|
18761
|
-
this.emitPeerConnectionFailure(cid, 'publish', code, reason
|
|
18762
|
-
this.emitPeerConnectionFailure(cid, 'subscribe', code, reason
|
|
18835
|
+
this.emitPeerConnectionFailure(cid, 'publish', code, reason);
|
|
18836
|
+
this.emitPeerConnectionFailure(cid, 'subscribe', code, reason);
|
|
18763
18837
|
}
|
|
18764
18838
|
catch (err) {
|
|
18765
18839
|
this.logger.warn('Failed to report abort', err);
|
|
@@ -18771,7 +18845,7 @@ class ClientEventReporter {
|
|
|
18771
18845
|
if (this.wsPairs.get(cid))
|
|
18772
18846
|
this.failWs(cid);
|
|
18773
18847
|
for (const role of ['publish', 'subscribe']) {
|
|
18774
|
-
this.emitPeerConnectionFailure(cid, role, 'CLIENT_ABORTED', 'superseded by a new join attempt'
|
|
18848
|
+
this.emitPeerConnectionFailure(cid, role, 'CLIENT_ABORTED', 'superseded by a new join attempt');
|
|
18775
18849
|
}
|
|
18776
18850
|
};
|
|
18777
18851
|
emitJoinInitiated = (cid) => {
|
|
@@ -18933,21 +19007,24 @@ class ClientEventReporter {
|
|
|
18933
19007
|
};
|
|
18934
19008
|
onPeerConnectionStateChange = (cid, event) => {
|
|
18935
19009
|
const role = event.peerType === PeerType.SUBSCRIBER ? 'subscribe' : 'publish';
|
|
18936
|
-
|
|
18937
|
-
|
|
19010
|
+
const pair = this.peerConnectionPairs.get(pcKey(cid, role));
|
|
19011
|
+
if (pair)
|
|
19012
|
+
pair.lastIceState = event.iceConnectionState;
|
|
19013
|
+
if (event.stateType === 'ice') {
|
|
19014
|
+
if (event.state === 'failed') {
|
|
19015
|
+
this.emitPeerConnectionFailure(cid, role, 'ICE_CONNECTIVITY_FAILED', 'ICE connectivity checks failed');
|
|
19016
|
+
}
|
|
18938
19017
|
return;
|
|
18939
19018
|
}
|
|
18940
|
-
if (event.
|
|
18941
|
-
this.emitPeerConnectionFailure(cid, role, 'DTLS_CONNECTIVITY_FAILED', 'DTLS connectivity checks failed'
|
|
19019
|
+
if (event.state === 'failed') {
|
|
19020
|
+
this.emitPeerConnectionFailure(cid, role, 'DTLS_CONNECTIVITY_FAILED', 'DTLS connectivity checks failed');
|
|
18942
19021
|
return;
|
|
18943
19022
|
}
|
|
18944
|
-
if (event.stateType !== 'peerConnection')
|
|
18945
|
-
return;
|
|
18946
19023
|
switch (event.state) {
|
|
18947
19024
|
case 'connecting':
|
|
18948
|
-
if (
|
|
19025
|
+
if (pair)
|
|
18949
19026
|
return;
|
|
18950
|
-
this.openPeerConnectionPair(cid, role);
|
|
19027
|
+
this.openPeerConnectionPair(cid, role, event.iceConnectionState);
|
|
18951
19028
|
break;
|
|
18952
19029
|
case 'connected':
|
|
18953
19030
|
this.emitPeerConnectionSuccess(cid, role);
|
|
@@ -18955,7 +19032,7 @@ class ClientEventReporter {
|
|
|
18955
19032
|
break;
|
|
18956
19033
|
}
|
|
18957
19034
|
};
|
|
18958
|
-
openPeerConnectionPair = (cid, role) => {
|
|
19035
|
+
openPeerConnectionPair = (cid, role, iceConnectionState) => {
|
|
18959
19036
|
const key = pcKey(cid, role);
|
|
18960
19037
|
const pair = {
|
|
18961
19038
|
sid: generateUUIDv4(),
|
|
@@ -18965,6 +19042,7 @@ class ClientEventReporter {
|
|
|
18965
19042
|
sfuId: this.getSfuId(cid),
|
|
18966
19043
|
userSessionId: this.getUserSessionId(cid),
|
|
18967
19044
|
wasPreviouslyConnected: this.pcEverConnected.get(key) === true,
|
|
19045
|
+
lastIceState: iceConnectionState,
|
|
18968
19046
|
};
|
|
18969
19047
|
this.peerConnectionPairs.set(key, pair);
|
|
18970
19048
|
this.sendForCall(cid, {
|
|
@@ -19000,7 +19078,7 @@ class ClientEventReporter {
|
|
|
19000
19078
|
});
|
|
19001
19079
|
this.peerConnectionPairs.delete(key);
|
|
19002
19080
|
};
|
|
19003
|
-
emitPeerConnectionFailure = (cid, role, code, reason
|
|
19081
|
+
emitPeerConnectionFailure = (cid, role, code, reason) => {
|
|
19004
19082
|
const key = pcKey(cid, role);
|
|
19005
19083
|
const pair = this.peerConnectionPairs.get(key);
|
|
19006
19084
|
if (!pair)
|
|
@@ -19018,7 +19096,7 @@ class ClientEventReporter {
|
|
|
19018
19096
|
outcome: 'failure',
|
|
19019
19097
|
retry_count_attempt: 0,
|
|
19020
19098
|
elapsed_time: Date.now() - pair.startedAt,
|
|
19021
|
-
ice_state:
|
|
19099
|
+
ice_state: toReportedIceState(pair.lastIceState),
|
|
19022
19100
|
retry_failure_reason: reason,
|
|
19023
19101
|
retry_failure_code: code,
|
|
19024
19102
|
});
|
|
@@ -19084,6 +19162,17 @@ class ClientEventReporter {
|
|
|
19084
19162
|
return false;
|
|
19085
19163
|
};
|
|
19086
19164
|
}
|
|
19165
|
+
const toReportedIceState = (state) => {
|
|
19166
|
+
switch (state) {
|
|
19167
|
+
case 'connected':
|
|
19168
|
+
case 'completed':
|
|
19169
|
+
return 'CONNECTED';
|
|
19170
|
+
case 'failed':
|
|
19171
|
+
return 'FAILED';
|
|
19172
|
+
default:
|
|
19173
|
+
return 'NOT_CONNECTED';
|
|
19174
|
+
}
|
|
19175
|
+
};
|
|
19087
19176
|
const readPermissionStatus = (permission) => {
|
|
19088
19177
|
const state = getCurrentValue(permission.asStateObservable());
|
|
19089
19178
|
switch (state) {
|