@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/dist/index.cjs.js CHANGED
@@ -6080,7 +6080,7 @@ const getSdkVersion = (sdk) => {
6080
6080
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6081
6081
  };
6082
6082
 
6083
- const version = "1.40.0";
6083
+ const version = "1.40.2";
6084
6084
  const [major, minor, patch] = version.split('.');
6085
6085
  let sdkInfo = {
6086
6086
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8513,14 +8513,27 @@ class StreamSfuClient {
8513
8513
  // be replaced with a new one in case a second join request is made
8514
8514
  const current = this.joinResponseTask;
8515
8515
  let timeoutId = undefined;
8516
+ const unsubscribeJoinErrorEvents = this.dispatcher.on('error', (event) => {
8517
+ const { error, reconnectStrategy } = event;
8518
+ if (!error)
8519
+ return;
8520
+ if (reconnectStrategy === WebsocketReconnectStrategy.DISCONNECT) {
8521
+ clearTimeout(timeoutId);
8522
+ unsubscribe?.();
8523
+ unsubscribeJoinErrorEvents();
8524
+ current.reject(new SfuJoinError(event));
8525
+ }
8526
+ });
8516
8527
  const unsubscribe = this.dispatcher.on('joinResponse', (joinResponse) => {
8517
8528
  clearTimeout(timeoutId);
8518
8529
  unsubscribe();
8530
+ unsubscribeJoinErrorEvents();
8519
8531
  this.keepAlive();
8520
8532
  current.resolve(joinResponse);
8521
8533
  });
8522
8534
  timeoutId = setTimeout(() => {
8523
8535
  unsubscribe();
8536
+ unsubscribeJoinErrorEvents();
8524
8537
  const message = `Waiting for "joinResponse" has timed out after ${this.joinResponseTimeout}ms`;
8525
8538
  this.tracer?.trace('joinRequestTimeout', message);
8526
8539
  current.reject(new Error(message));
@@ -8665,6 +8678,14 @@ StreamSfuClient.DISPOSE_OLD_SOCKET = 4100;
8665
8678
  * The close code used when the client fails to join the call (on the SFU).
8666
8679
  */
8667
8680
  StreamSfuClient.JOIN_FAILED = 4101;
8681
+ class SfuJoinError extends Error {
8682
+ constructor(event) {
8683
+ super(event.error?.message || 'Join Error');
8684
+ this.errorEvent = event;
8685
+ this.unrecoverable =
8686
+ event.reconnectStrategy === WebsocketReconnectStrategy.DISCONNECT;
8687
+ }
8688
+ }
8668
8689
 
8669
8690
  /**
8670
8691
  * Event handler that watched the delivery of `call.accepted`.
@@ -10318,6 +10339,19 @@ class DeviceManager {
10318
10339
  * if true, stops the media stream when call is left
10319
10340
  */
10320
10341
  this.stopOnLeave = true;
10342
+ /**
10343
+ * When `true`, the `apply()` method will skip automatically enabling/disabling
10344
+ * the device based on server defaults (`mic_default_on`, `camera_default_on`).
10345
+ *
10346
+ * This is useful when application code wants to handle device preferences
10347
+ * (e.g., persisted user preferences) and prevent server defaults from
10348
+ * overriding them.
10349
+ *
10350
+ * @default false
10351
+ *
10352
+ * @internal
10353
+ */
10354
+ this.deferServerDefaults = false;
10321
10355
  this.subscriptions = [];
10322
10356
  this.areSubscriptionsSetUp = false;
10323
10357
  this.isTrackStoppedDueToTrackEnd = false;
@@ -10996,8 +11030,15 @@ class CameraManager extends DeviceManager {
10996
11030
  * @internal
10997
11031
  */
10998
11032
  async selectTargetResolution(resolution) {
10999
- this.targetResolution.height = resolution.height;
11000
- this.targetResolution.width = resolution.width;
11033
+ // normalize target resolution to landscape format.
11034
+ // on mobile devices, the device itself adjusts the resolution to portrait or landscape
11035
+ // depending on the orientation of the device. using portrait resolution
11036
+ // will result in falling back to the default resolution (640x480).
11037
+ let { width, height } = resolution;
11038
+ if (width < height)
11039
+ [width, height] = [height, width];
11040
+ this.targetResolution.height = height;
11041
+ this.targetResolution.width = width;
11001
11042
  if (this.state.optimisticStatus === 'enabled') {
11002
11043
  try {
11003
11044
  await this.statusChangeSettled();
@@ -11011,9 +11052,8 @@ class CameraManager extends DeviceManager {
11011
11052
  const [videoTrack] = this.state.mediaStream.getVideoTracks();
11012
11053
  if (!videoTrack)
11013
11054
  return;
11014
- const { width, height } = videoTrack.getSettings();
11015
- if (width !== this.targetResolution.width ||
11016
- height !== this.targetResolution.height) {
11055
+ const { width: w, height: h } = videoTrack.getSettings();
11056
+ if (w !== width || h !== height) {
11017
11057
  await this.applySettingsToStream();
11018
11058
  this.logger.debug(`${width}x${height} target resolution applied to media stream`);
11019
11059
  }
@@ -11026,38 +11066,25 @@ class CameraManager extends DeviceManager {
11026
11066
  * @param publish whether to publish the stream after applying the settings.
11027
11067
  */
11028
11068
  async apply(settings, publish) {
11029
- const hasPublishedVideo = !!this.call.state.localParticipant?.videoStream;
11030
- const hasPermission = this.call.permissionsContext.hasPermission(OwnCapability.SEND_AUDIO);
11031
- if (hasPublishedVideo || !hasPermission)
11032
- return;
11033
11069
  // Wait for any in progress camera operation
11034
11070
  await this.statusChangeSettled();
11035
- const { target_resolution, camera_facing, camera_default_on, enabled } = settings;
11036
- // normalize target resolution to landscape format.
11037
- // on mobile devices, the device itself adjusts the resolution to portrait or landscape
11038
- // depending on the orientation of the device. using portrait resolution
11039
- // will result in falling back to the default resolution (640x480).
11040
- let { width, height } = target_resolution;
11041
- if (width < height)
11042
- [width, height] = [height, width];
11043
- await this.selectTargetResolution({ width, height });
11044
- // Set camera direction if it's not yet set
11045
- if (!this.state.direction && !this.state.selectedDevice) {
11046
- this.state.setDirection(camera_facing === 'front' ? 'front' : 'back');
11071
+ await this.selectTargetResolution(settings.target_resolution);
11072
+ // apply a direction and enable the camera only if in "pristine" state
11073
+ // and server defaults are not deferred to application code
11074
+ const canPublish = this.call.permissionsContext.canPublish(this.trackType);
11075
+ if (this.state.status === undefined && !this.deferServerDefaults) {
11076
+ if (!this.state.direction && !this.state.selectedDevice) {
11077
+ const direction = settings.camera_facing === 'front' ? 'front' : 'back';
11078
+ await this.selectDirection(direction);
11079
+ }
11080
+ if (canPublish && settings.camera_default_on && settings.enabled) {
11081
+ await this.enable();
11082
+ }
11047
11083
  }
11048
- if (!publish)
11049
- return;
11050
11084
  const { mediaStream } = this.state;
11051
- if (this.enabled && mediaStream) {
11052
- // The camera is already enabled (e.g. lobby screen). Publish the stream
11085
+ if (canPublish && publish && this.enabled && mediaStream) {
11053
11086
  await this.publishStream(mediaStream);
11054
11087
  }
11055
- else if (this.state.status === undefined &&
11056
- camera_default_on &&
11057
- enabled) {
11058
- // Start camera if backend config specifies, and there is no local setting
11059
- await this.enable();
11060
- }
11061
11088
  }
11062
11089
  getDevices() {
11063
11090
  return getVideoDevices(this.call.tracer);
@@ -11565,24 +11592,20 @@ class MicrophoneManager extends AudioDeviceManager {
11565
11592
  * @param publish whether to publish the stream after applying the settings.
11566
11593
  */
11567
11594
  async apply(settings, publish) {
11568
- if (!publish)
11569
- return;
11570
- const hasPublishedAudio = !!this.call.state.localParticipant?.audioStream;
11571
- const hasPermission = this.call.permissionsContext.hasPermission(OwnCapability.SEND_AUDIO);
11572
- if (hasPublishedAudio || !hasPermission)
11573
- return;
11574
11595
  // Wait for any in progress mic operation
11575
11596
  await this.statusChangeSettled();
11576
- // Publish media stream that was set before we joined
11597
+ const canPublish = this.call.permissionsContext.canPublish(this.trackType);
11598
+ // apply server-side settings only when the device state is pristine
11599
+ // and server defaults are not deferred to application code
11600
+ if (this.state.status === undefined && !this.deferServerDefaults) {
11601
+ if (canPublish && settings.mic_default_on) {
11602
+ await this.enable();
11603
+ }
11604
+ }
11577
11605
  const { mediaStream } = this.state;
11578
- if (this.enabled && mediaStream) {
11579
- // The mic is already enabled (e.g. lobby screen). Publish the stream
11606
+ if (canPublish && publish && this.enabled && mediaStream) {
11580
11607
  await this.publishStream(mediaStream);
11581
11608
  }
11582
- else if (this.state.status === undefined && settings.mic_default_on) {
11583
- // Start mic if backend config specifies, and there is no local setting
11584
- await this.enable();
11585
- }
11586
11609
  }
11587
11610
  getDevices() {
11588
11611
  return getAudioDevices(this.call.tracer);
@@ -12439,7 +12462,8 @@ class Call {
12439
12462
  }
12440
12463
  catch (err) {
12441
12464
  this.logger.warn(`Failed to join call (${attempt})`, this.cid);
12442
- if (err instanceof ErrorFromResponse && err.unrecoverable) {
12465
+ if ((err instanceof ErrorFromResponse && err.unrecoverable) ||
12466
+ (err instanceof SfuJoinError && err.unrecoverable)) {
12443
12467
  // if the error is unrecoverable, we should not retry as that signals
12444
12468
  // that connectivity is good, but the coordinator doesn't allow the user
12445
12469
  // to join the call due to some reason (e.g., ended call, expired token...)
@@ -15108,7 +15132,7 @@ class StreamClient {
15108
15132
  this.getUserAgent = () => {
15109
15133
  if (!this.cachedUserAgent) {
15110
15134
  const { clientAppIdentifier = {} } = this.options;
15111
- const { sdkName = 'js', sdkVersion = "1.40.0", ...extras } = clientAppIdentifier;
15135
+ const { sdkName = 'js', sdkVersion = "1.40.2", ...extras } = clientAppIdentifier;
15112
15136
  this.cachedUserAgent = [
15113
15137
  `stream-video-${sdkName}-v${sdkVersion}`,
15114
15138
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15786,6 +15810,7 @@ exports.RxUtils = rxUtils;
15786
15810
  exports.ScreenShareManager = ScreenShareManager;
15787
15811
  exports.ScreenShareState = ScreenShareState;
15788
15812
  exports.SfuEvents = events;
15813
+ exports.SfuJoinError = SfuJoinError;
15789
15814
  exports.SfuModels = models;
15790
15815
  exports.SpeakerManager = SpeakerManager;
15791
15816
  exports.SpeakerState = SpeakerState;