@stream-io/video-client 1.36.0 → 1.37.0

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,16 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.37.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.36.1...@stream-io/video-client-1.37.0) (2025-11-14)
6
+
7
+ ### Features
8
+
9
+ - ring individual members ([#1755](https://github.com/GetStream/stream-video-js/issues/1755)) ([57564d6](https://github.com/GetStream/stream-video-js/commit/57564d63f21da7b95b582f74c88b24af7e77659c))
10
+
11
+ ## [1.36.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.36.0...@stream-io/video-client-1.36.1) (2025-11-12)
12
+
13
+ - enforce the client to publish options on SDP level ([#1976](https://github.com/GetStream/stream-video-js/issues/1976)) ([1d93f72](https://github.com/GetStream/stream-video-js/commit/1d93f72cb4395aaf9b487eb66e0c3b6a8111aca4))
14
+
5
15
  ## [1.36.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.35.1...@stream-io/video-client-1.36.0) (2025-10-30)
6
16
 
7
17
  ### Features
@@ -6,9 +6,9 @@ export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
7
7
  import * as scopedLogger from '@stream-io/logger';
8
8
  export { LogLevelEnum } from '@stream-io/logger';
9
+ import { parse, write } from 'sdp-transform';
9
10
  import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
10
11
  import { UAParser } from 'ua-parser-js';
11
- import { parse, write } from 'sdp-transform';
12
12
  import { WorkerTimer } from '@stream-io/worker-timer';
13
13
 
14
14
  /* tslint:disable */
@@ -52,7 +52,6 @@ const FrameRecordingSettingsRequestQualityEnum = {
52
52
  _720P: '720p',
53
53
  _1080P: '1080p',
54
54
  _1440P: '1440p',
55
- _2160P: '2160p',
56
55
  };
57
56
  /**
58
57
  * @export
@@ -148,13 +147,11 @@ const RTMPBroadcastRequestQualityEnum = {
148
147
  _720P: '720p',
149
148
  _1080P: '1080p',
150
149
  _1440P: '1440p',
151
- _2160P: '2160p',
152
150
  PORTRAIT_360X640: 'portrait-360x640',
153
151
  PORTRAIT_480X854: 'portrait-480x854',
154
152
  PORTRAIT_720X1280: 'portrait-720x1280',
155
153
  PORTRAIT_1080X1920: 'portrait-1080x1920',
156
154
  PORTRAIT_1440X2560: 'portrait-1440x2560',
157
- PORTRAIT_2160X3840: 'portrait-2160x3840',
158
155
  };
159
156
  /**
160
157
  * @export
@@ -165,13 +162,11 @@ const RTMPSettingsRequestQualityEnum = {
165
162
  _720P: '720p',
166
163
  _1080P: '1080p',
167
164
  _1440P: '1440p',
168
- _2160P: '2160p',
169
165
  PORTRAIT_360X640: 'portrait-360x640',
170
166
  PORTRAIT_480X854: 'portrait-480x854',
171
167
  PORTRAIT_720X1280: 'portrait-720x1280',
172
168
  PORTRAIT_1080X1920: 'portrait-1080x1920',
173
169
  PORTRAIT_1440X2560: 'portrait-1440x2560',
174
- PORTRAIT_2160X3840: 'portrait-2160x3840',
175
170
  };
176
171
  /**
177
172
  * @export
@@ -190,13 +185,11 @@ const RecordSettingsRequestQualityEnum = {
190
185
  _720P: '720p',
191
186
  _1080P: '1080p',
192
187
  _1440P: '1440p',
193
- _2160P: '2160p',
194
188
  PORTRAIT_360X640: 'portrait-360x640',
195
189
  PORTRAIT_480X854: 'portrait-480x854',
196
190
  PORTRAIT_720X1280: 'portrait-720x1280',
197
191
  PORTRAIT_1080X1920: 'portrait-1080x1920',
198
192
  PORTRAIT_1440X2560: 'portrait-1440x2560',
199
- PORTRAIT_2160X3840: 'portrait-2160x3840',
200
193
  };
201
194
  /**
202
195
  * @export
@@ -3929,19 +3922,158 @@ const retryable = async (rpc, signal) => {
3929
3922
  return result;
3930
3923
  };
3931
3924
 
3925
+ /**
3926
+ * Extracts the mid from the transceiver or the SDP.
3927
+ *
3928
+ * @param transceiver the transceiver.
3929
+ * @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
3930
+ * @param sdp the SDP.
3931
+ */
3932
+ const extractMid = (transceiver, transceiverInitIndex, sdp) => {
3933
+ if (transceiver.mid)
3934
+ return transceiver.mid;
3935
+ if (!sdp)
3936
+ return String(transceiverInitIndex);
3937
+ const track = transceiver.sender.track;
3938
+ const parsedSdp = parse(sdp);
3939
+ const media = parsedSdp.media.find((m) => {
3940
+ return (m.type === track.kind &&
3941
+ // if `msid` is not present, we assume that the track is the first one
3942
+ (m.msid?.includes(track.id) ?? true));
3943
+ });
3944
+ if (typeof media?.mid !== 'undefined')
3945
+ return String(media.mid);
3946
+ if (transceiverInitIndex < 0)
3947
+ return '';
3948
+ return String(transceiverInitIndex);
3949
+ };
3950
+ /**
3951
+ * Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
3952
+ *
3953
+ * @param offerSdp the offer SDP containing the stereo configuration.
3954
+ * @param answerSdp the answer SDP to be modified.
3955
+ */
3956
+ const enableStereo = (offerSdp, answerSdp) => {
3957
+ const offeredStereoMids = new Set();
3958
+ const parsedOfferSdp = parse(offerSdp);
3959
+ for (const media of parsedOfferSdp.media) {
3960
+ if (media.type !== 'audio')
3961
+ continue;
3962
+ const opus = media.rtp.find((r) => r.codec === 'opus');
3963
+ if (!opus)
3964
+ continue;
3965
+ for (const fmtp of media.fmtp) {
3966
+ if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
3967
+ offeredStereoMids.add(media.mid);
3968
+ }
3969
+ }
3970
+ }
3971
+ // No stereo offered, return the original answerSdp
3972
+ if (offeredStereoMids.size === 0)
3973
+ return answerSdp;
3974
+ const parsedAnswerSdp = parse(answerSdp);
3975
+ for (const media of parsedAnswerSdp.media) {
3976
+ if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
3977
+ continue;
3978
+ const opus = media.rtp.find((r) => r.codec === 'opus');
3979
+ if (!opus)
3980
+ continue;
3981
+ for (const fmtp of media.fmtp) {
3982
+ if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
3983
+ fmtp.config += ';stereo=1';
3984
+ }
3985
+ }
3986
+ }
3987
+ return write(parsedAnswerSdp);
3988
+ };
3989
+ /**
3990
+ * Removes all codecs from the SDP except the specified codec.
3991
+ *
3992
+ * @param sdp the SDP to modify.
3993
+ * @param codecMimeTypeToKeep the codec mime type to keep (video/h264 or audio/opus).
3994
+ * @param fmtpProfileToKeep the fmtp profile to keep (e.g. 'profile-level-id=42e01f' or multiple segments like 'profile-level-id=64001f;packetization-mode=1').
3995
+ */
3996
+ const removeCodecsExcept = (sdp, codecMimeTypeToKeep, fmtpProfileToKeep) => {
3997
+ const [kind, codec] = toMimeType(codecMimeTypeToKeep).split('/');
3998
+ if (!kind || !codec)
3999
+ return sdp;
4000
+ const parsed = parse(sdp);
4001
+ for (const media of parsed.media) {
4002
+ if (media.type !== kind)
4003
+ continue;
4004
+ // Build a set of payloads to KEEP: all payloads whose rtp.codec matches codec
4005
+ let payloadsToKeep = new Set();
4006
+ for (const rtp of media.rtp) {
4007
+ if (rtp.codec.toLowerCase() !== codec)
4008
+ continue;
4009
+ payloadsToKeep.add(rtp.payload);
4010
+ }
4011
+ // If a specific fmtp profile is requested, only keep payloads whose fmtp config matches it
4012
+ if (fmtpProfileToKeep) {
4013
+ const filtered = new Set();
4014
+ const required = new Set(fmtpProfileToKeep.split(';'));
4015
+ for (const fmtp of media.fmtp) {
4016
+ if (payloadsToKeep.has(fmtp.payload) &&
4017
+ required.difference(new Set(fmtp.config.split(';'))).size === 0) {
4018
+ filtered.add(fmtp.payload);
4019
+ }
4020
+ }
4021
+ payloadsToKeep = filtered;
4022
+ }
4023
+ // If no payloads to keep AND no fmtpProfile was specified, skip modifications (preserve SDP as-is)
4024
+ if (payloadsToKeep.size === 0 && !fmtpProfileToKeep)
4025
+ continue;
4026
+ // Keep RTX payloads that are associated with kept primary payloads via apt
4027
+ // RTX mappings look like: a=fmtp:<rtxPayload> apt=<primaryPayload>
4028
+ for (const fmtp of media.fmtp) {
4029
+ const matches = /\s*apt\s*=\s*(\d+)\s*/i.exec(fmtp.config);
4030
+ if (!matches)
4031
+ continue;
4032
+ const primaryPayloadApt = Number(matches[1]);
4033
+ if (!payloadsToKeep.has(primaryPayloadApt))
4034
+ continue;
4035
+ payloadsToKeep.add(fmtp.payload);
4036
+ }
4037
+ // Filter rtp, fmtp and rtcpFb entries
4038
+ media.rtp = media.rtp.filter((rtp) => payloadsToKeep.has(rtp.payload));
4039
+ media.fmtp = media.fmtp.filter((fmtp) => payloadsToKeep.has(fmtp.payload));
4040
+ media.rtcpFb = media.rtcpFb?.filter((fb) => typeof fb.payload === 'number' ? payloadsToKeep.has(fb.payload) : true);
4041
+ // Update the m= line payload list to only the kept payloads, preserving original order
4042
+ const payloads = [];
4043
+ for (const id of (media.payloads || '').split(/\s+/)) {
4044
+ const payload = Number(id);
4045
+ if (!payloadsToKeep.has(payload))
4046
+ continue;
4047
+ payloads.push(payload);
4048
+ }
4049
+ media.payloads = payloads.join(' ');
4050
+ }
4051
+ return write(parsed);
4052
+ };
4053
+ /**
4054
+ * Converts the given codec to a mime-type format when necessary.
4055
+ * e.g.: `vp9` -> `video/vp9`
4056
+ */
4057
+ const toMimeType = (codec, kind = 'video') => codec.includes('/') ? codec : `${kind}/${codec}`;
4058
+
3932
4059
  /**
3933
4060
  * Returns a generic SDP for the given direction.
3934
4061
  * We use this SDP to send it as part of our JoinRequest so that the SFU
3935
4062
  * can use it to determine the client's codec capabilities.
3936
4063
  *
3937
4064
  * @param direction the direction of the transceiver.
4065
+ * @param codecToKeep the codec mime type to keep (video/h264 or audio/opus).
4066
+ * @param fmtpProfileToKeep optional fmtp profile to keep.
3938
4067
  */
3939
- const getGenericSdp = async (direction) => {
4068
+ const getGenericSdp = async (direction, codecToKeep, fmtpProfileToKeep) => {
3940
4069
  const tempPc = new RTCPeerConnection();
3941
4070
  tempPc.addTransceiver('video', { direction });
3942
4071
  tempPc.addTransceiver('audio', { direction });
3943
4072
  const offer = await tempPc.createOffer();
3944
- const sdp = offer.sdp ?? '';
4073
+ const { sdp: baseSdp = '' } = offer;
4074
+ const sdp = codecToKeep
4075
+ ? removeCodecsExcept(baseSdp, codecToKeep, fmtpProfileToKeep)
4076
+ : baseSdp;
3945
4077
  tempPc.getTransceivers().forEach((t) => {
3946
4078
  t.stop?.();
3947
4079
  });
@@ -5837,7 +5969,7 @@ const getSdkVersion = (sdk) => {
5837
5969
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5838
5970
  };
5839
5971
 
5840
- const version = "1.36.0";
5972
+ const version = "1.37.0";
5841
5973
  const [major, minor, patch] = version.split('.');
5842
5974
  let sdkInfo = {
5843
5975
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -6758,7 +6890,7 @@ class BasePeerConnection {
6758
6890
  /**
6759
6891
  * Constructs a new `BasePeerConnection` instance.
6760
6892
  */
6761
- constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, iceRestartDelay = 2500, }) {
6893
+ constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, }) {
6762
6894
  this.isIceRestarting = false;
6763
6895
  this.isDisposed = false;
6764
6896
  this.trackIdToTrackType = new Map();
@@ -6787,7 +6919,7 @@ class BasePeerConnection {
6787
6919
  e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
6788
6920
  ? WebsocketReconnectStrategy.FAST
6789
6921
  : WebsocketReconnectStrategy.REJOIN;
6790
- this.onReconnectionNeeded?.(strategy, reason);
6922
+ this.onReconnectionNeeded?.(strategy, reason, this.peerType);
6791
6923
  });
6792
6924
  };
6793
6925
  /**
@@ -6903,7 +7035,7 @@ class BasePeerConnection {
6903
7035
  }
6904
7036
  // we can't recover from a failed connection state (contrary to ICE)
6905
7037
  if (state === 'failed') {
6906
- this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed');
7038
+ this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed', this.peerType);
6907
7039
  return;
6908
7040
  }
6909
7041
  this.handleConnectionStateUpdate(state);
@@ -6979,6 +7111,7 @@ class BasePeerConnection {
6979
7111
  this.state = state;
6980
7112
  this.dispatcher = dispatcher;
6981
7113
  this.iceRestartDelay = iceRestartDelay;
7114
+ this.clientPublishOptions = clientPublishOptions;
6982
7115
  this.onReconnectionNeeded = onReconnectionNeeded;
6983
7116
  this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
6984
7117
  this.pc = this.createPeerConnection(connectionConfig);
@@ -7313,71 +7446,6 @@ const withSimulcastConstraints = (width, height, optimalVideoLayers, useSingleLa
7313
7446
  }));
7314
7447
  };
7315
7448
 
7316
- /**
7317
- * Extracts the mid from the transceiver or the SDP.
7318
- *
7319
- * @param transceiver the transceiver.
7320
- * @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
7321
- * @param sdp the SDP.
7322
- */
7323
- const extractMid = (transceiver, transceiverInitIndex, sdp) => {
7324
- if (transceiver.mid)
7325
- return transceiver.mid;
7326
- if (!sdp)
7327
- return String(transceiverInitIndex);
7328
- const track = transceiver.sender.track;
7329
- const parsedSdp = parse(sdp);
7330
- const media = parsedSdp.media.find((m) => {
7331
- return (m.type === track.kind &&
7332
- // if `msid` is not present, we assume that the track is the first one
7333
- (m.msid?.includes(track.id) ?? true));
7334
- });
7335
- if (typeof media?.mid !== 'undefined')
7336
- return String(media.mid);
7337
- if (transceiverInitIndex < 0)
7338
- return '';
7339
- return String(transceiverInitIndex);
7340
- };
7341
- /**
7342
- * Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
7343
- *
7344
- * @param offerSdp the offer SDP containing the stereo configuration.
7345
- * @param answerSdp the answer SDP to be modified.
7346
- */
7347
- const enableStereo = (offerSdp, answerSdp) => {
7348
- const offeredStereoMids = new Set();
7349
- const parsedOfferSdp = parse(offerSdp);
7350
- for (const media of parsedOfferSdp.media) {
7351
- if (media.type !== 'audio')
7352
- continue;
7353
- const opus = media.rtp.find((r) => r.codec === 'opus');
7354
- if (!opus)
7355
- continue;
7356
- for (const fmtp of media.fmtp) {
7357
- if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
7358
- offeredStereoMids.add(media.mid);
7359
- }
7360
- }
7361
- }
7362
- // No stereo offered, return the original answerSdp
7363
- if (offeredStereoMids.size === 0)
7364
- return answerSdp;
7365
- const parsedAnswerSdp = parse(answerSdp);
7366
- for (const media of parsedAnswerSdp.media) {
7367
- if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
7368
- continue;
7369
- const opus = media.rtp.find((r) => r.codec === 'opus');
7370
- if (!opus)
7371
- continue;
7372
- for (const fmtp of media.fmtp) {
7373
- if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
7374
- fmtp.config += ';stereo=1';
7375
- }
7376
- }
7377
- }
7378
- return write(parsedAnswerSdp);
7379
- };
7380
-
7381
7449
  /**
7382
7450
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
7383
7451
  *
@@ -7387,7 +7455,7 @@ class Publisher extends BasePeerConnection {
7387
7455
  /**
7388
7456
  * Constructs a new `Publisher` instance.
7389
7457
  */
7390
- constructor({ publishOptions, ...baseOptions }) {
7458
+ constructor(baseOptions, publishOptions) {
7391
7459
  super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
7392
7460
  this.transceiverCache = new TransceiverCache();
7393
7461
  this.clonedTracks = new Set();
@@ -7646,7 +7714,11 @@ class Publisher extends BasePeerConnection {
7646
7714
  try {
7647
7715
  this.isIceRestarting = options?.iceRestart ?? false;
7648
7716
  await this.pc.setLocalDescription(offer);
7649
- const { sdp = '' } = offer;
7717
+ const { sdp: baseSdp = '' } = offer;
7718
+ const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
7719
+ const sdp = dangerouslyForceCodec
7720
+ ? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
7721
+ : baseSdp;
7650
7722
  const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
7651
7723
  if (response.error)
7652
7724
  throw new NegotiationError(response.error);
@@ -7876,6 +7948,10 @@ class Subscriber extends BasePeerConnection {
7876
7948
  const answer = await this.pc.createAnswer();
7877
7949
  if (answer.sdp) {
7878
7950
  answer.sdp = enableStereo(subscriberOffer.sdp, answer.sdp);
7951
+ const { dangerouslyForceCodec, subscriberFmtpLine } = this.clientPublishOptions || {};
7952
+ if (dangerouslyForceCodec) {
7953
+ answer.sdp = removeCodecsExcept(answer.sdp, dangerouslyForceCodec, subscriberFmtpLine);
7954
+ }
7879
7955
  }
7880
7956
  await this.pc.setLocalDescription(answer);
7881
7957
  await this.sfuClient.sendAnswer({
@@ -12022,6 +12098,7 @@ class Call {
12022
12098
  * @param params.ring if set to true, a `call.ring` event will be sent to the call members.
12023
12099
  * @param params.notify if set to true, a `call.notification` event will be sent to the call members.
12024
12100
  * @param params.members_limit the total number of members to return as part of the response.
12101
+ * @param params.video if set to true, in a ringing scenario, mobile SDKs will show "incoming video call", audio only otherwise.
12025
12102
  */
12026
12103
  this.get = async (params) => {
12027
12104
  await this.setup();
@@ -12075,11 +12152,11 @@ class Call {
12075
12152
  return this.streamClient.post(`${this.streamClientBasePath}/delete`, data);
12076
12153
  };
12077
12154
  /**
12078
- * A shortcut for {@link Call.get} with `ring` parameter set to `true`.
12079
- * Will send a `call.ring` event to the call members.
12155
+ * Sends a ring notification to the provided users who are not already in the call.
12156
+ * All users should be members of the call.
12080
12157
  */
12081
- this.ring = async () => {
12082
- return await this.get({ ring: true });
12158
+ this.ring = async (data = {}) => {
12159
+ return this.streamClient.post(`${this.streamClientBasePath}/ring`, data);
12083
12160
  };
12084
12161
  /**
12085
12162
  * A shortcut for {@link Call.get} with `notify` parameter set to `true`.
@@ -12216,9 +12293,10 @@ class Call {
12216
12293
  // prepare a generic SDP and send it to the SFU.
12217
12294
  // these are throw-away SDPs that the SFU will use to determine
12218
12295
  // the capabilities of the client (codec support, etc.)
12296
+ const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } = this.clientPublishOptions || {};
12219
12297
  const [subscriberSdp, publisherSdp] = await Promise.all([
12220
- getGenericSdp('recvonly'),
12221
- getGenericSdp('sendonly'),
12298
+ getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
12299
+ getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
12222
12300
  ]);
12223
12301
  const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
12224
12302
  const reconnectDetails = isReconnecting
@@ -12410,20 +12488,22 @@ class Call {
12410
12488
  if (closePreviousInstances && this.subscriber) {
12411
12489
  this.subscriber.dispose();
12412
12490
  }
12413
- this.subscriber = new Subscriber({
12491
+ const basePeerConnectionOptions = {
12414
12492
  sfuClient,
12415
12493
  dispatcher: this.dispatcher,
12416
12494
  state: this.state,
12417
12495
  connectionConfig,
12418
12496
  tag: sfuClient.tag,
12419
12497
  enableTracing,
12420
- onReconnectionNeeded: (kind, reason) => {
12498
+ clientPublishOptions: this.clientPublishOptions,
12499
+ onReconnectionNeeded: (kind, reason, peerType) => {
12421
12500
  this.reconnect(kind, reason).catch((err) => {
12422
- const message = `[Reconnect] Error reconnecting after a subscriber error: ${reason}`;
12501
+ const message = `[Reconnect] Error reconnecting, after a ${PeerType[peerType]} error: ${reason}`;
12423
12502
  this.logger.warn(message, err);
12424
12503
  });
12425
12504
  },
12426
- });
12505
+ };
12506
+ this.subscriber = new Subscriber(basePeerConnectionOptions);
12427
12507
  // anonymous users can't publish anything hence, there is no need
12428
12508
  // to create Publisher Peer Connection for them
12429
12509
  const isAnonymous = this.streamClient.user?.type === 'anonymous';
@@ -12431,21 +12511,7 @@ class Call {
12431
12511
  if (closePreviousInstances && this.publisher) {
12432
12512
  this.publisher.dispose();
12433
12513
  }
12434
- this.publisher = new Publisher({
12435
- sfuClient,
12436
- dispatcher: this.dispatcher,
12437
- state: this.state,
12438
- connectionConfig,
12439
- publishOptions,
12440
- tag: sfuClient.tag,
12441
- enableTracing,
12442
- onReconnectionNeeded: (kind, reason) => {
12443
- this.reconnect(kind, reason).catch((err) => {
12444
- const message = `[Reconnect] Error reconnecting after a publisher error: ${reason}`;
12445
- this.logger.warn(message, err);
12446
- });
12447
- },
12448
- });
12514
+ this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
12449
12515
  }
12450
12516
  this.statsReporter?.stop();
12451
12517
  if (this.statsReportingIntervalInMs > 0) {
@@ -14807,7 +14873,7 @@ class StreamClient {
14807
14873
  this.getUserAgent = () => {
14808
14874
  if (!this.cachedUserAgent) {
14809
14875
  const { clientAppIdentifier = {} } = this.options;
14810
- const { sdkName = 'js', sdkVersion = "1.36.0", ...extras } = clientAppIdentifier;
14876
+ const { sdkName = 'js', sdkVersion = "1.37.0", ...extras } = clientAppIdentifier;
14811
14877
  this.cachedUserAgent = [
14812
14878
  `stream-video-${sdkName}-v${sdkVersion}`,
14813
14879
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15027,10 +15093,6 @@ class StreamVideoClient {
15027
15093
  * @param e the event.
15028
15094
  */
15029
15095
  this.initCallFromEvent = async (e) => {
15030
- if (this.state.connectedUser?.id === e.call.created_by.id) {
15031
- this.logger.debug(`Ignoring ${e.type} event sent by the current user`);
15032
- return;
15033
- }
15034
15096
  try {
15035
15097
  const concurrencyTag = getCallInitConcurrencyTag(e.call_cid);
15036
15098
  await withoutConcurrency(concurrencyTag, async () => {
@@ -15344,12 +15406,12 @@ class StreamVideoClient {
15344
15406
  this.shouldRejectCall = (currentCallId) => {
15345
15407
  if (!this.rejectCallWhenBusy)
15346
15408
  return false;
15347
- const hasOngoingRingingCall = this.state.calls.some((c) => c.cid !== currentCallId &&
15409
+ return this.state.calls.some((c) => c.cid !== currentCallId &&
15348
15410
  c.ringing &&
15411
+ !c.isCreatedByMe &&
15349
15412
  c.state.callingState !== CallingState.IDLE &&
15350
15413
  c.state.callingState !== CallingState.LEFT &&
15351
15414
  c.state.callingState !== CallingState.RECONNECTING_FAILED);
15352
- return hasOngoingRingingCall;
15353
15415
  };
15354
15416
  const apiKey = typeof apiKeyOrArgs === 'string' ? apiKeyOrArgs : apiKeyOrArgs.apiKey;
15355
15417
  const clientOptions = typeof apiKeyOrArgs === 'string' ? opts : apiKeyOrArgs.options;