@stream-io/video-client 1.52.0 → 1.52.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.52.0";
6651
+ const version = "1.52.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
 
@@ -8408,7 +8416,7 @@ class Publisher extends BasePeerConnection {
8408
8416
  /**
8409
8417
  * Constructs a new `Publisher` instance.
8410
8418
  */
8411
- constructor(baseOptions, publishOptions) {
8419
+ constructor(baseOptions, publishOptions, opts = {}) {
8412
8420
  super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
8413
8421
  this.transceiverCache = new TransceiverCache();
8414
8422
  this.clonedTracks = new Set();
@@ -8829,6 +8837,7 @@ class Publisher extends BasePeerConnection {
8829
8837
  muted: !isTrackLive,
8830
8838
  codec: publishOption.codec,
8831
8839
  publishOptionId: publishOption.id,
8840
+ selfSubAudioVideo: this.selfSubEnabled,
8832
8841
  };
8833
8842
  };
8834
8843
  this.cloneTrack = (track) => {
@@ -8909,6 +8918,7 @@ class Publisher extends BasePeerConnection {
8909
8918
  });
8910
8919
  };
8911
8920
  this.publishOptions = publishOptions;
8921
+ this.selfSubEnabled = opts.selfSubEnabled ?? false;
8912
8922
  this.on('iceRestart', (iceRestart) => {
8913
8923
  if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
8914
8924
  return;
@@ -8990,6 +9000,13 @@ class Subscriber extends BasePeerConnection {
8990
9000
  */
8991
9001
  constructor(opts) {
8992
9002
  super(PeerType.SUBSCRIBER, opts);
9003
+ /**
9004
+ * Remote streams received from the SFU. For a self-sub case
9005
+ * we need to be able to distinguish between the local capture stream.
9006
+ * The map will never contain local streams so we can safely use it to
9007
+ * check if the stream is remote and dispose it when needed.
9008
+ */
9009
+ this.trackedStreams = new WeakSet();
8993
9010
  /**
8994
9011
  * Restarts the ICE connection and renegotiates with the SFU.
8995
9012
  */
@@ -9024,6 +9041,7 @@ class Subscriber extends BasePeerConnection {
9024
9041
  // example: `e3f6aaf8-b03d-4911-be36-83f47d37a76a:TRACK_TYPE_VIDEO`
9025
9042
  const [trackId, rawTrackType] = primaryStream.id.split(':');
9026
9043
  const participantToUpdate = this.state.participants.find((p) => p.trackLookupPrefix === trackId);
9044
+ const isSelfSub = !!participantToUpdate?.isLocalParticipant;
9027
9045
  this.logger.debug(`[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`, track.id, track);
9028
9046
  const trackType = toTrackType(rawTrackType);
9029
9047
  if (!trackType) {
@@ -9047,6 +9065,9 @@ class Subscriber extends BasePeerConnection {
9047
9065
  this.setRemoteTrackInterrupted(trackId, trackType, true);
9048
9066
  }
9049
9067
  this.trackIdToTrackType.set(track.id, trackType);
9068
+ if (isSelfSub) {
9069
+ this.trackedStreams.add(primaryStream);
9070
+ }
9050
9071
  if (!participantToUpdate) {
9051
9072
  this.logger.warn(`[onTrack]: Received track for unknown participant: ${trackId}`, e);
9052
9073
  this.state.registerOrphanedTrack({
@@ -9062,6 +9083,12 @@ class Subscriber extends BasePeerConnection {
9062
9083
  this.logger.error(`Unknown track type: ${rawTrackType}`);
9063
9084
  return;
9064
9085
  }
9086
+ // Self-sub loopback audio routes to the speaker by default, which
9087
+ // would echo the local user's voice. Default-mute here; consumers
9088
+ // (the loopback recording hook) re-enable explicitly when needed.
9089
+ if (isSelfSub && e.track.kind === 'audio') {
9090
+ e.track.enabled = false;
9091
+ }
9065
9092
  // get the previous stream to dispose it later
9066
9093
  // usually this happens during migration, when the stream is replaced
9067
9094
  // with a new one but the old one is still in the state
@@ -9070,8 +9097,12 @@ class Subscriber extends BasePeerConnection {
9070
9097
  this.state.updateParticipant(participantToUpdate.sessionId, {
9071
9098
  [streamKindProp]: primaryStream,
9072
9099
  });
9073
- // now, dispose the previous stream if it exists
9074
9100
  if (previousStream) {
9101
+ if (isSelfSub && !this.trackedStreams.has(previousStream)) {
9102
+ // this is the local capture stream, we don't want to dispose it
9103
+ this.logger.debug(`[onTrack]: Skipping cleanup of previous ${e.track.kind} stream for userId: ${participantToUpdate.userId} because it is not tracked`);
9104
+ return;
9105
+ }
9075
9106
  this.logger.info(`[onTrack]: Cleaning up previous remote ${track.kind} tracks for userId: ${participantToUpdate.userId}`);
9076
9107
  previousStream.getTracks().forEach((t) => {
9077
9108
  t.stop();
@@ -13829,6 +13860,7 @@ class Call {
13829
13860
  // maintain the order of publishing tracks to restore them after a reconnection
13830
13861
  // it shouldn't contain duplicates
13831
13862
  this.trackPublishOrder = [];
13863
+ this.selfSubEnabled = false;
13832
13864
  this.hasJoinedOnce = false;
13833
13865
  this.deviceSettingsAppliedOnce = false;
13834
13866
  this.initialized = false;
@@ -14173,6 +14205,30 @@ class Call {
14173
14205
  await Promise.all(stopOnLeavePromises);
14174
14206
  });
14175
14207
  };
14208
+ /**
14209
+ * The largest video publish dimension across the current publish options.
14210
+ *
14211
+ * @internal
14212
+ */
14213
+ this.getMaxVideoPublishDimension = () => {
14214
+ if (!this.currentPublishOptions)
14215
+ return undefined;
14216
+ let maxDimension;
14217
+ let maxArea = 0;
14218
+ for (const opt of this.currentPublishOptions) {
14219
+ if (opt.trackType !== TrackType.VIDEO)
14220
+ continue;
14221
+ const dim = opt.videoDimension;
14222
+ if (!dim || !dim.width || !dim.height)
14223
+ continue;
14224
+ const area = dim.width * dim.height;
14225
+ if (area > maxArea) {
14226
+ maxDimension = dim;
14227
+ maxArea = area;
14228
+ }
14229
+ }
14230
+ return maxDimension;
14231
+ };
14176
14232
  /**
14177
14233
  * Update from the call response from the "call.ring" event
14178
14234
  * @internal
@@ -14319,7 +14375,7 @@ class Call {
14319
14375
  *
14320
14376
  * @returns a promise which resolves once the call join-flow has finished.
14321
14377
  */
14322
- this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
14378
+ this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, selfSubEnabled = false, ...data } = {}) => {
14323
14379
  const callingState = this.state.callingState;
14324
14380
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
14325
14381
  throw new Error(`Illegal State: call.join() shall be called only once`);
@@ -14327,6 +14383,9 @@ class Call {
14327
14383
  if (data?.ring) {
14328
14384
  this.ringingSubject.next(true);
14329
14385
  }
14386
+ // we need this to be set before the callingx.joinCall() is
14387
+ // called to avoid registering the test call in the CallKit/Telecom
14388
+ this.selfSubEnabled = selfSubEnabled;
14330
14389
  const callingX = globalThis.streamRNVideoSDK?.callingX;
14331
14390
  if (callingX) {
14332
14391
  // for Android/iOS, we need to start the call in the callingx library as soon as possible
@@ -14694,7 +14753,9 @@ class Call {
14694
14753
  if (closePreviousInstances && this.publisher) {
14695
14754
  await this.publisher.dispose();
14696
14755
  }
14697
- this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
14756
+ this.publisher = new Publisher(basePeerConnectionOptions, publishOptions, {
14757
+ selfSubEnabled: this.selfSubEnabled,
14758
+ });
14698
14759
  }
14699
14760
  this.statsReporter?.stop();
14700
14761
  if (this.statsReportingIntervalInMs > 0) {
@@ -16124,6 +16185,12 @@ class Call {
16124
16185
  get currentUserId() {
16125
16186
  return this.clientStore.connectedUser?.id;
16126
16187
  }
16188
+ /**
16189
+ * A flag indicating whether self-subscription is enabled for the call.
16190
+ */
16191
+ get isSelfSubEnabled() {
16192
+ return this.selfSubEnabled;
16193
+ }
16127
16194
  /**
16128
16195
  * A flag indicating whether the call was created by the current user.
16129
16196
  */
@@ -17315,7 +17382,7 @@ class StreamClient {
17315
17382
  this.getUserAgent = () => {
17316
17383
  if (!this.cachedUserAgent) {
17317
17384
  const { clientAppIdentifier = {} } = this.options;
17318
- const { sdkName = 'js', sdkVersion = "1.52.0", ...extras } = clientAppIdentifier;
17385
+ const { sdkName = 'js', sdkVersion = "1.52.1-beta.0", ...extras } = clientAppIdentifier;
17319
17386
  this.cachedUserAgent = [
17320
17387
  `stream-video-${sdkName}-v${sdkVersion}`,
17321
17388
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),