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