@stream-io/video-client 1.27.0 → 1.27.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 +8 -0
- package/dist/index.browser.es.js +43 -28
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +42 -27
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +43 -28
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/devices.d.ts +5 -5
- package/dist/src/helpers/lazy.d.ts +1 -1
- package/dist/src/stats/rtc/Tracer.d.ts +4 -1
- package/dist/src/stats/rtc/types.d.ts +1 -0
- package/dist/src/timers/index.d.ts +1 -1
- package/package.json +2 -2
- package/src/Call.ts +7 -4
- package/src/coordinator/connection/connection.ts +0 -1
- package/src/devices/CameraManager.ts +1 -1
- package/src/devices/InputMediaDeviceManager.ts +5 -3
- package/src/devices/MicrophoneManager.ts +2 -1
- package/src/devices/SpeakerManager.ts +1 -1
- package/src/devices/devices.ts +29 -11
- package/src/helpers/lazy.ts +3 -3
- package/src/rtc/__tests__/videoLayers.test.ts +4 -6
- package/src/rtc/videoLayers.ts +12 -6
- package/src/stats/rtc/Tracer.ts +19 -1
- package/src/stats/rtc/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.27.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.27.0...@stream-io/video-client-1.27.1) (2025-07-25)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- improved audio and video filter tracing ([#1862](https://github.com/GetStream/stream-video-js/issues/1862)) ([701ea4b](https://github.com/GetStream/stream-video-js/commit/701ea4b3266f68072c1325b70221fdefd77137ec))
|
|
10
|
+
- synchronize updateMuteState; use correct fallback dimensions ([#1867](https://github.com/GetStream/stream-video-js/issues/1867)) ([154cdda](https://github.com/GetStream/stream-video-js/commit/154cddaa4462ee03af5fdf4929ad9f4e3d4b5070))
|
|
11
|
+
- trace available devices and thermal state changes ([#1866](https://github.com/GetStream/stream-video-js/issues/1866)) ([d8312b5](https://github.com/GetStream/stream-video-js/commit/d8312b5c109b14baa28ee764202d387499d0fd52))
|
|
12
|
+
|
|
5
13
|
## [1.27.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.26.1...@stream-io/video-client-1.27.0) (2025-07-18)
|
|
6
14
|
|
|
7
15
|
### Features
|
package/dist/index.browser.es.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
|
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
|
-
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of } from 'rxjs';
|
|
7
|
+
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
|
|
8
8
|
import { UAParser } from 'ua-parser-js';
|
|
9
9
|
import { parse, write } from 'sdp-transform';
|
|
10
10
|
|
|
@@ -5914,7 +5914,7 @@ const aggregate = (stats) => {
|
|
|
5914
5914
|
return report;
|
|
5915
5915
|
};
|
|
5916
5916
|
|
|
5917
|
-
const version = "1.27.
|
|
5917
|
+
const version = "1.27.1";
|
|
5918
5918
|
const [major, minor, patch] = version.split('.');
|
|
5919
5919
|
let sdkInfo = {
|
|
5920
5920
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6498,6 +6498,15 @@ class Tracer {
|
|
|
6498
6498
|
return;
|
|
6499
6499
|
this.buffer.push([tag, this.id, data, Date.now()]);
|
|
6500
6500
|
};
|
|
6501
|
+
this.traceOnce = (key, tag, data) => {
|
|
6502
|
+
if (this.keys?.has(key))
|
|
6503
|
+
return;
|
|
6504
|
+
this.trace(tag, data);
|
|
6505
|
+
(this.keys ?? (this.keys = new Map())).set(key, true);
|
|
6506
|
+
};
|
|
6507
|
+
this.resetTrace = (key) => {
|
|
6508
|
+
this.keys?.delete(key);
|
|
6509
|
+
};
|
|
6501
6510
|
this.take = () => {
|
|
6502
6511
|
const snapshot = this.buffer;
|
|
6503
6512
|
this.buffer = [];
|
|
@@ -6510,6 +6519,7 @@ class Tracer {
|
|
|
6510
6519
|
};
|
|
6511
6520
|
this.dispose = () => {
|
|
6512
6521
|
this.buffer = [];
|
|
6522
|
+
this.keys?.clear();
|
|
6513
6523
|
};
|
|
6514
6524
|
this.id = id;
|
|
6515
6525
|
}
|
|
@@ -6973,9 +6983,8 @@ const computeVideoLayers = (videoTrack, publishOption) => {
|
|
|
6973
6983
|
if (isAudioTrackType(publishOption.trackType))
|
|
6974
6984
|
return;
|
|
6975
6985
|
const optimalVideoLayers = [];
|
|
6976
|
-
const
|
|
6977
|
-
const { width =
|
|
6978
|
-
const { bitrate, codec, fps, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, useSingleLayer, } = publishOption;
|
|
6986
|
+
const { bitrate, codec, fps = 30, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, useSingleLayer, } = publishOption;
|
|
6987
|
+
const { width = videoDimension.width, height = videoDimension.height } = videoTrack.getSettings();
|
|
6979
6988
|
const maxBitrate = getComputedMaxBitrate(videoDimension, width, height, bitrate);
|
|
6980
6989
|
let downscaleFactor = 1;
|
|
6981
6990
|
let bitrateFactor = 1;
|
|
@@ -7007,7 +7016,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
|
|
|
7007
7016
|
}
|
|
7008
7017
|
// for simplicity, we start with all layers enabled, then this function
|
|
7009
7018
|
// will clear/reassign the layers that are not needed
|
|
7010
|
-
return withSimulcastConstraints(
|
|
7019
|
+
return withSimulcastConstraints(width, height, optimalVideoLayers, useSingleLayer);
|
|
7011
7020
|
};
|
|
7012
7021
|
/**
|
|
7013
7022
|
* Computes the maximum bitrate for a given resolution.
|
|
@@ -7041,9 +7050,9 @@ const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight, bi
|
|
|
7041
7050
|
*
|
|
7042
7051
|
* https://chromium.googlesource.com/external/webrtc/+/refs/heads/main/media/engine/simulcast.cc#90
|
|
7043
7052
|
*/
|
|
7044
|
-
const withSimulcastConstraints = (
|
|
7053
|
+
const withSimulcastConstraints = (width, height, optimalVideoLayers, useSingleLayer) => {
|
|
7045
7054
|
let layers;
|
|
7046
|
-
const size = Math.max(
|
|
7055
|
+
const size = Math.max(width, height);
|
|
7047
7056
|
if (size <= 320) {
|
|
7048
7057
|
// provide only one layer 320x240 (f), the one with the highest quality
|
|
7049
7058
|
layers = optimalVideoLayers.filter((layer) => layer.rid === 'f');
|
|
@@ -7727,9 +7736,9 @@ const uninitialized = Symbol('uninitialized');
|
|
|
7727
7736
|
*/
|
|
7728
7737
|
function lazy(factory) {
|
|
7729
7738
|
let value = uninitialized;
|
|
7730
|
-
return () => {
|
|
7739
|
+
return (...args) => {
|
|
7731
7740
|
if (value === uninitialized) {
|
|
7732
|
-
value = factory();
|
|
7741
|
+
value = factory(...args);
|
|
7733
7742
|
}
|
|
7734
7743
|
return value;
|
|
7735
7744
|
};
|
|
@@ -9543,8 +9552,9 @@ function canQueryPermissions() {
|
|
|
9543
9552
|
*
|
|
9544
9553
|
* @param permission a BrowserPermission instance.
|
|
9545
9554
|
* @param kind the kind of devices to enumerate.
|
|
9555
|
+
* @param tracer the tracer to use for tracing the device enumeration.
|
|
9546
9556
|
*/
|
|
9547
|
-
const getDevices = (permission, kind) => {
|
|
9557
|
+
const getDevices = (permission, kind, tracer) => {
|
|
9548
9558
|
return from((async () => {
|
|
9549
9559
|
let devices = await navigator.mediaDevices.enumerateDevices();
|
|
9550
9560
|
// for privacy reasons, most browsers don't give you device labels
|
|
@@ -9553,6 +9563,7 @@ const getDevices = (permission, kind) => {
|
|
|
9553
9563
|
if (shouldPromptForBrowserPermission && (await permission.prompt())) {
|
|
9554
9564
|
devices = await navigator.mediaDevices.enumerateDevices();
|
|
9555
9565
|
}
|
|
9566
|
+
tracer?.traceOnce('device-enumeration', 'navigator.mediaDevices.enumerateDevices', devices);
|
|
9556
9567
|
return devices.filter((device) => device.kind === kind &&
|
|
9557
9568
|
device.label !== '' &&
|
|
9558
9569
|
device.deviceId !== 'default');
|
|
@@ -9603,12 +9614,12 @@ const getVideoBrowserPermission = lazy(() => new BrowserPermission({
|
|
|
9603
9614
|
constraints: videoDeviceConstraints,
|
|
9604
9615
|
queryName: 'camera',
|
|
9605
9616
|
}));
|
|
9606
|
-
const getDeviceChangeObserver = lazy(() => {
|
|
9617
|
+
const getDeviceChangeObserver = lazy((tracer) => {
|
|
9607
9618
|
// 'addEventListener' is not available in React Native, returning
|
|
9608
9619
|
// an observable that will never fire
|
|
9609
9620
|
if (!navigator.mediaDevices.addEventListener)
|
|
9610
9621
|
return from([]);
|
|
9611
|
-
return fromEvent(navigator.mediaDevices, 'devicechange').pipe(map(() => undefined), debounceTime(500));
|
|
9622
|
+
return fromEvent(navigator.mediaDevices, 'devicechange').pipe(tap(() => tracer?.resetTrace('device-enumeration')), map(() => undefined), debounceTime(500));
|
|
9612
9623
|
});
|
|
9613
9624
|
/**
|
|
9614
9625
|
* Prompts the user for a permission to use audio devices (if not already granted
|
|
@@ -9616,8 +9627,8 @@ const getDeviceChangeObserver = lazy(() => {
|
|
|
9616
9627
|
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
9617
9628
|
* the observable errors.
|
|
9618
9629
|
*/
|
|
9619
|
-
const getAudioDevices = lazy(() => {
|
|
9620
|
-
return merge(getDeviceChangeObserver(), getAudioBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput')), shareReplay(1));
|
|
9630
|
+
const getAudioDevices = lazy((tracer) => {
|
|
9631
|
+
return merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput', tracer)), shareReplay(1));
|
|
9621
9632
|
});
|
|
9622
9633
|
/**
|
|
9623
9634
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -9625,8 +9636,8 @@ const getAudioDevices = lazy(() => {
|
|
|
9625
9636
|
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
9626
9637
|
* the observable errors.
|
|
9627
9638
|
*/
|
|
9628
|
-
const getVideoDevices = lazy(() => {
|
|
9629
|
-
return merge(getDeviceChangeObserver(), getVideoBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput')), shareReplay(1));
|
|
9639
|
+
const getVideoDevices = lazy((tracer) => {
|
|
9640
|
+
return merge(getDeviceChangeObserver(tracer), getVideoBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput', tracer)), shareReplay(1));
|
|
9630
9641
|
});
|
|
9631
9642
|
/**
|
|
9632
9643
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -9634,8 +9645,8 @@ const getVideoDevices = lazy(() => {
|
|
|
9634
9645
|
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
9635
9646
|
* the observable errors.
|
|
9636
9647
|
*/
|
|
9637
|
-
const getAudioOutputDevices = lazy(() => {
|
|
9638
|
-
return merge(getDeviceChangeObserver(), getAudioBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput')), shareReplay(1));
|
|
9648
|
+
const getAudioOutputDevices = lazy((tracer) => {
|
|
9649
|
+
return merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput', tracer)), shareReplay(1));
|
|
9639
9650
|
});
|
|
9640
9651
|
let getUserMediaExecId = 0;
|
|
9641
9652
|
const getStream = async (constraints, tracer) => {
|
|
@@ -10007,6 +10018,7 @@ class InputMediaDeviceManager {
|
|
|
10007
10018
|
entry.stop?.();
|
|
10008
10019
|
this.filters = this.filters.filter((f) => f !== entry);
|
|
10009
10020
|
await this.applySettingsToStream();
|
|
10021
|
+
this.call.tracer.trace(`unregisterFilter.${TrackType[this.trackType]}`, null);
|
|
10010
10022
|
}),
|
|
10011
10023
|
};
|
|
10012
10024
|
}
|
|
@@ -10026,7 +10038,7 @@ class InputMediaDeviceManager {
|
|
|
10026
10038
|
*/
|
|
10027
10039
|
async select(deviceId) {
|
|
10028
10040
|
if (isReactNative()) {
|
|
10029
|
-
throw new Error('This method is not supported in React Native.
|
|
10041
|
+
throw new Error('This method is not supported in React Native.');
|
|
10030
10042
|
}
|
|
10031
10043
|
const prevDeviceId = this.state.selectedDevice;
|
|
10032
10044
|
if (deviceId === prevDeviceId) {
|
|
@@ -10593,7 +10605,7 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
10593
10605
|
}
|
|
10594
10606
|
}
|
|
10595
10607
|
getDevices() {
|
|
10596
|
-
return getVideoDevices();
|
|
10608
|
+
return getVideoDevices(this.call.tracer);
|
|
10597
10609
|
}
|
|
10598
10610
|
getStream(constraints) {
|
|
10599
10611
|
constraints.width = this.targetResolution.width;
|
|
@@ -11000,6 +11012,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
11000
11012
|
.catch((err) => {
|
|
11001
11013
|
this.logger('warn', 'Failed to unregister noise cancellation', err);
|
|
11002
11014
|
});
|
|
11015
|
+
this.call.tracer.trace('noiseCancellation.disabled', true);
|
|
11003
11016
|
await this.call.notifyNoiseCancellationStopped();
|
|
11004
11017
|
}
|
|
11005
11018
|
/**
|
|
@@ -11044,7 +11057,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
11044
11057
|
}
|
|
11045
11058
|
}
|
|
11046
11059
|
getDevices() {
|
|
11047
|
-
return getAudioDevices();
|
|
11060
|
+
return getAudioDevices(this.call.tracer);
|
|
11048
11061
|
}
|
|
11049
11062
|
getStream(constraints) {
|
|
11050
11063
|
return getAudioStream(constraints, this.call.tracer);
|
|
@@ -11309,7 +11322,7 @@ class SpeakerManager {
|
|
|
11309
11322
|
if (isReactNative()) {
|
|
11310
11323
|
throw new Error('This feature is not supported in React Native. Please visit https://getstream.io/video/docs/reactnative/core/camera-and-microphone/#speaker-management for more details');
|
|
11311
11324
|
}
|
|
11312
|
-
return getAudioOutputDevices();
|
|
11325
|
+
return getAudioOutputDevices(this.call.tracer);
|
|
11313
11326
|
}
|
|
11314
11327
|
/**
|
|
11315
11328
|
* Select a device.
|
|
@@ -12647,9 +12660,12 @@ class Call {
|
|
|
12647
12660
|
* @internal
|
|
12648
12661
|
*/
|
|
12649
12662
|
this.notifyTrackMuteState = async (muted, ...trackTypes) => {
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12663
|
+
const key = `muteState.${this.cid}.${trackTypes.join('-')}`;
|
|
12664
|
+
await withoutConcurrency(key, async () => {
|
|
12665
|
+
if (!this.sfuClient)
|
|
12666
|
+
return;
|
|
12667
|
+
await this.sfuClient.updateMuteStates(trackTypes.map((trackType) => ({ trackType, muted })));
|
|
12668
|
+
});
|
|
12653
12669
|
};
|
|
12654
12670
|
/**
|
|
12655
12671
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
@@ -13561,7 +13577,6 @@ class StableWSConnection {
|
|
|
13561
13577
|
this.totalFailures += 1;
|
|
13562
13578
|
this._setHealth(false);
|
|
13563
13579
|
this.isConnecting = false;
|
|
13564
|
-
this.rejectConnectionOpen?.(new Error(`WebSocket error: ${event}`));
|
|
13565
13580
|
this._log(`onerror() - WS connection resulted into error`, { event });
|
|
13566
13581
|
this._reconnect();
|
|
13567
13582
|
};
|
|
@@ -14489,7 +14504,7 @@ class StreamClient {
|
|
|
14489
14504
|
this.getUserAgent = () => {
|
|
14490
14505
|
if (!this.cachedUserAgent) {
|
|
14491
14506
|
const { clientAppIdentifier = {} } = this.options;
|
|
14492
|
-
const { sdkName = 'js', sdkVersion = "1.27.
|
|
14507
|
+
const { sdkName = 'js', sdkVersion = "1.27.1", ...extras } = clientAppIdentifier;
|
|
14493
14508
|
this.cachedUserAgent = [
|
|
14494
14509
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14495
14510
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|