@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.es.js CHANGED
@@ -3731,22 +3731,23 @@ const withRequestLogger = (logger, level) => {
3731
3731
  };
3732
3732
  };
3733
3733
  const withRequestTracer = (trace) => {
3734
+ const exclusions = new Set(['SendStats']);
3735
+ const responseInclusions = new Set(['SetPublisher']);
3734
3736
  const traceError = (name, input, err) => trace(`${name}OnFailure`, [err, input]);
3735
- const exclusions = {
3736
- SendStats: true,
3737
- };
3738
3737
  return {
3739
3738
  interceptUnary(next, method, input, options) {
3740
- if (exclusions[method.name]) {
3739
+ const name = method.name;
3740
+ if (exclusions.has(name))
3741
3741
  return next(method, input, options);
3742
- }
3743
- trace(method.name, input);
3742
+ trace(name, input);
3744
3743
  const unaryCall = next(method, input, options);
3745
3744
  unaryCall.then((invocation) => {
3746
- const err = invocation.response?.error;
3747
- if (err)
3748
- traceError(method.name, input, err);
3749
- }, (err) => traceError(method.name, input, err));
3745
+ const response = invocation.response;
3746
+ if (response.error)
3747
+ traceError(name, input, response.error);
3748
+ if (responseInclusions.has(name))
3749
+ trace(`${name}Response`, response);
3750
+ }, (error) => traceError(name, input, error));
3750
3751
  return unaryCall;
3751
3752
  },
3752
3753
  };
@@ -3948,6 +3949,37 @@ const extractMid = (transceiver, transceiverInitIndex, sdp) => {
3948
3949
  return '';
3949
3950
  return String(transceiverInitIndex);
3950
3951
  };
3952
+ /*
3953
+ * Sets the start bitrate for the VP9 and H264 codecs in the SDP.
3954
+ *
3955
+ * @param offerSdp the offer SDP to modify.
3956
+ * @param startBitrate the start bitrate in kbps to set. Default is 1000 kbps.
3957
+ */
3958
+ const setStartBitrate = (offerSdp, maxBitrateKbps, startBitrateFactor, targetMid) => {
3959
+ // start bitrate should be between 300kbps and max-bitrate-kbps
3960
+ const startBitrate = Math.max(Math.min(maxBitrateKbps, startBitrateFactor * maxBitrateKbps), 300);
3961
+ const parsedSdp = parse(offerSdp);
3962
+ const targetCodecs = new Set(['av1', 'vp9', 'h264']);
3963
+ for (const media of parsedSdp.media) {
3964
+ if (media.type !== 'video')
3965
+ continue;
3966
+ if (String(media.mid) !== targetMid)
3967
+ continue;
3968
+ for (const rtp of media.rtp) {
3969
+ if (!targetCodecs.has(rtp.codec.toLowerCase()))
3970
+ continue;
3971
+ for (const fmtp of media.fmtp) {
3972
+ if (fmtp.payload === rtp.payload) {
3973
+ if (!fmtp.config.includes('x-google-start-bitrate')) {
3974
+ fmtp.config += `;x-google-start-bitrate=${startBitrate}`;
3975
+ }
3976
+ break;
3977
+ }
3978
+ }
3979
+ }
3980
+ }
3981
+ return write(parsedSdp);
3982
+ };
3951
3983
  /**
3952
3984
  * Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
3953
3985
  *
@@ -5994,7 +6026,7 @@ const getSdkVersion = (sdk) => {
5994
6026
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5995
6027
  };
5996
6028
 
5997
- const version = "1.38.2";
6029
+ const version = "1.39.2";
5998
6030
  const [major, minor, patch] = version.split('.');
5999
6031
  let sdkInfo = {
6000
6032
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -7822,10 +7854,25 @@ class Publisher extends BasePeerConnection {
7822
7854
  this.isIceRestarting = options?.iceRestart ?? false;
7823
7855
  await this.pc.setLocalDescription(offer);
7824
7856
  const { sdp: baseSdp = '' } = offer;
7825
- const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
7826
- const sdp = dangerouslyForceCodec
7857
+ const { dangerouslyForceCodec, dangerouslySetStartBitrateFactor, fmtpLine, } = this.clientPublishOptions || {};
7858
+ let sdp = dangerouslyForceCodec
7827
7859
  ? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
7828
7860
  : baseSdp;
7861
+ if (dangerouslySetStartBitrateFactor) {
7862
+ this.transceiverCache.items().forEach((t) => {
7863
+ if (t.publishOption.trackType !== TrackType.VIDEO)
7864
+ return;
7865
+ const maxBitrateBps = t.publishOption.bitrate;
7866
+ const trackId = t.transceiver.sender.track?.id;
7867
+ if (!trackId)
7868
+ return;
7869
+ const mid = tracks.find((x) => x.trackId === trackId)?.mid;
7870
+ if (!mid)
7871
+ return;
7872
+ sdp = setStartBitrate(sdp, maxBitrateBps / 1000, // convert to kbps
7873
+ dangerouslySetStartBitrateFactor, mid);
7874
+ });
7875
+ }
7829
7876
  const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
7830
7877
  if (response.error)
7831
7878
  throw new NegotiationError(response.error);
@@ -9931,6 +9978,9 @@ const getDevices = (permission, kind, tracer) => {
9931
9978
  const checkIfAudioOutputChangeSupported = () => {
9932
9979
  if (typeof document === 'undefined')
9933
9980
  return false;
9981
+ // Safari uses WebAudio API for playing audio, so we check the AudioContext prototype
9982
+ if (isSafari())
9983
+ return 'setSinkId' in AudioContext.prototype;
9934
9984
  const element = document.createElement('audio');
9935
9985
  return 'setSinkId' in element;
9936
9986
  };
@@ -14991,7 +15041,7 @@ class StreamClient {
14991
15041
  this.getUserAgent = () => {
14992
15042
  if (!this.cachedUserAgent) {
14993
15043
  const { clientAppIdentifier = {} } = this.options;
14994
- const { sdkName = 'js', sdkVersion = "1.38.2", ...extras } = clientAppIdentifier;
15044
+ const { sdkName = 'js', sdkVersion = "1.39.2", ...extras } = clientAppIdentifier;
14995
15045
  this.cachedUserAgent = [
14996
15046
  `stream-video-${sdkName}-v${sdkVersion}`,
14997
15047
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15195,12 +15245,7 @@ class StreamVideoClient {
15195
15245
  .map((call) => call.cid);
15196
15246
  if (callsToReWatch.length <= 0)
15197
15247
  return;
15198
- this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
15199
- this.queryCalls({
15200
- watch: true,
15201
- filter_conditions: { cid: { $in: callsToReWatch } },
15202
- sort: [{ field: 'cid', direction: 1 }],
15203
- }).catch((err) => {
15248
+ this.rewatchCalls(callsToReWatch).catch((err) => {
15204
15249
  this.logger.error('Failed to re-watch calls', err);
15205
15250
  });
15206
15251
  }));
@@ -15264,6 +15309,34 @@ class StreamVideoClient {
15264
15309
  this.logger.error(`Failed to init call from event ${e.type}`, err);
15265
15310
  }
15266
15311
  };
15312
+ /**
15313
+ * Rewatches the given calls with retry logic.
15314
+ * @param callsToReWatch array of call IDs to rewatch
15315
+ */
15316
+ this.rewatchCalls = async (callsToReWatch) => {
15317
+ this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
15318
+ const maxRetries = 3;
15319
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
15320
+ try {
15321
+ this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')} attempt ${attempt + 1}`);
15322
+ await this.queryCalls({
15323
+ watch: true,
15324
+ filter_conditions: { cid: { $in: callsToReWatch } },
15325
+ });
15326
+ return;
15327
+ }
15328
+ catch (err) {
15329
+ if (err instanceof ErrorFromResponse && err.unrecoverable) {
15330
+ throw err;
15331
+ }
15332
+ this.logger.warn(`Failed to re-watch calls (attempt ${attempt + 1}/${maxRetries}), retrying.`, err);
15333
+ if (attempt === maxRetries - 1) {
15334
+ throw err;
15335
+ }
15336
+ }
15337
+ await sleep(retryInterval(attempt));
15338
+ }
15339
+ };
15267
15340
  /**
15268
15341
  * Connects the given user to the client.
15269
15342
  * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.