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