@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 +13 -0
- package/dist/index.browser.es.js +72 -48
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +72 -47
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +72 -48
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamSfuClient.d.ts +6 -1
- package/dist/src/devices/DeviceManager.d.ts +13 -0
- package/package.json +1 -1
- package/src/Call.ts +5 -2
- package/src/StreamSfuClient.ts +29 -1
- package/src/devices/CameraManager.ts +26 -38
- package/src/devices/DeviceManager.ts +14 -0
- package/src/devices/MicrophoneManager.ts +10 -14
- package/src/devices/__tests__/CameraManager.test.ts +6 -6
- package/src/devices/__tests__/MicrophoneManager.test.ts +2 -9
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
|
package/dist/index.browser.es.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
10980
|
-
|
|
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 (
|
|
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
|
-
|
|
11016
|
-
//
|
|
11017
|
-
//
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
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
|
-
|
|
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.
|
|
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
|