@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.cjs.js CHANGED
@@ -4887,6 +4887,26 @@ const defaultEgress = {
4887
4887
  hls: { playlist_url: '', status: '' },
4888
4888
  rtmps: [],
4889
4889
  };
4890
+ /**
4891
+ * Creates a stable participant filter function, ready to be used in combination
4892
+ * with the `useSyncExternalStore` hook.
4893
+ *
4894
+ * @param predicate the predicate to use.
4895
+ */
4896
+ const createStableParticipantsFilter = (predicate) => {
4897
+ const empty = [];
4898
+ return (participants) => {
4899
+ // no need to filter if there are no participants
4900
+ if (!participants.length)
4901
+ return participants;
4902
+ // return a stable empty array if there are no remote participants
4903
+ // instead of creating an empty one
4904
+ const filteredParticipants = participants.filter(predicate);
4905
+ if (!filteredParticipants.length)
4906
+ return empty;
4907
+ return filteredParticipants;
4908
+ };
4909
+ };
4890
4910
  /**
4891
4911
  * Holds the state of the current call.
4892
4912
  * @react You don't have to use this class directly, as we are exposing the state through Hooks.
@@ -5010,6 +5030,17 @@ class CallState {
5010
5030
  this.setAnonymousParticipantCount = (count) => {
5011
5031
  return this.setCurrentValue(this.anonymousParticipantCountSubject, count);
5012
5032
  };
5033
+ /**
5034
+ * Returns the current participants array directly from the BehaviorSubject.
5035
+ * This bypasses the observable pipeline and is guaranteed to be synchronous.
5036
+ * Use this when you need the absolute latest value without any potential
5037
+ * timing issues from shareReplay/refCount.
5038
+ *
5039
+ * @internal
5040
+ */
5041
+ this.getParticipantsSnapshot = () => {
5042
+ return this.participantsSubject.getValue();
5043
+ };
5013
5044
  /**
5014
5045
  * Sets the list of participants in the current call.
5015
5046
  *
@@ -5555,8 +5586,8 @@ class CallState {
5555
5586
  // in the original subject
5556
5587
  rxjs.map((ps) => ps.sort(this.sortParticipantsBy)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5557
5588
  this.localParticipant$ = this.participants$.pipe(rxjs.map((participants) => participants.find((p) => p.isLocalParticipant)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5558
- this.remoteParticipants$ = this.participants$.pipe(rxjs.map((participants) => participants.filter((p) => !p.isLocalParticipant)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5559
- this.pinnedParticipants$ = this.participants$.pipe(rxjs.map((participants) => participants.filter((p) => !!p.pin)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5589
+ this.remoteParticipants$ = this.participants$.pipe(rxjs.map(createStableParticipantsFilter((p) => !p.isLocalParticipant)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5590
+ this.pinnedParticipants$ = this.participants$.pipe(rxjs.map(createStableParticipantsFilter((p) => !!p.pin)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5560
5591
  this.dominantSpeaker$ = this.participants$.pipe(rxjs.map((participants) => participants.find((p) => p.isDominantSpeaker)), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5561
5592
  this.hasOngoingScreenShare$ = this.participants$.pipe(rxjs.map((participants) => participants.some((p) => hasScreenShare(p))), rxjs.distinctUntilChanged(), rxjs.shareReplay({ bufferSize: 1, refCount: true }));
5562
5593
  // dates
@@ -5989,7 +6020,7 @@ const getSdkVersion = (sdk) => {
5989
6020
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5990
6021
  };
5991
6022
 
5992
- const version = "1.37.0";
6023
+ const version = "1.37.2";
5993
6024
  const [major, minor, patch] = version.split('.');
5994
6025
  let sdkInfo = {
5995
6026
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -9466,13 +9497,19 @@ class DynascaleManager {
9466
9497
  }
9467
9498
  get trackSubscriptions() {
9468
9499
  const subscriptions = [];
9469
- for (const p of this.callState.remoteParticipants) {
9500
+ // Use getParticipantsSnapshot() to bypass the observable pipeline
9501
+ // and avoid stale data caused by shareReplay with no active subscribers
9502
+ const participants = this.callState.getParticipantsSnapshot();
9503
+ const videoTrackSubscriptionOverrides = this.videoTrackSubscriptionOverridesSubject.getValue();
9504
+ for (const p of participants) {
9505
+ if (p.isLocalParticipant)
9506
+ continue;
9470
9507
  // NOTE: audio tracks don't have to be requested explicitly
9471
9508
  // as the SFU will implicitly subscribe us to all of them,
9472
9509
  // once they become available.
9473
9510
  if (p.videoDimension && hasVideo(p)) {
9474
- const override = this.videoTrackSubscriptionOverrides[p.sessionId] ??
9475
- this.videoTrackSubscriptionOverrides[globalOverrideKey];
9511
+ const override = videoTrackSubscriptionOverrides[p.sessionId] ??
9512
+ videoTrackSubscriptionOverrides[globalOverrideKey];
9476
9513
  if (override?.enabled !== false) {
9477
9514
  subscriptions.push({
9478
9515
  userId: p.userId,
@@ -14891,7 +14928,7 @@ class StreamClient {
14891
14928
  this.getUserAgent = () => {
14892
14929
  if (!this.cachedUserAgent) {
14893
14930
  const { clientAppIdentifier = {} } = this.options;
14894
- const { sdkName = 'js', sdkVersion = "1.37.0", ...extras } = clientAppIdentifier;
14931
+ const { sdkName = 'js', sdkVersion = "1.37.2", ...extras } = clientAppIdentifier;
14895
14932
  this.cachedUserAgent = [
14896
14933
  `stream-video-${sdkName}-v${sdkVersion}`,
14897
14934
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),