@stream-io/video-client 1.18.4 → 1.18.6

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,23 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.18.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.5...@stream-io/video-client-1.18.6) (2025-03-13)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * ensure negotiation runs sequentially ([#1722](https://github.com/GetStream/stream-video-js/issues/1722)) ([7e166aa](https://github.com/GetStream/stream-video-js/commit/7e166aaf606c3f751068cf60bd554e6374f701d7))
11
+
12
+ ## [1.18.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.4...@stream-io/video-client-1.18.5) (2025-03-12)
13
+
14
+
15
+ * Upgrade to Next 15.2 ([#1717](https://github.com/GetStream/stream-video-js/issues/1717)) ([9b1aec3](https://github.com/GetStream/stream-video-js/commit/9b1aec3447dee611c0d900db44add6b6c89e2b8d))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * add pending browser permission state ([#1718](https://github.com/GetStream/stream-video-js/issues/1718)) ([7f24be6](https://github.com/GetStream/stream-video-js/commit/7f24be63d33105d0688be7b5b625bc9b6aa0d3a9))
21
+
5
22
  ## [1.18.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.3...@stream-io/video-client-1.18.4) (2025-03-10)
6
23
 
7
24
 
@@ -3645,7 +3645,10 @@ function hasPending(tag) {
3645
3645
  return pendingPromises.has(tag);
3646
3646
  }
3647
3647
  async function settled(tag) {
3648
- await pendingPromises.get(tag)?.promise;
3648
+ let pending;
3649
+ while ((pending = pendingPromises.get(tag))) {
3650
+ await pending.promise;
3651
+ }
3649
3652
  }
3650
3653
  /**
3651
3654
  * Implements common functionality of running async functions serially, by chaining
@@ -5344,8 +5347,6 @@ class BasePeerConnection {
5344
5347
  this.pc.removeEventListener('icecandidateerror', this.onIceCandidateError);
5345
5348
  this.pc.removeEventListener('signalingstatechange', this.onSignalingChange);
5346
5349
  this.pc.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
5347
- // cancel any ongoing ICE restart process
5348
- withCancellation('onIceConnectionStateChange', () => Promise.resolve());
5349
5350
  this.pc.removeEventListener('icegatheringstatechange', this.onIceGatherChange);
5350
5351
  this.unsubscribeIceTrickle?.();
5351
5352
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
@@ -5375,12 +5376,6 @@ class TransceiverCache {
5375
5376
  this.get = (publishOption) => {
5376
5377
  return this.findTransceiver(publishOption)?.transceiver;
5377
5378
  };
5378
- /**
5379
- * Gets the last transceiver for the given track type and publish option id.
5380
- */
5381
- this.getWith = (trackType, id) => {
5382
- return this.findTransceiver({ trackType, id })?.transceiver;
5383
- };
5384
5379
  /**
5385
5380
  * Checks if the cache has the given publish option.
5386
5381
  */
@@ -5694,7 +5689,7 @@ class Publisher extends BasePeerConnection {
5694
5689
  const trackToPublish = this.cloneTrack(track);
5695
5690
  const transceiver = this.transceiverCache.get(publishOption);
5696
5691
  if (!transceiver) {
5697
- this.addTransceiver(trackToPublish, publishOption);
5692
+ await this.addTransceiver(trackToPublish, publishOption);
5698
5693
  }
5699
5694
  else {
5700
5695
  const previousTrack = transceiver.sender.track;
@@ -5708,7 +5703,7 @@ class Publisher extends BasePeerConnection {
5708
5703
  /**
5709
5704
  * Adds a new transceiver carrying the given track to the peer connection.
5710
5705
  */
5711
- this.addTransceiver = (track, publishOption) => {
5706
+ this.addTransceiver = async (track, publishOption) => {
5712
5707
  const videoEncodings = computeVideoLayers(track, publishOption);
5713
5708
  const sendEncodings = isSvcCodec(publishOption.codec?.name)
5714
5709
  ? toSvcEncodings(videoEncodings)
@@ -5720,6 +5715,7 @@ class Publisher extends BasePeerConnection {
5720
5715
  const trackType = publishOption.trackType;
5721
5716
  this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
5722
5717
  this.transceiverCache.add(publishOption, transceiver);
5718
+ await this.negotiate();
5723
5719
  };
5724
5720
  /**
5725
5721
  * Synchronizes the current Publisher state with the provided publish options.
@@ -5739,7 +5735,7 @@ class Publisher extends BasePeerConnection {
5739
5735
  // take the track from the existing transceiver for the same track type,
5740
5736
  // clone it and publish it with the new publish options
5741
5737
  const track = this.cloneTrack(item.transceiver.sender.track);
5742
- this.addTransceiver(track, publishOption);
5738
+ await this.addTransceiver(track, publishOption);
5743
5739
  }
5744
5740
  // stop publishing with options not required anymore -> [vp9]
5745
5741
  for (const item of this.transceiverCache.items()) {
@@ -5809,7 +5805,9 @@ class Publisher extends BasePeerConnection {
5809
5805
  const enabledLayers = layers.filter((l) => l.active);
5810
5806
  const tag = 'Update publish quality:';
5811
5807
  this.logger('info', `${tag} requested layers by SFU:`, enabledLayers);
5812
- const sender = this.transceiverCache.getWith(trackType, publishOptionId)?.sender;
5808
+ const transceiverId = this.transceiverCache.find((t) => t.publishOption.id === publishOptionId &&
5809
+ t.publishOption.trackType === trackType);
5810
+ const sender = transceiverId?.transceiver.sender;
5813
5811
  if (!sender) {
5814
5812
  return this.logger('warn', `${tag} no video sender found.`);
5815
5813
  }
@@ -5817,8 +5815,8 @@ class Publisher extends BasePeerConnection {
5817
5815
  if (params.encodings.length === 0) {
5818
5816
  return this.logger('warn', `${tag} there are no encodings set.`);
5819
5817
  }
5820
- const [codecInUse] = params.codecs;
5821
- const usesSvcCodec = codecInUse && isSvcCodec(codecInUse.mimeType);
5818
+ const codecInUse = transceiverId?.publishOption.codec?.name;
5819
+ const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
5822
5820
  let changed = false;
5823
5821
  for (const encoder of params.encodings) {
5824
5822
  const layer = usesSvcCodec
@@ -5876,40 +5874,32 @@ class Publisher extends BasePeerConnection {
5876
5874
  }
5877
5875
  await this.negotiate({ iceRestart: true });
5878
5876
  };
5879
- this.onNegotiationNeeded = () => {
5880
- withCancellation('publisher.negotiate', (signal) => this.negotiate().catch((err) => {
5881
- if (signal.aborted)
5882
- return;
5883
- this.logger('error', `Negotiation failed.`, err);
5884
- this.onUnrecoverableError?.();
5885
- }));
5886
- };
5887
5877
  /**
5888
5878
  * Initiates a new offer/answer exchange with the currently connected SFU.
5889
5879
  *
5890
5880
  * @param options the optional offer options to use.
5891
5881
  */
5892
5882
  this.negotiate = async (options) => {
5893
- const offer = await this.pc.createOffer(options);
5894
- const trackInfos = this.getAnnouncedTracks(offer.sdp);
5895
- if (trackInfos.length === 0) {
5896
- throw new Error(`Can't negotiate without announcing any tracks`);
5897
- }
5898
- try {
5899
- this.isIceRestarting = options?.iceRestart ?? false;
5900
- await this.pc.setLocalDescription(offer);
5901
- const { response } = await this.sfuClient.setPublisher({
5902
- sdp: offer.sdp || '',
5903
- tracks: trackInfos,
5904
- });
5905
- if (response.error)
5906
- throw new Error(response.error.message);
5907
- await this.pc.setRemoteDescription({ type: 'answer', sdp: response.sdp });
5908
- }
5909
- finally {
5910
- this.isIceRestarting = false;
5911
- }
5912
- this.addTrickledIceCandidates();
5883
+ return withoutConcurrency('publisher.negotiate', async () => {
5884
+ const offer = await this.pc.createOffer(options);
5885
+ const tracks = this.getAnnouncedTracks(offer.sdp);
5886
+ if (!tracks.length)
5887
+ throw new Error(`Can't negotiate without any tracks`);
5888
+ try {
5889
+ this.isIceRestarting = options?.iceRestart ?? false;
5890
+ await this.pc.setLocalDescription(offer);
5891
+ const { sdp = '' } = offer;
5892
+ const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
5893
+ if (response.error)
5894
+ throw new Error(response.error.message);
5895
+ const { sdp: answerSdp } = response;
5896
+ await this.pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
5897
+ }
5898
+ finally {
5899
+ this.isIceRestarting = false;
5900
+ }
5901
+ this.addTrickledIceCandidates();
5902
+ });
5913
5903
  };
5914
5904
  /**
5915
5905
  * Returns a list of tracks that are currently being published.
@@ -5993,7 +5983,6 @@ class Publisher extends BasePeerConnection {
5993
5983
  this.clonedTracks.delete(track);
5994
5984
  };
5995
5985
  this.publishOptions = publishOptions;
5996
- this.pc.addEventListener('negotiationneeded', this.onNegotiationNeeded);
5997
5986
  this.on('iceRestart', (iceRestart) => {
5998
5987
  if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
5999
5988
  return;
@@ -6012,17 +6001,6 @@ class Publisher extends BasePeerConnection {
6012
6001
  return this.syncPublishOptions();
6013
6002
  });
6014
6003
  }
6015
- /**
6016
- * Detaches the event handlers from the `RTCPeerConnection`.
6017
- * This is useful when we want to replace the `RTCPeerConnection`
6018
- * instance with a new one (in case of migration).
6019
- */
6020
- detachEventHandlers() {
6021
- super.detachEventHandlers();
6022
- this.pc.removeEventListener('negotiationneeded', this.onNegotiationNeeded);
6023
- // abort any ongoing negotiation
6024
- withCancellation('publisher.negotiate', () => Promise.resolve());
6025
- }
6026
6004
  /**
6027
6005
  * Disposes this Publisher instance.
6028
6006
  */
@@ -7454,7 +7432,7 @@ const aggregate = (stats) => {
7454
7432
  return report;
7455
7433
  };
7456
7434
 
7457
- const version = "1.18.4";
7435
+ const version = "1.18.6";
7458
7436
  const [major, minor, patch] = version.split('.');
7459
7437
  let sdkInfo = {
7460
7438
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8388,6 +8366,7 @@ class BrowserPermission {
8388
8366
  }
8389
8367
  try {
8390
8368
  this.wasPrompted = true;
8369
+ this.setState('prompting');
8391
8370
  const stream = await navigator.mediaDevices.getUserMedia(this.permission.constraints);
8392
8371
  disposeOfMediaStream(stream);
8393
8372
  this.setState('granted');
@@ -8411,6 +8390,7 @@ class BrowserPermission {
8411
8390
  error: e,
8412
8391
  permission: this.permission,
8413
8392
  });
8393
+ this.setState('prompt');
8414
8394
  throw e;
8415
8395
  }
8416
8396
  });
@@ -8422,13 +8402,19 @@ class BrowserPermission {
8422
8402
  return () => this.listeners.delete(cb);
8423
8403
  }
8424
8404
  asObservable() {
8425
- return fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten()).pipe(
8405
+ return this.getStateObservable().pipe(
8426
8406
  // In some browsers, the 'change' event doesn't reliably emit and hence,
8427
8407
  // permissionState stays in 'prompt' state forever.
8428
8408
  // Typically, this happens when a user grants one-time permission.
8429
8409
  // Instead of checking if a permission is granted, we check if it isn't denied
8430
8410
  map((state) => state !== 'denied'));
8431
8411
  }
8412
+ getIsPromptingObservable() {
8413
+ return this.getStateObservable().pipe(map((state) => state === 'prompting'));
8414
+ }
8415
+ getStateObservable() {
8416
+ return fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten());
8417
+ }
8432
8418
  setState(state) {
8433
8419
  if (this.state !== state) {
8434
8420
  this.state = state;
@@ -9184,6 +9170,9 @@ class InputMediaDeviceManagerState {
9184
9170
  this.hasBrowserPermission$ = permission
9185
9171
  ? permission.asObservable().pipe(shareReplay(1))
9186
9172
  : of(true);
9173
+ this.isPromptingPermission$ = permission
9174
+ ? permission.getIsPromptingObservable().pipe(shareReplay(1))
9175
+ : of(false);
9187
9176
  }
9188
9177
  /**
9189
9178
  * The device status
@@ -13082,7 +13071,7 @@ class StreamClient {
13082
13071
  this.getUserAgent = () => {
13083
13072
  if (!this.cachedUserAgent) {
13084
13073
  const { clientAppIdentifier = {} } = this.options;
13085
- const { sdkName = 'js', sdkVersion = "1.18.4", ...extras } = clientAppIdentifier;
13074
+ const { sdkName = 'js', sdkVersion = "1.18.6", ...extras } = clientAppIdentifier;
13086
13075
  this.cachedUserAgent = [
13087
13076
  `stream-video-${sdkName}-v${sdkVersion}`,
13088
13077
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),