@stream-io/video-client 1.38.2 → 1.39.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
@@ -3750,22 +3750,23 @@ const withRequestLogger = (logger, level) => {
3750
3750
  };
3751
3751
  };
3752
3752
  const withRequestTracer = (trace) => {
3753
+ const exclusions = new Set(['SendStats']);
3754
+ const responseInclusions = new Set(['SetPublisher']);
3753
3755
  const traceError = (name, input, err) => trace(`${name}OnFailure`, [err, input]);
3754
- const exclusions = {
3755
- SendStats: true,
3756
- };
3757
3756
  return {
3758
3757
  interceptUnary(next, method, input, options) {
3759
- if (exclusions[method.name]) {
3758
+ const name = method.name;
3759
+ if (exclusions.has(name))
3760
3760
  return next(method, input, options);
3761
- }
3762
- trace(method.name, input);
3761
+ trace(name, input);
3763
3762
  const unaryCall = next(method, input, options);
3764
3763
  unaryCall.then((invocation) => {
3765
- const err = invocation.response?.error;
3766
- if (err)
3767
- traceError(method.name, input, err);
3768
- }, (err) => traceError(method.name, input, err));
3764
+ const response = invocation.response;
3765
+ if (response.error)
3766
+ traceError(name, input, response.error);
3767
+ if (responseInclusions.has(name))
3768
+ trace(`${name}Response`, response);
3769
+ }, (error) => traceError(name, input, error));
3769
3770
  return unaryCall;
3770
3771
  },
3771
3772
  };
@@ -3967,6 +3968,37 @@ const extractMid = (transceiver, transceiverInitIndex, sdp) => {
3967
3968
  return '';
3968
3969
  return String(transceiverInitIndex);
3969
3970
  };
3971
+ /*
3972
+ * Sets the start bitrate for the VP9 and H264 codecs in the SDP.
3973
+ *
3974
+ * @param offerSdp the offer SDP to modify.
3975
+ * @param startBitrate the start bitrate in kbps to set. Default is 1000 kbps.
3976
+ */
3977
+ const setStartBitrate = (offerSdp, maxBitrateKbps, startBitrateFactor, targetMid) => {
3978
+ // start bitrate should be between 300kbps and max-bitrate-kbps
3979
+ const startBitrate = Math.max(Math.min(maxBitrateKbps, startBitrateFactor * maxBitrateKbps), 300);
3980
+ const parsedSdp = sdpTransform.parse(offerSdp);
3981
+ const targetCodecs = new Set(['av1', 'vp9', 'h264']);
3982
+ for (const media of parsedSdp.media) {
3983
+ if (media.type !== 'video')
3984
+ continue;
3985
+ if (String(media.mid) !== targetMid)
3986
+ continue;
3987
+ for (const rtp of media.rtp) {
3988
+ if (!targetCodecs.has(rtp.codec.toLowerCase()))
3989
+ continue;
3990
+ for (const fmtp of media.fmtp) {
3991
+ if (fmtp.payload === rtp.payload) {
3992
+ if (!fmtp.config.includes('x-google-start-bitrate')) {
3993
+ fmtp.config += `;x-google-start-bitrate=${startBitrate}`;
3994
+ }
3995
+ break;
3996
+ }
3997
+ }
3998
+ }
3999
+ }
4000
+ return sdpTransform.write(parsedSdp);
4001
+ };
3970
4002
  /**
3971
4003
  * Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
3972
4004
  *
@@ -6013,7 +6045,7 @@ const getSdkVersion = (sdk) => {
6013
6045
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6014
6046
  };
6015
6047
 
6016
- const version = "1.38.2";
6048
+ const version = "1.39.2";
6017
6049
  const [major, minor, patch] = version.split('.');
6018
6050
  let sdkInfo = {
6019
6051
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -7841,10 +7873,25 @@ class Publisher extends BasePeerConnection {
7841
7873
  this.isIceRestarting = options?.iceRestart ?? false;
7842
7874
  await this.pc.setLocalDescription(offer);
7843
7875
  const { sdp: baseSdp = '' } = offer;
7844
- const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
7845
- const sdp = dangerouslyForceCodec
7876
+ const { dangerouslyForceCodec, dangerouslySetStartBitrateFactor, fmtpLine, } = this.clientPublishOptions || {};
7877
+ let sdp = dangerouslyForceCodec
7846
7878
  ? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
7847
7879
  : baseSdp;
7880
+ if (dangerouslySetStartBitrateFactor) {
7881
+ this.transceiverCache.items().forEach((t) => {
7882
+ if (t.publishOption.trackType !== TrackType.VIDEO)
7883
+ return;
7884
+ const maxBitrateBps = t.publishOption.bitrate;
7885
+ const trackId = t.transceiver.sender.track?.id;
7886
+ if (!trackId)
7887
+ return;
7888
+ const mid = tracks.find((x) => x.trackId === trackId)?.mid;
7889
+ if (!mid)
7890
+ return;
7891
+ sdp = setStartBitrate(sdp, maxBitrateBps / 1000, // convert to kbps
7892
+ dangerouslySetStartBitrateFactor, mid);
7893
+ });
7894
+ }
7848
7895
  const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
7849
7896
  if (response.error)
7850
7897
  throw new NegotiationError(response.error);
@@ -9950,6 +9997,9 @@ const getDevices = (permission, kind, tracer) => {
9950
9997
  const checkIfAudioOutputChangeSupported = () => {
9951
9998
  if (typeof document === 'undefined')
9952
9999
  return false;
10000
+ // Safari uses WebAudio API for playing audio, so we check the AudioContext prototype
10001
+ if (isSafari())
10002
+ return 'setSinkId' in AudioContext.prototype;
9953
10003
  const element = document.createElement('audio');
9954
10004
  return 'setSinkId' in element;
9955
10005
  };
@@ -15010,7 +15060,7 @@ class StreamClient {
15010
15060
  this.getUserAgent = () => {
15011
15061
  if (!this.cachedUserAgent) {
15012
15062
  const { clientAppIdentifier = {} } = this.options;
15013
- const { sdkName = 'js', sdkVersion = "1.38.2", ...extras } = clientAppIdentifier;
15063
+ const { sdkName = 'js', sdkVersion = "1.39.2", ...extras } = clientAppIdentifier;
15014
15064
  this.cachedUserAgent = [
15015
15065
  `stream-video-${sdkName}-v${sdkVersion}`,
15016
15066
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15214,12 +15264,7 @@ class StreamVideoClient {
15214
15264
  .map((call) => call.cid);
15215
15265
  if (callsToReWatch.length <= 0)
15216
15266
  return;
15217
- this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
15218
- this.queryCalls({
15219
- watch: true,
15220
- filter_conditions: { cid: { $in: callsToReWatch } },
15221
- sort: [{ field: 'cid', direction: 1 }],
15222
- }).catch((err) => {
15267
+ this.rewatchCalls(callsToReWatch).catch((err) => {
15223
15268
  this.logger.error('Failed to re-watch calls', err);
15224
15269
  });
15225
15270
  }));
@@ -15283,6 +15328,34 @@ class StreamVideoClient {
15283
15328
  this.logger.error(`Failed to init call from event ${e.type}`, err);
15284
15329
  }
15285
15330
  };
15331
+ /**
15332
+ * Rewatches the given calls with retry logic.
15333
+ * @param callsToReWatch array of call IDs to rewatch
15334
+ */
15335
+ this.rewatchCalls = async (callsToReWatch) => {
15336
+ this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
15337
+ const maxRetries = 3;
15338
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
15339
+ try {
15340
+ this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')} attempt ${attempt + 1}`);
15341
+ await this.queryCalls({
15342
+ watch: true,
15343
+ filter_conditions: { cid: { $in: callsToReWatch } },
15344
+ });
15345
+ return;
15346
+ }
15347
+ catch (err) {
15348
+ if (err instanceof ErrorFromResponse && err.unrecoverable) {
15349
+ throw err;
15350
+ }
15351
+ this.logger.warn(`Failed to re-watch calls (attempt ${attempt + 1}/${maxRetries}), retrying.`, err);
15352
+ if (attempt === maxRetries - 1) {
15353
+ throw err;
15354
+ }
15355
+ }
15356
+ await sleep(retryInterval(attempt));
15357
+ }
15358
+ };
15286
15359
  /**
15287
15360
  * Connects the given user to the client.
15288
15361
  * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.