@stream-io/video-client 1.44.1-beta.1 → 1.44.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,18 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.44.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.44.1...@stream-io/video-client-1.44.2) (2026-03-06)
6
+
7
+ ### Bug Fixes
8
+
9
+ - do not setup speaker early for ringing type calls ([#2154](https://github.com/GetStream/stream-video-js/issues/2154)) ([57adb90](https://github.com/GetStream/stream-video-js/commit/57adb90f03cfaceb4e6d3c050feaea239b80b1d9))
10
+
11
+ ## [1.44.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.44.0...@stream-io/video-client-1.44.1) (2026-03-04)
12
+
13
+ ### Bug Fixes
14
+
15
+ - **client:** handle SFU tag changes during reconnect ([#2149](https://github.com/GetStream/stream-video-js/issues/2149)) ([5aa89d3](https://github.com/GetStream/stream-video-js/commit/5aa89d378a73d33d8e46a6eb40e688bd0f50cca9)), closes [#2121](https://github.com/GetStream/stream-video-js/issues/2121)
16
+
5
17
  ## [1.44.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.43.0...@stream-io/video-client-1.44.0) (2026-02-27)
6
18
 
7
19
  - update agent instructions [skip ci] ([9cec4c6](https://github.com/GetStream/stream-video-js/commit/9cec4c6431ff51549fcfc870a0df935b0b8aa850))
@@ -4283,7 +4283,7 @@ const isSfuEvent = (eventName) => {
4283
4283
  class Dispatcher {
4284
4284
  constructor() {
4285
4285
  this.logger = videoLoggerSystem.getLogger('Dispatcher');
4286
- this.subscribers = {};
4286
+ this.subscribers = new Map();
4287
4287
  /**
4288
4288
  * Dispatch an event to all subscribers.
4289
4289
  *
@@ -4296,12 +4296,14 @@ class Dispatcher {
4296
4296
  return;
4297
4297
  const payload = message.eventPayload[eventKind];
4298
4298
  this.logger.debug(`Dispatching ${eventKind}, tag=${tag}`, payload);
4299
- const handlers = this.subscribers[eventKind];
4299
+ const handlers = this.subscribers.get(eventKind);
4300
4300
  if (!handlers)
4301
4301
  return;
4302
- this.emit(payload, handlers[tag]);
4302
+ const { byTag, dynamic } = handlers;
4303
+ this.emit(payload, byTag.get(tag));
4303
4304
  if (tag !== '*')
4304
- this.emit(payload, handlers['*']);
4305
+ this.emit(payload, byTag.get('*'));
4306
+ this.emitDynamic(payload, tag, dynamic);
4305
4307
  };
4306
4308
  /**
4307
4309
  * Emit an event to a list of listeners.
@@ -4311,26 +4313,54 @@ class Dispatcher {
4311
4313
  */
4312
4314
  this.emit = (payload, listeners = []) => {
4313
4315
  for (const listener of listeners) {
4314
- try {
4315
- listener(payload);
4316
- }
4317
- catch (e) {
4318
- this.logger.warn('Listener failed with error', e);
4316
+ this.emitOne(payload, listener);
4317
+ }
4318
+ };
4319
+ /**
4320
+ * Emit an event to a list of listeners.
4321
+ *
4322
+ */
4323
+ this.emitDynamic = (payload, tag, dynamic) => {
4324
+ for (const { tagSelector, listener } of dynamic) {
4325
+ const dynamicTag = tagSelector();
4326
+ if (dynamicTag === tag || (tag !== '*' && dynamicTag === '*')) {
4327
+ this.emitOne(payload, listener);
4319
4328
  }
4320
4329
  }
4321
4330
  };
4331
+ /**
4332
+ * Emit an event to a single listener.
4333
+ * @param payload the event payload to emit.
4334
+ * @param listener the listener to emit the event to.
4335
+ */
4336
+ this.emitOne = (payload, listener) => {
4337
+ try {
4338
+ listener(payload);
4339
+ }
4340
+ catch (e) {
4341
+ this.logger.warn('Listener failed with error', e);
4342
+ }
4343
+ };
4322
4344
  /**
4323
4345
  * Subscribe to an event.
4324
4346
  *
4325
4347
  * @param eventName the name of the event to subscribe to.
4326
- * @param tag for scoping events to a specific tag. Use `*` dispatch to every tag.
4348
+ * @param tag for scoping events to a specific tag. Can be a static tag
4349
+ * string or a function that resolves the tag dynamically.
4327
4350
  * @param fn the callback function to invoke when the event is emitted.
4328
4351
  * @returns a function that can be called to unsubscribe from the event.
4329
4352
  */
4330
4353
  this.on = (eventName, tag, fn) => {
4331
- var _a;
4332
- const bucket = ((_a = this.subscribers)[eventName] ?? (_a[eventName] = {}));
4333
- (bucket[tag] ?? (bucket[tag] = [])).push(fn);
4354
+ const { byTag, dynamic } = this.getHandlers(eventName);
4355
+ const listener = fn;
4356
+ if (typeof tag === 'string') {
4357
+ const listeners = byTag.get(tag) ?? [];
4358
+ listeners.push(listener);
4359
+ byTag.set(tag, listeners);
4360
+ }
4361
+ else {
4362
+ dynamic.push({ tagSelector: tag, listener });
4363
+ }
4334
4364
  return () => {
4335
4365
  this.off(eventName, tag, fn);
4336
4366
  };
@@ -4339,15 +4369,35 @@ class Dispatcher {
4339
4369
  * Unsubscribe from an event.
4340
4370
  *
4341
4371
  * @param eventName the name of the event to unsubscribe from.
4342
- * @param tag for scoping events to a specific tag. Use `*` dispatch to every tag.
4372
+ * @param tag the original static/dynamic tag selector used during subscription.
4343
4373
  * @param fn the callback function to remove from the event listeners.
4344
4374
  */
4345
4375
  this.off = (eventName, tag, fn) => {
4346
- const bucket = this.subscribers[eventName];
4347
- const listeners = bucket?.[tag];
4348
- if (!listeners)
4376
+ const bucket = this.subscribers.get(eventName);
4377
+ if (!bucket)
4349
4378
  return;
4350
- bucket[tag] = listeners.filter((f) => f !== fn);
4379
+ const { byTag, dynamic } = bucket;
4380
+ if (typeof tag === 'string') {
4381
+ const listeners = byTag.get(tag) || [];
4382
+ const idx = listeners.indexOf(fn);
4383
+ if (idx >= 0)
4384
+ listeners.splice(idx, 1);
4385
+ }
4386
+ else {
4387
+ const idx = dynamic.findIndex(({ tagSelector, listener }) => {
4388
+ return tagSelector === tag && listener === fn;
4389
+ });
4390
+ if (idx >= 0)
4391
+ dynamic.splice(idx, 1);
4392
+ }
4393
+ };
4394
+ this.getHandlers = (eventName) => {
4395
+ const existing = this.subscribers.get(eventName);
4396
+ if (existing)
4397
+ return existing;
4398
+ const next = { byTag: new Map(), dynamic: [] };
4399
+ this.subscribers.set(eventName, next);
4400
+ return next;
4351
4401
  };
4352
4402
  }
4353
4403
  }
@@ -6231,7 +6281,7 @@ const getSdkVersion = (sdk) => {
6231
6281
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6232
6282
  };
6233
6283
 
6234
- const version = "1.44.1-beta.1";
6284
+ const version = "1.44.2";
6235
6285
  const [major, minor, patch] = version.split('.');
6236
6286
  let sdkInfo = {
6237
6287
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -7298,7 +7348,8 @@ class BasePeerConnection {
7298
7348
  * Consecutive events are queued and executed one after the other.
7299
7349
  */
7300
7350
  this.on = (event, fn) => {
7301
- this.subscriptions.push(this.dispatcher.on(event, this.tag, (e) => {
7351
+ const getTag = () => this.tag;
7352
+ this.subscriptions.push(this.dispatcher.on(event, getTag, (e) => {
7302
7353
  const lockKey = `pc.${this.lock}.${event}`;
7303
7354
  withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
7304
7355
  if (this.isDisposed)
@@ -7331,6 +7382,7 @@ class BasePeerConnection {
7331
7382
  */
7332
7383
  this.setSfuClient = (sfuClient) => {
7333
7384
  this.sfuClient = sfuClient;
7385
+ this.tag = sfuClient.tag;
7334
7386
  };
7335
7387
  /**
7336
7388
  * Returns the result of the `RTCPeerConnection.getStats()` method
@@ -7521,6 +7573,7 @@ class BasePeerConnection {
7521
7573
  pc.removeEventListener('icegatheringstatechange', this.onIceGatherChange);
7522
7574
  this.unsubscribeIceTrickle?.();
7523
7575
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
7576
+ this.subscriptions = [];
7524
7577
  }
7525
7578
  }
7526
7579
 
@@ -12944,7 +12997,7 @@ class Call {
12944
12997
  // const calls = useCalls().filter((c) => c.ringing);
12945
12998
  const calls = this.clientStore.calls.filter((c) => c.cid !== this.cid);
12946
12999
  this.clientStore.setCalls([this, ...calls]);
12947
- const skipSpeakerApply = isReactNative() && true;
13000
+ const skipSpeakerApply = isReactNative();
12948
13001
  await this.applyDeviceConfig(settings, false, skipSpeakerApply);
12949
13002
  };
12950
13003
  /**
@@ -12968,8 +13021,11 @@ class Call {
12968
13021
  this.watching = true;
12969
13022
  this.clientStore.registerOrUpdateCall(this);
12970
13023
  }
13024
+ // Skip speaker setup on RN if ringing was requested or the call is already ringing
12971
13025
  const skipSpeakerApply = isReactNative()
12972
- ? (params?.ring ?? this.ringing)
13026
+ ? params?.ring === true
13027
+ ? true
13028
+ : this.ringing
12973
13029
  : false;
12974
13030
  await this.applyDeviceConfig(response.call.settings, false, skipSpeakerApply);
12975
13031
  return response;
@@ -12992,8 +13048,11 @@ class Call {
12992
13048
  this.watching = true;
12993
13049
  this.clientStore.registerOrUpdateCall(this);
12994
13050
  }
13051
+ // Skip speaker setup on RN if ringing was requested or the call is already ringing
12995
13052
  const skipSpeakerApply = isReactNative()
12996
- ? (data?.ring ?? this.ringing)
13053
+ ? data?.ring === true
13054
+ ? true
13055
+ : this.ringing
12997
13056
  : false;
12998
13057
  await this.applyDeviceConfig(response.call.settings, false, skipSpeakerApply);
12999
13058
  return response;
@@ -13250,7 +13309,7 @@ class Call {
13250
13309
  // device settings should be applied only once, we don't have to
13251
13310
  // re-apply them on later reconnections or server-side data fetches
13252
13311
  if (!this.deviceSettingsAppliedOnce && this.state.settings) {
13253
- await this.applyDeviceConfig(this.state.settings, true);
13312
+ await this.applyDeviceConfig(this.state.settings, true, false);
13254
13313
  globalThis.streamRNVideoSDK?.callManager.start();
13255
13314
  this.deviceSettingsAppliedOnce = true;
13256
13315
  }
@@ -14508,7 +14567,7 @@ class Call {
14508
14567
  *
14509
14568
  * @internal
14510
14569
  */
14511
- this.applyDeviceConfig = async (settings, publish, skipSpeakerApply = false) => {
14570
+ this.applyDeviceConfig = async (settings, publish, skipSpeakerApply) => {
14512
14571
  if (!skipSpeakerApply) {
14513
14572
  this.speaker.apply(settings);
14514
14573
  }
@@ -15826,7 +15885,7 @@ class StreamClient {
15826
15885
  this.getUserAgent = () => {
15827
15886
  if (!this.cachedUserAgent) {
15828
15887
  const { clientAppIdentifier = {} } = this.options;
15829
- const { sdkName = 'js', sdkVersion = "1.44.1-beta.1", ...extras } = clientAppIdentifier;
15888
+ const { sdkName = 'js', sdkVersion = "1.44.2", ...extras } = clientAppIdentifier;
15830
15889
  this.cachedUserAgent = [
15831
15890
  `stream-video-${sdkName}-v${sdkVersion}`,
15832
15891
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -16261,7 +16320,7 @@ class StreamVideoClient {
16261
16320
  clientStore: this.writeableStateStore,
16262
16321
  });
16263
16322
  call.state.updateFromCallResponse(c.call);
16264
- await call.applyDeviceConfig(c.call.settings, false);
16323
+ await call.applyDeviceConfig(c.call.settings, false, isReactNative());
16265
16324
  if (data.watch) {
16266
16325
  await call.setup();
16267
16326
  this.writeableStateStore.registerCall(call);