@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.
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.52.0";
6671
+ const version = "1.52.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: browserVersion,
6815
+ webrtcVersion: webRtcInfo?.version || '',
6808
6816
  };
6809
6817
  };
6810
6818
 
@@ -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) {
@@ -9067,6 +9085,9 @@ class Subscriber extends BasePeerConnection {
9067
9085
  this.setRemoteTrackInterrupted(trackId, trackType, true);
9068
9086
  }
9069
9087
  this.trackIdToTrackType.set(track.id, trackType);
9088
+ if (isSelfSub) {
9089
+ this.trackedStreams.add(primaryStream);
9090
+ }
9070
9091
  if (!participantToUpdate) {
9071
9092
  this.logger.warn(`[onTrack]: Received track for unknown participant: ${trackId}`, e);
9072
9093
  this.state.registerOrphanedTrack({
@@ -9082,6 +9103,12 @@ class Subscriber extends BasePeerConnection {
9082
9103
  this.logger.error(`Unknown track type: ${rawTrackType}`);
9083
9104
  return;
9084
9105
  }
9106
+ // Self-sub loopback audio routes to the speaker by default, which
9107
+ // would echo the local user's voice. Default-mute here; consumers
9108
+ // (the loopback recording hook) re-enable explicitly when needed.
9109
+ if (isSelfSub && e.track.kind === 'audio') {
9110
+ e.track.enabled = false;
9111
+ }
9085
9112
  // get the previous stream to dispose it later
9086
9113
  // usually this happens during migration, when the stream is replaced
9087
9114
  // with a new one but the old one is still in the state
@@ -9090,8 +9117,12 @@ class Subscriber extends BasePeerConnection {
9090
9117
  this.state.updateParticipant(participantToUpdate.sessionId, {
9091
9118
  [streamKindProp]: primaryStream,
9092
9119
  });
9093
- // now, dispose the previous stream if it exists
9094
9120
  if (previousStream) {
9121
+ if (isSelfSub && !this.trackedStreams.has(previousStream)) {
9122
+ // this is the local capture stream, we don't want to dispose it
9123
+ this.logger.debug(`[onTrack]: Skipping cleanup of previous ${e.track.kind} stream for userId: ${participantToUpdate.userId} because it is not tracked`);
9124
+ return;
9125
+ }
9095
9126
  this.logger.info(`[onTrack]: Cleaning up previous remote ${track.kind} tracks for userId: ${participantToUpdate.userId}`);
9096
9127
  previousStream.getTracks().forEach((t) => {
9097
9128
  t.stop();
@@ -13849,6 +13880,7 @@ class Call {
13849
13880
  // maintain the order of publishing tracks to restore them after a reconnection
13850
13881
  // it shouldn't contain duplicates
13851
13882
  this.trackPublishOrder = [];
13883
+ this.selfSubEnabled = false;
13852
13884
  this.hasJoinedOnce = false;
13853
13885
  this.deviceSettingsAppliedOnce = false;
13854
13886
  this.initialized = false;
@@ -14193,6 +14225,30 @@ class Call {
14193
14225
  await Promise.all(stopOnLeavePromises);
14194
14226
  });
14195
14227
  };
14228
+ /**
14229
+ * The largest video publish dimension across the current publish options.
14230
+ *
14231
+ * @internal
14232
+ */
14233
+ this.getMaxVideoPublishDimension = () => {
14234
+ if (!this.currentPublishOptions)
14235
+ return undefined;
14236
+ let maxDimension;
14237
+ let maxArea = 0;
14238
+ for (const opt of this.currentPublishOptions) {
14239
+ if (opt.trackType !== TrackType.VIDEO)
14240
+ continue;
14241
+ const dim = opt.videoDimension;
14242
+ if (!dim || !dim.width || !dim.height)
14243
+ continue;
14244
+ const area = dim.width * dim.height;
14245
+ if (area > maxArea) {
14246
+ maxDimension = dim;
14247
+ maxArea = area;
14248
+ }
14249
+ }
14250
+ return maxDimension;
14251
+ };
14196
14252
  /**
14197
14253
  * Update from the call response from the "call.ring" event
14198
14254
  * @internal
@@ -14339,7 +14395,7 @@ class Call {
14339
14395
  *
14340
14396
  * @returns a promise which resolves once the call join-flow has finished.
14341
14397
  */
14342
- this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, ...data } = {}) => {
14398
+ this.join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, selfSubEnabled = false, ...data } = {}) => {
14343
14399
  const callingState = this.state.callingState;
14344
14400
  if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
14345
14401
  throw new Error(`Illegal State: call.join() shall be called only once`);
@@ -14347,6 +14403,9 @@ class Call {
14347
14403
  if (data?.ring) {
14348
14404
  this.ringingSubject.next(true);
14349
14405
  }
14406
+ // we need this to be set before the callingx.joinCall() is
14407
+ // called to avoid registering the test call in the CallKit/Telecom
14408
+ this.selfSubEnabled = selfSubEnabled;
14350
14409
  const callingX = globalThis.streamRNVideoSDK?.callingX;
14351
14410
  if (callingX) {
14352
14411
  // for Android/iOS, we need to start the call in the callingx library as soon as possible
@@ -14714,7 +14773,9 @@ class Call {
14714
14773
  if (closePreviousInstances && this.publisher) {
14715
14774
  await this.publisher.dispose();
14716
14775
  }
14717
- this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
14776
+ this.publisher = new Publisher(basePeerConnectionOptions, publishOptions, {
14777
+ selfSubEnabled: this.selfSubEnabled,
14778
+ });
14718
14779
  }
14719
14780
  this.statsReporter?.stop();
14720
14781
  if (this.statsReportingIntervalInMs > 0) {
@@ -16144,6 +16205,12 @@ class Call {
16144
16205
  get currentUserId() {
16145
16206
  return this.clientStore.connectedUser?.id;
16146
16207
  }
16208
+ /**
16209
+ * A flag indicating whether self-subscription is enabled for the call.
16210
+ */
16211
+ get isSelfSubEnabled() {
16212
+ return this.selfSubEnabled;
16213
+ }
16147
16214
  /**
16148
16215
  * A flag indicating whether the call was created by the current user.
16149
16216
  */
@@ -17333,7 +17400,7 @@ class StreamClient {
17333
17400
  this.getUserAgent = () => {
17334
17401
  if (!this.cachedUserAgent) {
17335
17402
  const { clientAppIdentifier = {} } = this.options;
17336
- const { sdkName = 'js', sdkVersion = "1.52.0", ...extras } = clientAppIdentifier;
17403
+ const { sdkName = 'js', sdkVersion = "1.52.1-beta.0", ...extras } = clientAppIdentifier;
17337
17404
  this.cachedUserAgent = [
17338
17405
  `stream-video-${sdkName}-v${sdkVersion}`,
17339
17406
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),