@stream-io/video-client 1.53.2 → 1.54.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/index.browser.es.js +111 -12
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +111 -12
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +111 -12
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +13 -1
- package/dist/src/coordinator/connection/types.d.ts +6 -0
- package/dist/src/gen/google/protobuf/struct.d.ts +3 -1
- package/dist/src/gen/google/protobuf/timestamp.d.ts +3 -1
- package/dist/src/gen/video/sfu/event/events.d.ts +22 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +4 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +23 -2
- package/dist/src/reporting/ClientEventReporter.d.ts +2 -0
- package/dist/src/rtc/Publisher.d.ts +4 -1
- package/dist/src/rtc/Subscriber.d.ts +7 -0
- package/package.json +1 -1
- package/src/Call.ts +86 -6
- package/src/StreamVideoClient.ts +1 -0
- package/src/coordinator/connection/types.ts +7 -0
- package/src/gen/google/protobuf/struct.ts +7 -12
- package/src/gen/google/protobuf/timestamp.ts +6 -7
- package/src/gen/video/sfu/event/events.ts +23 -25
- package/src/gen/video/sfu/models/models.ts +11 -1
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +25 -29
- package/src/gen/video/sfu/signal_rpc/signal.ts +1 -0
- package/src/helpers/client-details.ts +1 -1
- package/src/reporting/ClientEventReporter.ts +4 -0
- package/src/reporting/__tests__/ClientEventReporter.test.ts +33 -0
- package/src/rtc/Publisher.ts +4 -0
- package/src/rtc/Subscriber.ts +28 -1
- package/src/rtc/__tests__/Call.reconnect.test.ts +149 -2
package/dist/index.cjs.js
CHANGED
|
@@ -527,6 +527,7 @@ class ErrorFromResponse extends Error {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
+
/* eslint-disable */
|
|
530
531
|
// @generated by protobuf-ts 2.10.0 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
|
|
531
532
|
// @generated from protobuf file "google/protobuf/struct.proto" (package "google.protobuf", syntax proto3)
|
|
532
533
|
// tslint:disable
|
|
@@ -792,6 +793,7 @@ class ListValue$Type extends runtime.MessageType {
|
|
|
792
793
|
*/
|
|
793
794
|
const ListValue = new ListValue$Type();
|
|
794
795
|
|
|
796
|
+
/* eslint-disable */
|
|
795
797
|
// @generated by protobuf-ts 2.10.0 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
|
|
796
798
|
// @generated from protobuf file "google/protobuf/timestamp.proto" (package "google.protobuf", syntax proto3)
|
|
797
799
|
// tslint:disable
|
|
@@ -1858,6 +1860,12 @@ class TrackInfo$Type extends runtime.MessageType {
|
|
|
1858
1860
|
kind: 'scalar',
|
|
1859
1861
|
T: 5 /*ScalarType.INT32*/,
|
|
1860
1862
|
},
|
|
1863
|
+
{
|
|
1864
|
+
no: 13,
|
|
1865
|
+
name: 'self_sub_audio_video',
|
|
1866
|
+
kind: 'scalar',
|
|
1867
|
+
T: 8 /*ScalarType.BOOL*/,
|
|
1868
|
+
},
|
|
1861
1869
|
]);
|
|
1862
1870
|
}
|
|
1863
1871
|
}
|
|
@@ -6660,7 +6668,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6660
6668
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6661
6669
|
};
|
|
6662
6670
|
|
|
6663
|
-
const version = "1.
|
|
6671
|
+
const version = "1.54.1-beta.0";
|
|
6664
6672
|
const [major, minor, patch] = version.split('.');
|
|
6665
6673
|
let sdkInfo = {
|
|
6666
6674
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6804,7 +6812,7 @@ const getClientDetails = async () => {
|
|
|
6804
6812
|
.join(' '),
|
|
6805
6813
|
version: '',
|
|
6806
6814
|
},
|
|
6807
|
-
webrtcVersion:
|
|
6815
|
+
webrtcVersion: webRtcInfo?.version || '',
|
|
6808
6816
|
};
|
|
6809
6817
|
};
|
|
6810
6818
|
|
|
@@ -8448,7 +8456,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8448
8456
|
/**
|
|
8449
8457
|
* Constructs a new `Publisher` instance.
|
|
8450
8458
|
*/
|
|
8451
|
-
constructor(baseOptions, publishOptions) {
|
|
8459
|
+
constructor(baseOptions, publishOptions, opts = {}) {
|
|
8452
8460
|
super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
|
|
8453
8461
|
this.transceiverCache = new TransceiverCache();
|
|
8454
8462
|
this.clonedTracks = new Set();
|
|
@@ -8869,6 +8877,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8869
8877
|
muted: !isTrackLive,
|
|
8870
8878
|
codec: publishOption.codec,
|
|
8871
8879
|
publishOptionId: publishOption.id,
|
|
8880
|
+
selfSubAudioVideo: this.selfSubEnabled,
|
|
8872
8881
|
};
|
|
8873
8882
|
};
|
|
8874
8883
|
this.cloneTrack = (track) => {
|
|
@@ -8949,6 +8958,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8949
8958
|
});
|
|
8950
8959
|
};
|
|
8951
8960
|
this.publishOptions = publishOptions;
|
|
8961
|
+
this.selfSubEnabled = opts.selfSubEnabled ?? false;
|
|
8952
8962
|
this.on('iceRestart', (iceRestart) => {
|
|
8953
8963
|
if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
|
|
8954
8964
|
return;
|
|
@@ -9030,6 +9040,13 @@ class Subscriber extends BasePeerConnection {
|
|
|
9030
9040
|
*/
|
|
9031
9041
|
constructor(opts) {
|
|
9032
9042
|
super(PeerType.SUBSCRIBER, opts);
|
|
9043
|
+
/**
|
|
9044
|
+
* Remote streams received from the SFU. For a self-sub case
|
|
9045
|
+
* we need to be able to distinguish between the local capture stream.
|
|
9046
|
+
* The map will never contain local streams so we can safely use it to
|
|
9047
|
+
* check if the stream is remote and dispose it when needed.
|
|
9048
|
+
*/
|
|
9049
|
+
this.trackedStreams = new WeakSet();
|
|
9033
9050
|
/**
|
|
9034
9051
|
* Restarts the ICE connection and renegotiates with the SFU.
|
|
9035
9052
|
*/
|
|
@@ -9064,6 +9081,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
9064
9081
|
// example: `e3f6aaf8-b03d-4911-be36-83f47d37a76a:TRACK_TYPE_VIDEO`
|
|
9065
9082
|
const [trackId, rawTrackType] = primaryStream.id.split(':');
|
|
9066
9083
|
const participantToUpdate = this.state.participants.find((p) => p.trackLookupPrefix === trackId);
|
|
9084
|
+
const isSelfSub = !!participantToUpdate?.isLocalParticipant;
|
|
9067
9085
|
this.logger.debug(`[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`, track.id, track);
|
|
9068
9086
|
const trackType = toTrackType(rawTrackType);
|
|
9069
9087
|
if (!trackType) {
|
|
@@ -9088,6 +9106,9 @@ class Subscriber extends BasePeerConnection {
|
|
|
9088
9106
|
this.setRemoteTrackInterrupted(trackId, trackType, true);
|
|
9089
9107
|
}
|
|
9090
9108
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
9109
|
+
if (isSelfSub) {
|
|
9110
|
+
this.trackedStreams.add(primaryStream);
|
|
9111
|
+
}
|
|
9091
9112
|
if (!participantToUpdate) {
|
|
9092
9113
|
this.logger.warn(`[onTrack]: Received track for unknown participant: ${trackId}`, e);
|
|
9093
9114
|
this.state.registerOrphanedTrack({
|
|
@@ -9103,6 +9124,12 @@ class Subscriber extends BasePeerConnection {
|
|
|
9103
9124
|
this.logger.error(`Unknown track type: ${rawTrackType}`);
|
|
9104
9125
|
return;
|
|
9105
9126
|
}
|
|
9127
|
+
// Self-sub loopback audio routes to the speaker by default, which
|
|
9128
|
+
// would echo the local user's voice. Default-mute here; consumers
|
|
9129
|
+
// (the loopback recording hook) re-enable explicitly when needed.
|
|
9130
|
+
if (isSelfSub && e.track.kind === 'audio') {
|
|
9131
|
+
e.track.enabled = false;
|
|
9132
|
+
}
|
|
9106
9133
|
// get the previous stream to dispose it later
|
|
9107
9134
|
// usually this happens during migration, when the stream is replaced
|
|
9108
9135
|
// with a new one but the old one is still in the state
|
|
@@ -9111,8 +9138,12 @@ class Subscriber extends BasePeerConnection {
|
|
|
9111
9138
|
this.state.updateParticipant(participantToUpdate.sessionId, {
|
|
9112
9139
|
[streamKindProp]: primaryStream,
|
|
9113
9140
|
});
|
|
9114
|
-
// now, dispose the previous stream if it exists
|
|
9115
9141
|
if (previousStream) {
|
|
9142
|
+
if (isSelfSub && !this.trackedStreams.has(previousStream)) {
|
|
9143
|
+
// this is the local capture stream, we don't want to dispose it
|
|
9144
|
+
this.logger.debug(`[onTrack]: Skipping cleanup of previous ${e.track.kind} stream for userId: ${participantToUpdate.userId} because it is not tracked`);
|
|
9145
|
+
return;
|
|
9146
|
+
}
|
|
9116
9147
|
this.logger.info(`[onTrack]: Cleaning up previous remote ${track.kind} tracks for userId: ${participantToUpdate.userId}`);
|
|
9117
9148
|
previousStream.getTracks().forEach((t) => {
|
|
9118
9149
|
t.stop();
|
|
@@ -13935,6 +13966,7 @@ class Call {
|
|
|
13935
13966
|
// maintain the order of publishing tracks to restore them after a reconnection
|
|
13936
13967
|
// it shouldn't contain duplicates
|
|
13937
13968
|
this.trackPublishOrder = [];
|
|
13969
|
+
this.selfSubEnabled = false;
|
|
13938
13970
|
this.hasJoinedOnce = false;
|
|
13939
13971
|
this.deviceSettingsAppliedOnce = false;
|
|
13940
13972
|
this.initialized = false;
|
|
@@ -14284,6 +14316,30 @@ class Call {
|
|
|
14284
14316
|
await Promise.all(stopOnLeavePromises);
|
|
14285
14317
|
});
|
|
14286
14318
|
};
|
|
14319
|
+
/**
|
|
14320
|
+
* The largest video publish dimension across the current publish options.
|
|
14321
|
+
*
|
|
14322
|
+
* @internal
|
|
14323
|
+
*/
|
|
14324
|
+
this.getMaxVideoPublishDimension = () => {
|
|
14325
|
+
if (!this.currentPublishOptions)
|
|
14326
|
+
return undefined;
|
|
14327
|
+
let maxDimension;
|
|
14328
|
+
let maxArea = 0;
|
|
14329
|
+
for (const opt of this.currentPublishOptions) {
|
|
14330
|
+
if (opt.trackType !== TrackType.VIDEO)
|
|
14331
|
+
continue;
|
|
14332
|
+
const dim = opt.videoDimension;
|
|
14333
|
+
if (!dim || !dim.width || !dim.height)
|
|
14334
|
+
continue;
|
|
14335
|
+
const area = dim.width * dim.height;
|
|
14336
|
+
if (area > maxArea) {
|
|
14337
|
+
maxDimension = dim;
|
|
14338
|
+
maxArea = area;
|
|
14339
|
+
}
|
|
14340
|
+
}
|
|
14341
|
+
return maxDimension;
|
|
14342
|
+
};
|
|
14287
14343
|
/**
|
|
14288
14344
|
* Update from the call response from the "call.ring" event
|
|
14289
14345
|
* @internal
|
|
@@ -14430,7 +14486,7 @@ class Call {
|
|
|
14430
14486
|
*
|
|
14431
14487
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
14432
14488
|
*/
|
|
14433
|
-
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
|
|
14489
|
+
this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, selfSubEnabled = false, ...data } = {}) => {
|
|
14434
14490
|
const callingState = this.state.callingState;
|
|
14435
14491
|
if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
|
|
14436
14492
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
@@ -14438,6 +14494,9 @@ class Call {
|
|
|
14438
14494
|
if (data?.ring) {
|
|
14439
14495
|
this.ringingSubject.next(true);
|
|
14440
14496
|
}
|
|
14497
|
+
// we need this to be set before the callingx.joinCall() is
|
|
14498
|
+
// called to avoid registering the test call in the CallKit/Telecom
|
|
14499
|
+
this.selfSubEnabled = selfSubEnabled;
|
|
14441
14500
|
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
14442
14501
|
if (callingX) {
|
|
14443
14502
|
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
@@ -14574,7 +14633,7 @@ class Call {
|
|
|
14574
14633
|
]);
|
|
14575
14634
|
const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
|
|
14576
14635
|
const reconnectDetails = isReconnecting
|
|
14577
|
-
? this.getReconnectDetails(
|
|
14636
|
+
? this.getReconnectDetails(previousSfuClient?.edgeName, previousSessionId)
|
|
14578
14637
|
: undefined;
|
|
14579
14638
|
const preferredPublishOptions = !isReconnecting
|
|
14580
14639
|
? this.getPreferredPublishOptions()
|
|
@@ -14827,7 +14886,9 @@ class Call {
|
|
|
14827
14886
|
if (closePreviousInstances && this.publisher) {
|
|
14828
14887
|
await this.publisher.dispose();
|
|
14829
14888
|
}
|
|
14830
|
-
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions
|
|
14889
|
+
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions, {
|
|
14890
|
+
selfSubEnabled: this.selfSubEnabled,
|
|
14891
|
+
});
|
|
14831
14892
|
}
|
|
14832
14893
|
this.statsReporter?.stop();
|
|
14833
14894
|
if (this.statsReportingIntervalInMs > 0) {
|
|
@@ -14943,6 +15004,7 @@ class Call {
|
|
|
14943
15004
|
const reconnectStartTime = Date.now();
|
|
14944
15005
|
this.reconnectStrategy = strategy;
|
|
14945
15006
|
this.reconnectReason = reason;
|
|
15007
|
+
const sfuRejoinFailures = new Map();
|
|
14946
15008
|
const markAsReconnectingFailed = async () => {
|
|
14947
15009
|
try {
|
|
14948
15010
|
// attempt to fetch the call data from the server, as the call
|
|
@@ -15001,7 +15063,8 @@ class Call {
|
|
|
15001
15063
|
if (this.reconnectStrategy !== WebsocketReconnectStrategy.FAST) {
|
|
15002
15064
|
this.reconnectAttempts++;
|
|
15003
15065
|
}
|
|
15004
|
-
const
|
|
15066
|
+
const attemptedStrategy = this.reconnectStrategy;
|
|
15067
|
+
const currentStrategy = WebsocketReconnectStrategy[attemptedStrategy];
|
|
15005
15068
|
try {
|
|
15006
15069
|
// wait until the network is available
|
|
15007
15070
|
await this.networkAvailableTask?.promise;
|
|
@@ -15014,9 +15077,24 @@ class Call {
|
|
|
15014
15077
|
case WebsocketReconnectStrategy.FAST:
|
|
15015
15078
|
await this.reconnectFast();
|
|
15016
15079
|
break;
|
|
15017
|
-
case WebsocketReconnectStrategy.REJOIN:
|
|
15018
|
-
|
|
15080
|
+
case WebsocketReconnectStrategy.REJOIN: {
|
|
15081
|
+
const confirmedBadSfus = Array.from(sfuRejoinFailures)
|
|
15082
|
+
.filter(([, failures]) => failures >= 2)
|
|
15083
|
+
.map(([sfu]) => sfu);
|
|
15084
|
+
if (this.joinCallData && confirmedBadSfus.length) {
|
|
15085
|
+
this.joinCallData.migrating_from =
|
|
15086
|
+
confirmedBadSfus[confirmedBadSfus.length - 1];
|
|
15087
|
+
this.joinCallData.migrating_from_list = confirmedBadSfus;
|
|
15088
|
+
}
|
|
15089
|
+
try {
|
|
15090
|
+
await this.reconnectRejoin();
|
|
15091
|
+
}
|
|
15092
|
+
finally {
|
|
15093
|
+
delete this.joinCallData?.migrating_from;
|
|
15094
|
+
delete this.joinCallData?.migrating_from_list;
|
|
15095
|
+
}
|
|
15019
15096
|
break;
|
|
15097
|
+
}
|
|
15020
15098
|
case WebsocketReconnectStrategy.MIGRATE:
|
|
15021
15099
|
await this.reconnectMigrate();
|
|
15022
15100
|
break;
|
|
@@ -15029,6 +15107,15 @@ class Call {
|
|
|
15029
15107
|
break; // do-while loop, reconnection worked, exit the loop
|
|
15030
15108
|
}
|
|
15031
15109
|
catch (error) {
|
|
15110
|
+
if (attemptedStrategy === WebsocketReconnectStrategy.REJOIN) {
|
|
15111
|
+
const failedSfu = this.credentials?.server.edge_name;
|
|
15112
|
+
if (failedSfu) {
|
|
15113
|
+
const switchSfu = error instanceof SfuJoinError &&
|
|
15114
|
+
SfuJoinError.isJoinErrorCode(error.errorEvent);
|
|
15115
|
+
const failures = (sfuRejoinFailures.get(failedSfu) ?? 0) + 1;
|
|
15116
|
+
sfuRejoinFailures.set(failedSfu, switchSfu ? Math.max(failures, 2) : failures);
|
|
15117
|
+
}
|
|
15118
|
+
}
|
|
15032
15119
|
if (this.state.callingState === exports.CallingState.OFFLINE) {
|
|
15033
15120
|
this.logger.debug(`[Reconnect] Can't reconnect while offline, stopping reconnection attempts`);
|
|
15034
15121
|
break;
|
|
@@ -15224,6 +15311,8 @@ class Call {
|
|
|
15224
15311
|
this.state.setCallingState(exports.CallingState.OFFLINE);
|
|
15225
15312
|
}
|
|
15226
15313
|
else {
|
|
15314
|
+
if (!this.networkAvailableTask)
|
|
15315
|
+
return;
|
|
15227
15316
|
this.logger.debug('[Reconnect] Going online');
|
|
15228
15317
|
this.sfuClient?.close(StreamSfuClient.DISPOSE_OLD_SOCKET, 'Closing WS to reconnect after going online');
|
|
15229
15318
|
// we went online, release the previous waiters and reset the state
|
|
@@ -16295,6 +16384,12 @@ class Call {
|
|
|
16295
16384
|
get currentUserId() {
|
|
16296
16385
|
return this.clientStore.connectedUser?.id;
|
|
16297
16386
|
}
|
|
16387
|
+
/**
|
|
16388
|
+
* A flag indicating whether self-subscription is enabled for the call.
|
|
16389
|
+
*/
|
|
16390
|
+
get isSelfSubEnabled() {
|
|
16391
|
+
return this.selfSubEnabled;
|
|
16392
|
+
}
|
|
16298
16393
|
/**
|
|
16299
16394
|
* A flag indicating whether the call was created by the current user.
|
|
16300
16395
|
*/
|
|
@@ -17482,11 +17577,11 @@ class StreamClient {
|
|
|
17482
17577
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
17483
17578
|
};
|
|
17484
17579
|
this.getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
|
|
17485
|
-
"1.
|
|
17580
|
+
"1.54.1-beta.0";
|
|
17486
17581
|
this.getUserAgent = () => {
|
|
17487
17582
|
if (!this.cachedUserAgent) {
|
|
17488
17583
|
const { clientAppIdentifier = {} } = this.options;
|
|
17489
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
17584
|
+
const { sdkName = 'js', sdkVersion = "1.54.1-beta.0", ...extras } = clientAppIdentifier;
|
|
17490
17585
|
this.cachedUserAgent = [
|
|
17491
17586
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
17492
17587
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -18164,6 +18259,8 @@ class ClientEventReporter {
|
|
|
18164
18259
|
};
|
|
18165
18260
|
};
|
|
18166
18261
|
this.send = (body) => {
|
|
18262
|
+
if (!this.enabled)
|
|
18263
|
+
return;
|
|
18167
18264
|
void this.sendWithRetry(body);
|
|
18168
18265
|
};
|
|
18169
18266
|
this.sendWithRetry = async (body) => {
|
|
@@ -18189,6 +18286,7 @@ class ClientEventReporter {
|
|
|
18189
18286
|
return false;
|
|
18190
18287
|
};
|
|
18191
18288
|
this.streamClient = options.streamClient;
|
|
18289
|
+
this.enabled = options.enabled ?? true;
|
|
18192
18290
|
}
|
|
18193
18291
|
}
|
|
18194
18292
|
const readPermissionStatus = (permission) => {
|
|
@@ -18669,6 +18767,7 @@ class StreamVideoClient {
|
|
|
18669
18767
|
this.streamClient = createCoordinatorClient(apiKey, clientOptions);
|
|
18670
18768
|
this.clientEventReporter = new ClientEventReporter({
|
|
18671
18769
|
streamClient: this.streamClient,
|
|
18770
|
+
enabled: clientOptions?.clientEventsReportingEnabled ?? true,
|
|
18672
18771
|
});
|
|
18673
18772
|
this.writeableStateStore = new StreamVideoWriteableStateStore();
|
|
18674
18773
|
this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(this.writeableStateStore);
|