@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/dist/index.cjs.js CHANGED
@@ -3647,7 +3647,10 @@ function hasPending(tag) {
3647
3647
  return pendingPromises.has(tag);
3648
3648
  }
3649
3649
  async function settled(tag) {
3650
- await pendingPromises.get(tag)?.promise;
3650
+ let pending;
3651
+ while ((pending = pendingPromises.get(tag))) {
3652
+ await pending.promise;
3653
+ }
3651
3654
  }
3652
3655
  /**
3653
3656
  * Implements common functionality of running async functions serially, by chaining
@@ -5346,8 +5349,6 @@ class BasePeerConnection {
5346
5349
  this.pc.removeEventListener('icecandidateerror', this.onIceCandidateError);
5347
5350
  this.pc.removeEventListener('signalingstatechange', this.onSignalingChange);
5348
5351
  this.pc.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
5349
- // cancel any ongoing ICE restart process
5350
- withCancellation('onIceConnectionStateChange', () => Promise.resolve());
5351
5352
  this.pc.removeEventListener('icegatheringstatechange', this.onIceGatherChange);
5352
5353
  this.unsubscribeIceTrickle?.();
5353
5354
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
@@ -5377,12 +5378,6 @@ class TransceiverCache {
5377
5378
  this.get = (publishOption) => {
5378
5379
  return this.findTransceiver(publishOption)?.transceiver;
5379
5380
  };
5380
- /**
5381
- * Gets the last transceiver for the given track type and publish option id.
5382
- */
5383
- this.getWith = (trackType, id) => {
5384
- return this.findTransceiver({ trackType, id })?.transceiver;
5385
- };
5386
5381
  /**
5387
5382
  * Checks if the cache has the given publish option.
5388
5383
  */
@@ -5696,7 +5691,7 @@ class Publisher extends BasePeerConnection {
5696
5691
  const trackToPublish = this.cloneTrack(track);
5697
5692
  const transceiver = this.transceiverCache.get(publishOption);
5698
5693
  if (!transceiver) {
5699
- this.addTransceiver(trackToPublish, publishOption);
5694
+ await this.addTransceiver(trackToPublish, publishOption);
5700
5695
  }
5701
5696
  else {
5702
5697
  const previousTrack = transceiver.sender.track;
@@ -5710,7 +5705,7 @@ class Publisher extends BasePeerConnection {
5710
5705
  /**
5711
5706
  * Adds a new transceiver carrying the given track to the peer connection.
5712
5707
  */
5713
- this.addTransceiver = (track, publishOption) => {
5708
+ this.addTransceiver = async (track, publishOption) => {
5714
5709
  const videoEncodings = computeVideoLayers(track, publishOption);
5715
5710
  const sendEncodings = isSvcCodec(publishOption.codec?.name)
5716
5711
  ? toSvcEncodings(videoEncodings)
@@ -5722,6 +5717,7 @@ class Publisher extends BasePeerConnection {
5722
5717
  const trackType = publishOption.trackType;
5723
5718
  this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
5724
5719
  this.transceiverCache.add(publishOption, transceiver);
5720
+ await this.negotiate();
5725
5721
  };
5726
5722
  /**
5727
5723
  * Synchronizes the current Publisher state with the provided publish options.
@@ -5741,7 +5737,7 @@ class Publisher extends BasePeerConnection {
5741
5737
  // take the track from the existing transceiver for the same track type,
5742
5738
  // clone it and publish it with the new publish options
5743
5739
  const track = this.cloneTrack(item.transceiver.sender.track);
5744
- this.addTransceiver(track, publishOption);
5740
+ await this.addTransceiver(track, publishOption);
5745
5741
  }
5746
5742
  // stop publishing with options not required anymore -> [vp9]
5747
5743
  for (const item of this.transceiverCache.items()) {
@@ -5811,7 +5807,9 @@ class Publisher extends BasePeerConnection {
5811
5807
  const enabledLayers = layers.filter((l) => l.active);
5812
5808
  const tag = 'Update publish quality:';
5813
5809
  this.logger('info', `${tag} requested layers by SFU:`, enabledLayers);
5814
- const sender = this.transceiverCache.getWith(trackType, publishOptionId)?.sender;
5810
+ const transceiverId = this.transceiverCache.find((t) => t.publishOption.id === publishOptionId &&
5811
+ t.publishOption.trackType === trackType);
5812
+ const sender = transceiverId?.transceiver.sender;
5815
5813
  if (!sender) {
5816
5814
  return this.logger('warn', `${tag} no video sender found.`);
5817
5815
  }
@@ -5819,8 +5817,8 @@ class Publisher extends BasePeerConnection {
5819
5817
  if (params.encodings.length === 0) {
5820
5818
  return this.logger('warn', `${tag} there are no encodings set.`);
5821
5819
  }
5822
- const [codecInUse] = params.codecs;
5823
- const usesSvcCodec = codecInUse && isSvcCodec(codecInUse.mimeType);
5820
+ const codecInUse = transceiverId?.publishOption.codec?.name;
5821
+ const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
5824
5822
  let changed = false;
5825
5823
  for (const encoder of params.encodings) {
5826
5824
  const layer = usesSvcCodec
@@ -5878,40 +5876,32 @@ class Publisher extends BasePeerConnection {
5878
5876
  }
5879
5877
  await this.negotiate({ iceRestart: true });
5880
5878
  };
5881
- this.onNegotiationNeeded = () => {
5882
- withCancellation('publisher.negotiate', (signal) => this.negotiate().catch((err) => {
5883
- if (signal.aborted)
5884
- return;
5885
- this.logger('error', `Negotiation failed.`, err);
5886
- this.onUnrecoverableError?.();
5887
- }));
5888
- };
5889
5879
  /**
5890
5880
  * Initiates a new offer/answer exchange with the currently connected SFU.
5891
5881
  *
5892
5882
  * @param options the optional offer options to use.
5893
5883
  */
5894
5884
  this.negotiate = async (options) => {
5895
- const offer = await this.pc.createOffer(options);
5896
- const trackInfos = this.getAnnouncedTracks(offer.sdp);
5897
- if (trackInfos.length === 0) {
5898
- throw new Error(`Can't negotiate without announcing any tracks`);
5899
- }
5900
- try {
5901
- this.isIceRestarting = options?.iceRestart ?? false;
5902
- await this.pc.setLocalDescription(offer);
5903
- const { response } = await this.sfuClient.setPublisher({
5904
- sdp: offer.sdp || '',
5905
- tracks: trackInfos,
5906
- });
5907
- if (response.error)
5908
- throw new Error(response.error.message);
5909
- await this.pc.setRemoteDescription({ type: 'answer', sdp: response.sdp });
5910
- }
5911
- finally {
5912
- this.isIceRestarting = false;
5913
- }
5914
- this.addTrickledIceCandidates();
5885
+ return withoutConcurrency('publisher.negotiate', async () => {
5886
+ const offer = await this.pc.createOffer(options);
5887
+ const tracks = this.getAnnouncedTracks(offer.sdp);
5888
+ if (!tracks.length)
5889
+ throw new Error(`Can't negotiate without any tracks`);
5890
+ try {
5891
+ this.isIceRestarting = options?.iceRestart ?? false;
5892
+ await this.pc.setLocalDescription(offer);
5893
+ const { sdp = '' } = offer;
5894
+ const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
5895
+ if (response.error)
5896
+ throw new Error(response.error.message);
5897
+ const { sdp: answerSdp } = response;
5898
+ await this.pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
5899
+ }
5900
+ finally {
5901
+ this.isIceRestarting = false;
5902
+ }
5903
+ this.addTrickledIceCandidates();
5904
+ });
5915
5905
  };
5916
5906
  /**
5917
5907
  * Returns a list of tracks that are currently being published.
@@ -5995,7 +5985,6 @@ class Publisher extends BasePeerConnection {
5995
5985
  this.clonedTracks.delete(track);
5996
5986
  };
5997
5987
  this.publishOptions = publishOptions;
5998
- this.pc.addEventListener('negotiationneeded', this.onNegotiationNeeded);
5999
5988
  this.on('iceRestart', (iceRestart) => {
6000
5989
  if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
6001
5990
  return;
@@ -6014,17 +6003,6 @@ class Publisher extends BasePeerConnection {
6014
6003
  return this.syncPublishOptions();
6015
6004
  });
6016
6005
  }
6017
- /**
6018
- * Detaches the event handlers from the `RTCPeerConnection`.
6019
- * This is useful when we want to replace the `RTCPeerConnection`
6020
- * instance with a new one (in case of migration).
6021
- */
6022
- detachEventHandlers() {
6023
- super.detachEventHandlers();
6024
- this.pc.removeEventListener('negotiationneeded', this.onNegotiationNeeded);
6025
- // abort any ongoing negotiation
6026
- withCancellation('publisher.negotiate', () => Promise.resolve());
6027
- }
6028
6006
  /**
6029
6007
  * Disposes this Publisher instance.
6030
6008
  */
@@ -7456,7 +7434,7 @@ const aggregate = (stats) => {
7456
7434
  return report;
7457
7435
  };
7458
7436
 
7459
- const version = "1.18.4";
7437
+ const version = "1.18.6";
7460
7438
  const [major, minor, patch] = version.split('.');
7461
7439
  let sdkInfo = {
7462
7440
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8390,6 +8368,7 @@ class BrowserPermission {
8390
8368
  }
8391
8369
  try {
8392
8370
  this.wasPrompted = true;
8371
+ this.setState('prompting');
8393
8372
  const stream = await navigator.mediaDevices.getUserMedia(this.permission.constraints);
8394
8373
  disposeOfMediaStream(stream);
8395
8374
  this.setState('granted');
@@ -8413,6 +8392,7 @@ class BrowserPermission {
8413
8392
  error: e,
8414
8393
  permission: this.permission,
8415
8394
  });
8395
+ this.setState('prompt');
8416
8396
  throw e;
8417
8397
  }
8418
8398
  });
@@ -8424,13 +8404,19 @@ class BrowserPermission {
8424
8404
  return () => this.listeners.delete(cb);
8425
8405
  }
8426
8406
  asObservable() {
8427
- return rxjs.fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten()).pipe(
8407
+ return this.getStateObservable().pipe(
8428
8408
  // In some browsers, the 'change' event doesn't reliably emit and hence,
8429
8409
  // permissionState stays in 'prompt' state forever.
8430
8410
  // Typically, this happens when a user grants one-time permission.
8431
8411
  // Instead of checking if a permission is granted, we check if it isn't denied
8432
8412
  rxjs.map((state) => state !== 'denied'));
8433
8413
  }
8414
+ getIsPromptingObservable() {
8415
+ return this.getStateObservable().pipe(rxjs.map((state) => state === 'prompting'));
8416
+ }
8417
+ getStateObservable() {
8418
+ return rxjs.fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten());
8419
+ }
8434
8420
  setState(state) {
8435
8421
  if (this.state !== state) {
8436
8422
  this.state = state;
@@ -9186,6 +9172,9 @@ class InputMediaDeviceManagerState {
9186
9172
  this.hasBrowserPermission$ = permission
9187
9173
  ? permission.asObservable().pipe(rxjs.shareReplay(1))
9188
9174
  : rxjs.of(true);
9175
+ this.isPromptingPermission$ = permission
9176
+ ? permission.getIsPromptingObservable().pipe(rxjs.shareReplay(1))
9177
+ : rxjs.of(false);
9189
9178
  }
9190
9179
  /**
9191
9180
  * 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}`),