@stream-io/video-client 1.37.0 → 1.37.2

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.es.js CHANGED
@@ -4868,6 +4868,26 @@ const defaultEgress = {
4868
4868
  hls: { playlist_url: '', status: '' },
4869
4869
  rtmps: [],
4870
4870
  };
4871
+ /**
4872
+ * Creates a stable participant filter function, ready to be used in combination
4873
+ * with the `useSyncExternalStore` hook.
4874
+ *
4875
+ * @param predicate the predicate to use.
4876
+ */
4877
+ const createStableParticipantsFilter = (predicate) => {
4878
+ const empty = [];
4879
+ return (participants) => {
4880
+ // no need to filter if there are no participants
4881
+ if (!participants.length)
4882
+ return participants;
4883
+ // return a stable empty array if there are no remote participants
4884
+ // instead of creating an empty one
4885
+ const filteredParticipants = participants.filter(predicate);
4886
+ if (!filteredParticipants.length)
4887
+ return empty;
4888
+ return filteredParticipants;
4889
+ };
4890
+ };
4871
4891
  /**
4872
4892
  * Holds the state of the current call.
4873
4893
  * @react You don't have to use this class directly, as we are exposing the state through Hooks.
@@ -4991,6 +5011,17 @@ class CallState {
4991
5011
  this.setAnonymousParticipantCount = (count) => {
4992
5012
  return this.setCurrentValue(this.anonymousParticipantCountSubject, count);
4993
5013
  };
5014
+ /**
5015
+ * Returns the current participants array directly from the BehaviorSubject.
5016
+ * This bypasses the observable pipeline and is guaranteed to be synchronous.
5017
+ * Use this when you need the absolute latest value without any potential
5018
+ * timing issues from shareReplay/refCount.
5019
+ *
5020
+ * @internal
5021
+ */
5022
+ this.getParticipantsSnapshot = () => {
5023
+ return this.participantsSubject.getValue();
5024
+ };
4994
5025
  /**
4995
5026
  * Sets the list of participants in the current call.
4996
5027
  *
@@ -5536,8 +5567,8 @@ class CallState {
5536
5567
  // in the original subject
5537
5568
  map((ps) => ps.sort(this.sortParticipantsBy)), shareReplay({ bufferSize: 1, refCount: true }));
5538
5569
  this.localParticipant$ = this.participants$.pipe(map((participants) => participants.find((p) => p.isLocalParticipant)), shareReplay({ bufferSize: 1, refCount: true }));
5539
- this.remoteParticipants$ = this.participants$.pipe(map((participants) => participants.filter((p) => !p.isLocalParticipant)), shareReplay({ bufferSize: 1, refCount: true }));
5540
- this.pinnedParticipants$ = this.participants$.pipe(map((participants) => participants.filter((p) => !!p.pin)), shareReplay({ bufferSize: 1, refCount: true }));
5570
+ this.remoteParticipants$ = this.participants$.pipe(map(createStableParticipantsFilter((p) => !p.isLocalParticipant)), shareReplay({ bufferSize: 1, refCount: true }));
5571
+ this.pinnedParticipants$ = this.participants$.pipe(map(createStableParticipantsFilter((p) => !!p.pin)), shareReplay({ bufferSize: 1, refCount: true }));
5541
5572
  this.dominantSpeaker$ = this.participants$.pipe(map((participants) => participants.find((p) => p.isDominantSpeaker)), shareReplay({ bufferSize: 1, refCount: true }));
5542
5573
  this.hasOngoingScreenShare$ = this.participants$.pipe(map((participants) => participants.some((p) => hasScreenShare(p))), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
5543
5574
  // dates
@@ -5970,7 +6001,7 @@ const getSdkVersion = (sdk) => {
5970
6001
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5971
6002
  };
5972
6003
 
5973
- const version = "1.37.0";
6004
+ const version = "1.37.2";
5974
6005
  const [major, minor, patch] = version.split('.');
5975
6006
  let sdkInfo = {
5976
6007
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -9447,13 +9478,19 @@ class DynascaleManager {
9447
9478
  }
9448
9479
  get trackSubscriptions() {
9449
9480
  const subscriptions = [];
9450
- for (const p of this.callState.remoteParticipants) {
9481
+ // Use getParticipantsSnapshot() to bypass the observable pipeline
9482
+ // and avoid stale data caused by shareReplay with no active subscribers
9483
+ const participants = this.callState.getParticipantsSnapshot();
9484
+ const videoTrackSubscriptionOverrides = this.videoTrackSubscriptionOverridesSubject.getValue();
9485
+ for (const p of participants) {
9486
+ if (p.isLocalParticipant)
9487
+ continue;
9451
9488
  // NOTE: audio tracks don't have to be requested explicitly
9452
9489
  // as the SFU will implicitly subscribe us to all of them,
9453
9490
  // once they become available.
9454
9491
  if (p.videoDimension && hasVideo(p)) {
9455
- const override = this.videoTrackSubscriptionOverrides[p.sessionId] ??
9456
- this.videoTrackSubscriptionOverrides[globalOverrideKey];
9492
+ const override = videoTrackSubscriptionOverrides[p.sessionId] ??
9493
+ videoTrackSubscriptionOverrides[globalOverrideKey];
9457
9494
  if (override?.enabled !== false) {
9458
9495
  subscriptions.push({
9459
9496
  userId: p.userId,
@@ -14872,7 +14909,7 @@ class StreamClient {
14872
14909
  this.getUserAgent = () => {
14873
14910
  if (!this.cachedUserAgent) {
14874
14911
  const { clientAppIdentifier = {} } = this.options;
14875
- const { sdkName = 'js', sdkVersion = "1.37.0", ...extras } = clientAppIdentifier;
14912
+ const { sdkName = 'js', sdkVersion = "1.37.2", ...extras } = clientAppIdentifier;
14876
14913
  this.cachedUserAgent = [
14877
14914
  `stream-video-${sdkName}-v${sdkVersion}`,
14878
14915
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),