@stream-io/video-client 1.40.0 → 1.40.1

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,12 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [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)
6
+
7
+ ### Bug Fixes
8
+
9
+ - 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))
10
+
5
11
  ## [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
12
 
7
13
  ### 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.1";
6064
6064
  const [major, minor, patch] = version.split('.');
6065
6065
  let sdkInfo = {
6066
6066
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -10298,6 +10298,19 @@ class DeviceManager {
10298
10298
  * if true, stops the media stream when call is left
10299
10299
  */
10300
10300
  this.stopOnLeave = true;
10301
+ /**
10302
+ * When `true`, the `apply()` method will skip automatically enabling/disabling
10303
+ * the device based on server defaults (`mic_default_on`, `camera_default_on`).
10304
+ *
10305
+ * This is useful when application code wants to handle device preferences
10306
+ * (e.g., persisted user preferences) and prevent server defaults from
10307
+ * overriding them.
10308
+ *
10309
+ * @default false
10310
+ *
10311
+ * @internal
10312
+ */
10313
+ this.deferServerDefaults = false;
10301
10314
  this.subscriptions = [];
10302
10315
  this.areSubscriptionsSetUp = false;
10303
10316
  this.isTrackStoppedDueToTrackEnd = false;
@@ -10976,8 +10989,15 @@ class CameraManager extends DeviceManager {
10976
10989
  * @internal
10977
10990
  */
10978
10991
  async selectTargetResolution(resolution) {
10979
- this.targetResolution.height = resolution.height;
10980
- this.targetResolution.width = resolution.width;
10992
+ // normalize target resolution to landscape format.
10993
+ // on mobile devices, the device itself adjusts the resolution to portrait or landscape
10994
+ // depending on the orientation of the device. using portrait resolution
10995
+ // will result in falling back to the default resolution (640x480).
10996
+ let { width, height } = resolution;
10997
+ if (width < height)
10998
+ [width, height] = [height, width];
10999
+ this.targetResolution.height = height;
11000
+ this.targetResolution.width = width;
10981
11001
  if (this.state.optimisticStatus === 'enabled') {
10982
11002
  try {
10983
11003
  await this.statusChangeSettled();
@@ -10991,9 +11011,8 @@ class CameraManager extends DeviceManager {
10991
11011
  const [videoTrack] = this.state.mediaStream.getVideoTracks();
10992
11012
  if (!videoTrack)
10993
11013
  return;
10994
- const { width, height } = videoTrack.getSettings();
10995
- if (width !== this.targetResolution.width ||
10996
- height !== this.targetResolution.height) {
11014
+ const { width: w, height: h } = videoTrack.getSettings();
11015
+ if (w !== width || h !== height) {
10997
11016
  await this.applySettingsToStream();
10998
11017
  this.logger.debug(`${width}x${height} target resolution applied to media stream`);
10999
11018
  }
@@ -11006,38 +11025,25 @@ class CameraManager extends DeviceManager {
11006
11025
  * @param publish whether to publish the stream after applying the settings.
11007
11026
  */
11008
11027
  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
11028
  // Wait for any in progress camera operation
11014
11029
  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');
11030
+ await this.selectTargetResolution(settings.target_resolution);
11031
+ // apply a direction and enable the camera only if in "pristine" state
11032
+ // and server defaults are not deferred to application code
11033
+ const canPublish = this.call.permissionsContext.canPublish(this.trackType);
11034
+ if (this.state.status === undefined && !this.deferServerDefaults) {
11035
+ if (!this.state.direction && !this.state.selectedDevice) {
11036
+ const direction = settings.camera_facing === 'front' ? 'front' : 'back';
11037
+ await this.selectDirection(direction);
11038
+ }
11039
+ if (canPublish && settings.camera_default_on && settings.enabled) {
11040
+ await this.enable();
11041
+ }
11027
11042
  }
11028
- if (!publish)
11029
- return;
11030
11043
  const { mediaStream } = this.state;
11031
- if (this.enabled && mediaStream) {
11032
- // The camera is already enabled (e.g. lobby screen). Publish the stream
11044
+ if (canPublish && publish && this.enabled && mediaStream) {
11033
11045
  await this.publishStream(mediaStream);
11034
11046
  }
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
11047
  }
11042
11048
  getDevices() {
11043
11049
  return getVideoDevices(this.call.tracer);
@@ -11545,24 +11551,20 @@ class MicrophoneManager extends AudioDeviceManager {
11545
11551
  * @param publish whether to publish the stream after applying the settings.
11546
11552
  */
11547
11553
  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
11554
  // Wait for any in progress mic operation
11555
11555
  await this.statusChangeSettled();
11556
- // Publish media stream that was set before we joined
11556
+ const canPublish = this.call.permissionsContext.canPublish(this.trackType);
11557
+ // apply server-side settings only when the device state is pristine
11558
+ // and server defaults are not deferred to application code
11559
+ if (this.state.status === undefined && !this.deferServerDefaults) {
11560
+ if (canPublish && settings.mic_default_on) {
11561
+ await this.enable();
11562
+ }
11563
+ }
11557
11564
  const { mediaStream } = this.state;
11558
- if (this.enabled && mediaStream) {
11559
- // The mic is already enabled (e.g. lobby screen). Publish the stream
11565
+ if (canPublish && publish && this.enabled && mediaStream) {
11560
11566
  await this.publishStream(mediaStream);
11561
11567
  }
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
11568
  }
11567
11569
  getDevices() {
11568
11570
  return getAudioDevices(this.call.tracer);
@@ -15090,7 +15092,7 @@ class StreamClient {
15090
15092
  this.getUserAgent = () => {
15091
15093
  if (!this.cachedUserAgent) {
15092
15094
  const { clientAppIdentifier = {} } = this.options;
15093
- const { sdkName = 'js', sdkVersion = "1.40.0", ...extras } = clientAppIdentifier;
15095
+ const { sdkName = 'js', sdkVersion = "1.40.1", ...extras } = clientAppIdentifier;
15094
15096
  this.cachedUserAgent = [
15095
15097
  `stream-video-${sdkName}-v${sdkVersion}`,
15096
15098
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),