@stream-io/video-client 1.40.0 → 1.40.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,19 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.40.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.40.1...@stream-io/video-client-1.40.2) (2026-01-15)
6
+
7
+ ### Bug Fixes
8
+
9
+ - handle unrecoverable SFU join errors ([9b8198d](https://github.com/GetStream/stream-video-js/commit/9b8198d00e901a8eade169495a14d25c8d3bdf1e))
10
+ - handle unrecoverable SFU join errors ([#2083](https://github.com/GetStream/stream-video-js/issues/2083)) ([6ffb576](https://github.com/GetStream/stream-video-js/commit/6ffb5761b3dfb8e649cfa4f16dd30d294475eeae))
11
+
12
+ ## [1.40.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.40.0...@stream-io/video-client-1.40.1) (2026-01-14)
13
+
14
+ ### Bug Fixes
15
+
16
+ - ensure proper set up of server-side preferences for mic and camera ([#2080](https://github.com/GetStream/stream-video-js/issues/2080)) ([3529c8f](https://github.com/GetStream/stream-video-js/commit/3529c8fc0233d3f9f8f21c80cffc4ec27334954f))
17
+
5
18
  ## [1.40.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.39.3...@stream-io/video-client-1.40.0) (2026-01-09)
6
19
 
7
20
  ### Features
@@ -6060,7 +6060,7 @@ const getSdkVersion = (sdk) => {
6060
6060
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6061
6061
  };
6062
6062
 
6063
- const version = "1.40.0";
6063
+ const version = "1.40.2";
6064
6064
  const [major, minor, patch] = version.split('.');
6065
6065
  let sdkInfo = {
6066
6066
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8493,14 +8493,27 @@ class StreamSfuClient {
8493
8493
  // be replaced with a new one in case a second join request is made
8494
8494
  const current = this.joinResponseTask;
8495
8495
  let timeoutId = undefined;
8496
+ const unsubscribeJoinErrorEvents = this.dispatcher.on('error', (event) => {
8497
+ const { error, reconnectStrategy } = event;
8498
+ if (!error)
8499
+ return;
8500
+ if (reconnectStrategy === WebsocketReconnectStrategy.DISCONNECT) {
8501
+ clearTimeout(timeoutId);
8502
+ unsubscribe?.();
8503
+ unsubscribeJoinErrorEvents();
8504
+ current.reject(new SfuJoinError(event));
8505
+ }
8506
+ });
8496
8507
  const unsubscribe = this.dispatcher.on('joinResponse', (joinResponse) => {
8497
8508
  clearTimeout(timeoutId);
8498
8509
  unsubscribe();
8510
+ unsubscribeJoinErrorEvents();
8499
8511
  this.keepAlive();
8500
8512
  current.resolve(joinResponse);
8501
8513
  });
8502
8514
  timeoutId = setTimeout(() => {
8503
8515
  unsubscribe();
8516
+ unsubscribeJoinErrorEvents();
8504
8517
  const message = `Waiting for "joinResponse" has timed out after ${this.joinResponseTimeout}ms`;
8505
8518
  this.tracer?.trace('joinRequestTimeout', message);
8506
8519
  current.reject(new Error(message));
@@ -8645,6 +8658,14 @@ StreamSfuClient.DISPOSE_OLD_SOCKET = 4100;
8645
8658
  * The close code used when the client fails to join the call (on the SFU).
8646
8659
  */
8647
8660
  StreamSfuClient.JOIN_FAILED = 4101;
8661
+ class SfuJoinError extends Error {
8662
+ constructor(event) {
8663
+ super(event.error?.message || 'Join Error');
8664
+ this.errorEvent = event;
8665
+ this.unrecoverable =
8666
+ event.reconnectStrategy === WebsocketReconnectStrategy.DISCONNECT;
8667
+ }
8668
+ }
8648
8669
 
8649
8670
  /**
8650
8671
  * Event handler that watched the delivery of `call.accepted`.
@@ -10298,6 +10319,19 @@ class DeviceManager {
10298
10319
  * if true, stops the media stream when call is left
10299
10320
  */
10300
10321
  this.stopOnLeave = true;
10322
+ /**
10323
+ * When `true`, the `apply()` method will skip automatically enabling/disabling
10324
+ * the device based on server defaults (`mic_default_on`, `camera_default_on`).
10325
+ *
10326
+ * This is useful when application code wants to handle device preferences
10327
+ * (e.g., persisted user preferences) and prevent server defaults from
10328
+ * overriding them.
10329
+ *
10330
+ * @default false
10331
+ *
10332
+ * @internal
10333
+ */
10334
+ this.deferServerDefaults = false;
10301
10335
  this.subscriptions = [];
10302
10336
  this.areSubscriptionsSetUp = false;
10303
10337
  this.isTrackStoppedDueToTrackEnd = false;
@@ -10976,8 +11010,15 @@ class CameraManager extends DeviceManager {
10976
11010
  * @internal
10977
11011
  */
10978
11012
  async selectTargetResolution(resolution) {
10979
- this.targetResolution.height = resolution.height;
10980
- this.targetResolution.width = resolution.width;
11013
+ // normalize target resolution to landscape format.
11014
+ // on mobile devices, the device itself adjusts the resolution to portrait or landscape
11015
+ // depending on the orientation of the device. using portrait resolution
11016
+ // will result in falling back to the default resolution (640x480).
11017
+ let { width, height } = resolution;
11018
+ if (width < height)
11019
+ [width, height] = [height, width];
11020
+ this.targetResolution.height = height;
11021
+ this.targetResolution.width = width;
10981
11022
  if (this.state.optimisticStatus === 'enabled') {
10982
11023
  try {
10983
11024
  await this.statusChangeSettled();
@@ -10991,9 +11032,8 @@ class CameraManager extends DeviceManager {
10991
11032
  const [videoTrack] = this.state.mediaStream.getVideoTracks();
10992
11033
  if (!videoTrack)
10993
11034
  return;
10994
- const { width, height } = videoTrack.getSettings();
10995
- if (width !== this.targetResolution.width ||
10996
- height !== this.targetResolution.height) {
11035
+ const { width: w, height: h } = videoTrack.getSettings();
11036
+ if (w !== width || h !== height) {
10997
11037
  await this.applySettingsToStream();
10998
11038
  this.logger.debug(`${width}x${height} target resolution applied to media stream`);
10999
11039
  }
@@ -11006,38 +11046,25 @@ class CameraManager extends DeviceManager {
11006
11046
  * @param publish whether to publish the stream after applying the settings.
11007
11047
  */
11008
11048
  async apply(settings, publish) {
11009
- const hasPublishedVideo = !!this.call.state.localParticipant?.videoStream;
11010
- const hasPermission = this.call.permissionsContext.hasPermission(OwnCapability.SEND_AUDIO);
11011
- if (hasPublishedVideo || !hasPermission)
11012
- return;
11013
11049
  // Wait for any in progress camera operation
11014
11050
  await this.statusChangeSettled();
11015
- const { target_resolution, camera_facing, camera_default_on, enabled } = settings;
11016
- // normalize target resolution to landscape format.
11017
- // on mobile devices, the device itself adjusts the resolution to portrait or landscape
11018
- // depending on the orientation of the device. using portrait resolution
11019
- // will result in falling back to the default resolution (640x480).
11020
- let { width, height } = target_resolution;
11021
- if (width < height)
11022
- [width, height] = [height, width];
11023
- await this.selectTargetResolution({ width, height });
11024
- // Set camera direction if it's not yet set
11025
- if (!this.state.direction && !this.state.selectedDevice) {
11026
- this.state.setDirection(camera_facing === 'front' ? 'front' : 'back');
11051
+ await this.selectTargetResolution(settings.target_resolution);
11052
+ // apply a direction and enable the camera only if in "pristine" state
11053
+ // and server defaults are not deferred to application code
11054
+ const canPublish = this.call.permissionsContext.canPublish(this.trackType);
11055
+ if (this.state.status === undefined && !this.deferServerDefaults) {
11056
+ if (!this.state.direction && !this.state.selectedDevice) {
11057
+ const direction = settings.camera_facing === 'front' ? 'front' : 'back';
11058
+ await this.selectDirection(direction);
11059
+ }
11060
+ if (canPublish && settings.camera_default_on && settings.enabled) {
11061
+ await this.enable();
11062
+ }
11027
11063
  }
11028
- if (!publish)
11029
- return;
11030
11064
  const { mediaStream } = this.state;
11031
- if (this.enabled && mediaStream) {
11032
- // The camera is already enabled (e.g. lobby screen). Publish the stream
11065
+ if (canPublish && publish && this.enabled && mediaStream) {
11033
11066
  await this.publishStream(mediaStream);
11034
11067
  }
11035
- else if (this.state.status === undefined &&
11036
- camera_default_on &&
11037
- enabled) {
11038
- // Start camera if backend config specifies, and there is no local setting
11039
- await this.enable();
11040
- }
11041
11068
  }
11042
11069
  getDevices() {
11043
11070
  return getVideoDevices(this.call.tracer);
@@ -11545,24 +11572,20 @@ class MicrophoneManager extends AudioDeviceManager {
11545
11572
  * @param publish whether to publish the stream after applying the settings.
11546
11573
  */
11547
11574
  async apply(settings, publish) {
11548
- if (!publish)
11549
- return;
11550
- const hasPublishedAudio = !!this.call.state.localParticipant?.audioStream;
11551
- const hasPermission = this.call.permissionsContext.hasPermission(OwnCapability.SEND_AUDIO);
11552
- if (hasPublishedAudio || !hasPermission)
11553
- return;
11554
11575
  // Wait for any in progress mic operation
11555
11576
  await this.statusChangeSettled();
11556
- // Publish media stream that was set before we joined
11577
+ const canPublish = this.call.permissionsContext.canPublish(this.trackType);
11578
+ // apply server-side settings only when the device state is pristine
11579
+ // and server defaults are not deferred to application code
11580
+ if (this.state.status === undefined && !this.deferServerDefaults) {
11581
+ if (canPublish && settings.mic_default_on) {
11582
+ await this.enable();
11583
+ }
11584
+ }
11557
11585
  const { mediaStream } = this.state;
11558
- if (this.enabled && mediaStream) {
11559
- // The mic is already enabled (e.g. lobby screen). Publish the stream
11586
+ if (canPublish && publish && this.enabled && mediaStream) {
11560
11587
  await this.publishStream(mediaStream);
11561
11588
  }
11562
- else if (this.state.status === undefined && settings.mic_default_on) {
11563
- // Start mic if backend config specifies, and there is no local setting
11564
- await this.enable();
11565
- }
11566
11589
  }
11567
11590
  getDevices() {
11568
11591
  return getAudioDevices(this.call.tracer);
@@ -12419,7 +12442,8 @@ class Call {
12419
12442
  }
12420
12443
  catch (err) {
12421
12444
  this.logger.warn(`Failed to join call (${attempt})`, this.cid);
12422
- if (err instanceof ErrorFromResponse && err.unrecoverable) {
12445
+ if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
12446
+ (err instanceof SfuJoinError && err.unrecoverable)) {
12423
12447
  // if the error is unrecoverable, we should not retry as that signals
12424
12448
  // that connectivity is good, but the coordinator doesn't allow the user
12425
12449
  // to join the call due to some reason (e.g., ended call, expired token...)
@@ -15090,7 +15114,7 @@ class StreamClient {
15090
15114
  this.getUserAgent = () => {
15091
15115
  if (!this.cachedUserAgent) {
15092
15116
  const { clientAppIdentifier = {} } = this.options;
15093
- const { sdkName = 'js', sdkVersion = "1.40.0", ...extras } = clientAppIdentifier;
15117
+ const { sdkName = 'js', sdkVersion = "1.40.2", ...extras } = clientAppIdentifier;
15094
15118
  this.cachedUserAgent = [
15095
15119
  `stream-video-${sdkName}-v${sdkVersion}`,
15096
15120
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15726,5 +15750,5 @@ const humanize = (n) => {
15726
15750
  return String(n);
15727
15751
  };
15728
15752
 
15729
- export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
15753
+ export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
15730
15754
  //# sourceMappingURL=index.browser.es.js.map