@stream-io/video-client 1.54.0 → 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.
@@ -507,6 +507,7 @@ class ErrorFromResponse extends Error {
507
507
  }
508
508
  }
509
509
 
510
+ /* eslint-disable */
510
511
  // @generated by protobuf-ts 2.10.0 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
511
512
  // @generated from protobuf file "google/protobuf/struct.proto" (package "google.protobuf", syntax proto3)
512
513
  // tslint:disable
@@ -772,6 +773,7 @@ class ListValue$Type extends MessageType {
772
773
  */
773
774
  const ListValue = new ListValue$Type();
774
775
 
776
+ /* eslint-disable */
775
777
  // @generated by protobuf-ts 2.10.0 with parameter long_type_string,client_generic,server_none,eslint_disable,optimize_code_size
776
778
  // @generated from protobuf file "google/protobuf/timestamp.proto" (package "google.protobuf", syntax proto3)
777
779
  // tslint:disable
@@ -1838,6 +1840,12 @@ class TrackInfo$Type extends MessageType {
1838
1840
  kind: 'scalar',
1839
1841
  T: 5 /*ScalarType.INT32*/,
1840
1842
  },
1843
+ {
1844
+ no: 13,
1845
+ name: 'self_sub_audio_video',
1846
+ kind: 'scalar',
1847
+ T: 8 /*ScalarType.BOOL*/,
1848
+ },
1841
1849
  ]);
1842
1850
  }
1843
1851
  }
@@ -6640,7 +6648,7 @@ const getSdkVersion = (sdk) => {
6640
6648
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6641
6649
  };
6642
6650
 
6643
- const version = "1.54.0";
6651
+ const version = "1.54.1-beta.0";
6644
6652
  const [major, minor, patch] = version.split('.');
6645
6653
  let sdkInfo = {
6646
6654
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -6784,7 +6792,7 @@ const getClientDetails = async () => {
6784
6792
  .join(' '),
6785
6793
  version: '',
6786
6794
  },
6787
- webrtcVersion: browserVersion,
6795
+ webrtcVersion: webRtcInfo?.version || '',
6788
6796
  };
6789
6797
  };
6790
6798
 
@@ -8428,7 +8436,7 @@ class Publisher extends BasePeerConnection {
8428
8436
  /**
8429
8437
  * Constructs a new `Publisher` instance.
8430
8438
  */
8431
- constructor(baseOptions, publishOptions) {
8439
+ constructor(baseOptions, publishOptions, opts = {}) {
8432
8440
  super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
8433
8441
  this.transceiverCache = new TransceiverCache();
8434
8442
  this.clonedTracks = new Set();
@@ -8849,6 +8857,7 @@ class Publisher extends BasePeerConnection {
8849
8857
  muted: !isTrackLive,
8850
8858
  codec: publishOption.codec,
8851
8859
  publishOptionId: publishOption.id,
8860
+ selfSubAudioVideo: this.selfSubEnabled,
8852
8861
  };
8853
8862
  };
8854
8863
  this.cloneTrack = (track) => {
@@ -8929,6 +8938,7 @@ class Publisher extends BasePeerConnection {
8929
8938
  });
8930
8939
  };
8931
8940
  this.publishOptions = publishOptions;
8941
+ this.selfSubEnabled = opts.selfSubEnabled ?? false;
8932
8942
  this.on('iceRestart', (iceRestart) => {
8933
8943
  if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
8934
8944
  return;
@@ -9010,6 +9020,13 @@ class Subscriber extends BasePeerConnection {
9010
9020
  */
9011
9021
  constructor(opts) {
9012
9022
  super(PeerType.SUBSCRIBER, opts);
9023
+ /**
9024
+ * Remote streams received from the SFU. For a self-sub case
9025
+ * we need to be able to distinguish between the local capture stream.
9026
+ * The map will never contain local streams so we can safely use it to
9027
+ * check if the stream is remote and dispose it when needed.
9028
+ */
9029
+ this.trackedStreams = new WeakSet();
9013
9030
  /**
9014
9031
  * Restarts the ICE connection and renegotiates with the SFU.
9015
9032
  */
@@ -9044,6 +9061,7 @@ class Subscriber extends BasePeerConnection {
9044
9061
  // example: `e3f6aaf8-b03d-4911-be36-83f47d37a76a:TRACK_TYPE_VIDEO`
9045
9062
  const [trackId, rawTrackType] = primaryStream.id.split(':');
9046
9063
  const participantToUpdate = this.state.participants.find((p) => p.trackLookupPrefix === trackId);
9064
+ const isSelfSub = !!participantToUpdate?.isLocalParticipant;
9047
9065
  this.logger.debug(`[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`, track.id, track);
9048
9066
  const trackType = toTrackType(rawTrackType);
9049
9067
  if (!trackType) {
@@ -9068,6 +9086,9 @@ class Subscriber extends BasePeerConnection {
9068
9086
  this.setRemoteTrackInterrupted(trackId, trackType, true);
9069
9087
  }
9070
9088
  this.trackIdToTrackType.set(track.id, trackType);
9089
+ if (isSelfSub) {
9090
+ this.trackedStreams.add(primaryStream);
9091
+ }
9071
9092
  if (!participantToUpdate) {
9072
9093
  this.logger.warn(`[onTrack]: Received track for unknown participant: ${trackId}`, e);
9073
9094
  this.state.registerOrphanedTrack({
@@ -9083,6 +9104,12 @@ class Subscriber extends BasePeerConnection {
9083
9104
  this.logger.error(`Unknown track type: ${rawTrackType}`);
9084
9105
  return;
9085
9106
  }
9107
+ // Self-sub loopback audio routes to the speaker by default, which
9108
+ // would echo the local user's voice. Default-mute here; consumers
9109
+ // (the loopback recording hook) re-enable explicitly when needed.
9110
+ if (isSelfSub && e.track.kind === 'audio') {
9111
+ e.track.enabled = false;
9112
+ }
9086
9113
  // get the previous stream to dispose it later
9087
9114
  // usually this happens during migration, when the stream is replaced
9088
9115
  // with a new one but the old one is still in the state
@@ -9091,8 +9118,12 @@ class Subscriber extends BasePeerConnection {
9091
9118
  this.state.updateParticipant(participantToUpdate.sessionId, {
9092
9119
  [streamKindProp]: primaryStream,
9093
9120
  });
9094
- // now, dispose the previous stream if it exists
9095
9121
  if (previousStream) {
9122
+ if (isSelfSub && !this.trackedStreams.has(previousStream)) {
9123
+ // this is the local capture stream, we don't want to dispose it
9124
+ this.logger.debug(`[onTrack]: Skipping cleanup of previous ${e.track.kind} stream for userId: ${participantToUpdate.userId} because it is not tracked`);
9125
+ return;
9126
+ }
9096
9127
  this.logger.info(`[onTrack]: Cleaning up previous remote ${track.kind} tracks for userId: ${participantToUpdate.userId}`);
9097
9128
  previousStream.getTracks().forEach((t) => {
9098
9129
  t.stop();
@@ -13915,6 +13946,7 @@ class Call {
13915
13946
  // maintain the order of publishing tracks to restore them after a reconnection
13916
13947
  // it shouldn't contain duplicates
13917
13948
  this.trackPublishOrder = [];
13949
+ this.selfSubEnabled = false;
13918
13950
  this.hasJoinedOnce = false;
13919
13951
  this.deviceSettingsAppliedOnce = false;
13920
13952
  this.initialized = false;
@@ -14264,6 +14296,30 @@ class Call {
14264
14296
  await Promise.all(stopOnLeavePromises);
14265
14297
  });
14266
14298
  };
14299
+ /**
14300
+ * The largest video publish dimension across the current publish options.
14301
+ *
14302
+ * @internal
14303
+ */
14304
+ this.getMaxVideoPublishDimension = () => {
14305
+ if (!this.currentPublishOptions)
14306
+ return undefined;
14307
+ let maxDimension;
14308
+ let maxArea = 0;
14309
+ for (const opt of this.currentPublishOptions) {
14310
+ if (opt.trackType !== TrackType.VIDEO)
14311
+ continue;
14312
+ const dim = opt.videoDimension;
14313
+ if (!dim || !dim.width || !dim.height)
14314
+ continue;
14315
+ const area = dim.width * dim.height;
14316
+ if (area > maxArea) {
14317
+ maxDimension = dim;
14318
+ maxArea = area;
14319
+ }
14320
+ }
14321
+ return maxDimension;
14322
+ };
14267
14323
  /**
14268
14324
  * Update from the call response from the "call.ring" event
14269
14325
  * @internal
@@ -14410,7 +14466,7 @@ class Call {
14410
14466
  *
14411
14467
  * @returns a promise which resolves once the call join-flow has finished.
14412
14468
  */
14413
- this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
14469
+ this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, selfSubEnabled = false, ...data } = {}) => {
14414
14470
  const callingState = this.state.callingState;
14415
14471
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
14416
14472
  throw new Error(`Illegal State: call.join() shall be called only once`);
@@ -14418,6 +14474,9 @@ class Call {
14418
14474
  if (data?.ring) {
14419
14475
  this.ringingSubject.next(true);
14420
14476
  }
14477
+ // we need this to be set before the callingx.joinCall() is
14478
+ // called to avoid registering the test call in the CallKit/Telecom
14479
+ this.selfSubEnabled = selfSubEnabled;
14421
14480
  const callingX = globalThis.streamRNVideoSDK?.callingX;
14422
14481
  if (callingX) {
14423
14482
  // for Android/iOS, we need to start the call in the callingx library as soon as possible
@@ -14807,7 +14866,9 @@ class Call {
14807
14866
  if (closePreviousInstances && this.publisher) {
14808
14867
  await this.publisher.dispose();
14809
14868
  }
14810
- this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
14869
+ this.publisher = new Publisher(basePeerConnectionOptions, publishOptions, {
14870
+ selfSubEnabled: this.selfSubEnabled,
14871
+ });
14811
14872
  }
14812
14873
  this.statsReporter?.stop();
14813
14874
  if (this.statsReportingIntervalInMs > 0) {
@@ -16303,6 +16364,12 @@ class Call {
16303
16364
  get currentUserId() {
16304
16365
  return this.clientStore.connectedUser?.id;
16305
16366
  }
16367
+ /**
16368
+ * A flag indicating whether self-subscription is enabled for the call.
16369
+ */
16370
+ get isSelfSubEnabled() {
16371
+ return this.selfSubEnabled;
16372
+ }
16306
16373
  /**
16307
16374
  * A flag indicating whether the call was created by the current user.
16308
16375
  */
@@ -17492,11 +17559,11 @@ class StreamClient {
17492
17559
  return await this.wsConnection.connect(this.defaultWSTimeout);
17493
17560
  };
17494
17561
  this.getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
17495
- "1.54.0";
17562
+ "1.54.1-beta.0";
17496
17563
  this.getUserAgent = () => {
17497
17564
  if (!this.cachedUserAgent) {
17498
17565
  const { clientAppIdentifier = {} } = this.options;
17499
- const { sdkName = 'js', sdkVersion = "1.54.0", ...extras } = clientAppIdentifier;
17566
+ const { sdkName = 'js', sdkVersion = "1.54.1-beta.0", ...extras } = clientAppIdentifier;
17500
17567
  this.cachedUserAgent = [
17501
17568
  `stream-video-${sdkName}-v${sdkVersion}`,
17502
17569
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),