@stream-io/video-client 1.37.2 → 1.38.0
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 +15 -0
- package/dist/index.browser.es.js +128 -17
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +128 -16
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +128 -17
- package/dist/index.es.js.map +1 -1
- package/dist/src/helpers/humanize.d.ts +4 -0
- package/dist/src/stats/types.d.ts +32 -2
- package/index.ts +1 -0
- package/package.json +2 -2
- package/src/devices/DeviceManager.ts +0 -1
- package/src/devices/MicrophoneManager.ts +15 -4
- package/src/devices/__tests__/NoiseCancellationStub.ts +6 -2
- package/src/helpers/__tests__/humanize.test.ts +41 -0
- package/src/helpers/humanize.ts +20 -0
- package/src/stats/CallStateStatsReporter.ts +111 -10
- package/src/stats/types.ts +33 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.38.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.37.3...@stream-io/video-client-1.38.0) (2025-12-08)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **LivestreamLayout:** Enrich with mute option and humanized participant count ([#2027](https://github.com/GetStream/stream-video-js/issues/2027)) ([cdc0c4f](https://github.com/GetStream/stream-video-js/commit/cdc0c4f985ab15a6c2e184b73432911510b43f99))
|
|
10
|
+
- **react:** Extend the statistics report with audio stats ([#2020](https://github.com/GetStream/stream-video-js/issues/2020)) ([0f4df3c](https://github.com/GetStream/stream-video-js/commit/0f4df3ce5f3b865c8ef09766dd72bc33f65539f3))
|
|
11
|
+
|
|
12
|
+
## [1.37.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.37.2...@stream-io/video-client-1.37.3) (2025-11-25)
|
|
13
|
+
|
|
14
|
+
- instructions for Claude and other coding agents ([#2012](https://github.com/GetStream/stream-video-js/issues/2012)) ([08a3459](https://github.com/GetStream/stream-video-js/commit/08a345954f7cb5b1fae5a4b39b5b585bf1f631ec))
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- **noise cancellation:** delay toggling until initialization is finished ([#2014](https://github.com/GetStream/stream-video-js/issues/2014)) ([d28b8ea](https://github.com/GetStream/stream-video-js/commit/d28b8ea282322a25688ff48966b0dc10dd7e60bd))
|
|
19
|
+
|
|
5
20
|
## [1.37.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.37.1...@stream-io/video-client-1.37.2) (2025-11-20)
|
|
6
21
|
|
|
7
22
|
### Bug Fixes
|
package/dist/index.browser.es.js
CHANGED
|
@@ -6000,7 +6000,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6000
6000
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6001
6001
|
};
|
|
6002
6002
|
|
|
6003
|
-
const version = "1.
|
|
6003
|
+
const version = "1.38.0";
|
|
6004
6004
|
const [major, minor, patch] = version.split('.');
|
|
6005
6005
|
let sdkInfo = {
|
|
6006
6006
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6270,17 +6270,26 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
|
|
|
6270
6270
|
getRawStatsForTrack('subscriber'),
|
|
6271
6271
|
publisher ? getRawStatsForTrack('publisher') : undefined,
|
|
6272
6272
|
]);
|
|
6273
|
-
const process = (report, kind) =>
|
|
6274
|
-
|
|
6273
|
+
const process = (report, kind) => {
|
|
6274
|
+
const videoStats = aggregate(transform(report, { kind, trackKind: 'video', publisher }));
|
|
6275
|
+
const audioStats = aggregateAudio(transform(report, { kind, trackKind: 'audio', publisher }));
|
|
6276
|
+
return {
|
|
6277
|
+
videoStats,
|
|
6278
|
+
audioStats,
|
|
6279
|
+
};
|
|
6280
|
+
};
|
|
6281
|
+
const subscriberResult = subscriberRawStats
|
|
6275
6282
|
? process(subscriberRawStats, 'subscriber')
|
|
6276
|
-
:
|
|
6277
|
-
const
|
|
6283
|
+
: { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
|
|
6284
|
+
const publisherResult = publisherRawStats
|
|
6278
6285
|
? process(publisherRawStats, 'publisher')
|
|
6279
|
-
:
|
|
6286
|
+
: { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
|
|
6280
6287
|
state.setCallStatsReport({
|
|
6281
6288
|
datacenter,
|
|
6282
|
-
publisherStats,
|
|
6283
|
-
|
|
6289
|
+
publisherStats: publisherResult.videoStats,
|
|
6290
|
+
publisherAudioStats: publisherResult.audioStats,
|
|
6291
|
+
subscriberStats: subscriberResult.videoStats,
|
|
6292
|
+
subscriberAudioStats: subscriberResult.audioStats,
|
|
6284
6293
|
subscriberRawStats,
|
|
6285
6294
|
publisherRawStats,
|
|
6286
6295
|
participants: participantStats,
|
|
@@ -6338,6 +6347,11 @@ const transform = (report, opts) => {
|
|
|
6338
6347
|
roundTripTime = candidatePair?.currentRoundTripTime;
|
|
6339
6348
|
}
|
|
6340
6349
|
let trackType;
|
|
6350
|
+
let audioLevel;
|
|
6351
|
+
let concealedSamples;
|
|
6352
|
+
let concealmentEvents;
|
|
6353
|
+
let packetsReceived;
|
|
6354
|
+
let packetsLost;
|
|
6341
6355
|
if (kind === 'publisher' && publisher) {
|
|
6342
6356
|
const firefox = isFirefox();
|
|
6343
6357
|
const mediaSource = stats.find((s) => s.type === 'media-source' &&
|
|
@@ -6345,7 +6359,22 @@ const transform = (report, opts) => {
|
|
|
6345
6359
|
(firefox ? true : s.id === rtcStreamStats.mediaSourceId));
|
|
6346
6360
|
if (mediaSource) {
|
|
6347
6361
|
trackType = publisher.getTrackType(mediaSource.trackIdentifier);
|
|
6362
|
+
if (trackKind === 'audio' &&
|
|
6363
|
+
typeof mediaSource.audioLevel === 'number') {
|
|
6364
|
+
audioLevel = mediaSource.audioLevel;
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6368
|
+
else if (kind === 'subscriber' && trackKind === 'audio') {
|
|
6369
|
+
const inboundStats = rtcStreamStats;
|
|
6370
|
+
const inboundLevel = inboundStats.audioLevel;
|
|
6371
|
+
if (typeof inboundLevel === 'number') {
|
|
6372
|
+
audioLevel = inboundLevel;
|
|
6348
6373
|
}
|
|
6374
|
+
concealedSamples = inboundStats.concealedSamples;
|
|
6375
|
+
concealmentEvents = inboundStats.concealmentEvents;
|
|
6376
|
+
packetsReceived = inboundStats.packetsReceived;
|
|
6377
|
+
packetsLost = inboundStats.packetsLost;
|
|
6349
6378
|
}
|
|
6350
6379
|
return {
|
|
6351
6380
|
bytesSent: rtcStreamStats.bytesSent,
|
|
@@ -6362,6 +6391,11 @@ const transform = (report, opts) => {
|
|
|
6362
6391
|
rid: rtcStreamStats.rid,
|
|
6363
6392
|
ssrc: rtcStreamStats.ssrc,
|
|
6364
6393
|
trackType,
|
|
6394
|
+
audioLevel,
|
|
6395
|
+
concealedSamples,
|
|
6396
|
+
concealmentEvents,
|
|
6397
|
+
packetsReceived,
|
|
6398
|
+
packetsLost,
|
|
6365
6399
|
};
|
|
6366
6400
|
});
|
|
6367
6401
|
return {
|
|
@@ -6370,7 +6404,7 @@ const transform = (report, opts) => {
|
|
|
6370
6404
|
timestamp: Date.now(),
|
|
6371
6405
|
};
|
|
6372
6406
|
};
|
|
6373
|
-
const
|
|
6407
|
+
const getEmptyVideoStats = (stats) => {
|
|
6374
6408
|
return {
|
|
6375
6409
|
rawReport: stats ?? { streams: [], timestamp: Date.now() },
|
|
6376
6410
|
totalBytesSent: 0,
|
|
@@ -6386,13 +6420,28 @@ const getEmptyStats = (stats) => {
|
|
|
6386
6420
|
timestamp: Date.now(),
|
|
6387
6421
|
};
|
|
6388
6422
|
};
|
|
6423
|
+
const getEmptyAudioStats = () => {
|
|
6424
|
+
return {
|
|
6425
|
+
totalBytesSent: 0,
|
|
6426
|
+
totalBytesReceived: 0,
|
|
6427
|
+
averageJitterInMs: 0,
|
|
6428
|
+
averageRoundTripTimeInMs: 0,
|
|
6429
|
+
codec: '',
|
|
6430
|
+
codecPerTrackType: {},
|
|
6431
|
+
timestamp: Date.now(),
|
|
6432
|
+
totalConcealedSamples: 0,
|
|
6433
|
+
totalConcealmentEvents: 0,
|
|
6434
|
+
totalPacketsReceived: 0,
|
|
6435
|
+
totalPacketsLost: 0,
|
|
6436
|
+
};
|
|
6437
|
+
};
|
|
6389
6438
|
/**
|
|
6390
6439
|
* Aggregates generic stats.
|
|
6391
6440
|
*
|
|
6392
6441
|
* @param stats the stats to aggregate.
|
|
6393
6442
|
*/
|
|
6394
6443
|
const aggregate = (stats) => {
|
|
6395
|
-
const aggregatedStats =
|
|
6444
|
+
const aggregatedStats = getEmptyVideoStats(stats);
|
|
6396
6445
|
let maxArea = -1;
|
|
6397
6446
|
const area = (w, h) => w * h;
|
|
6398
6447
|
const qualityLimitationReasons = new Set();
|
|
@@ -6437,6 +6486,39 @@ const aggregate = (stats) => {
|
|
|
6437
6486
|
}
|
|
6438
6487
|
return report;
|
|
6439
6488
|
};
|
|
6489
|
+
/**
|
|
6490
|
+
* Aggregates audio stats from a stats report.
|
|
6491
|
+
*
|
|
6492
|
+
* @param stats the stats report containing audio streams.
|
|
6493
|
+
* @returns aggregated audio stats.
|
|
6494
|
+
*/
|
|
6495
|
+
const aggregateAudio = (stats) => {
|
|
6496
|
+
const streams = stats.streams;
|
|
6497
|
+
const audioStats = getEmptyAudioStats();
|
|
6498
|
+
const report = streams.reduce((acc, stream) => {
|
|
6499
|
+
acc.totalBytesSent += stream.bytesSent || 0;
|
|
6500
|
+
acc.totalBytesReceived += stream.bytesReceived || 0;
|
|
6501
|
+
acc.averageJitterInMs += stream.jitter || 0;
|
|
6502
|
+
acc.averageRoundTripTimeInMs += stream.currentRoundTripTime || 0;
|
|
6503
|
+
acc.totalConcealedSamples += stream.concealedSamples || 0;
|
|
6504
|
+
acc.totalConcealmentEvents += stream.concealmentEvents || 0;
|
|
6505
|
+
acc.totalPacketsReceived += stream.packetsReceived || 0;
|
|
6506
|
+
acc.totalPacketsLost += stream.packetsLost || 0;
|
|
6507
|
+
return acc;
|
|
6508
|
+
}, audioStats);
|
|
6509
|
+
if (streams.length > 0) {
|
|
6510
|
+
report.averageJitterInMs = Math.round((report.averageJitterInMs / streams.length) * 1000);
|
|
6511
|
+
report.averageRoundTripTimeInMs = Math.round((report.averageRoundTripTimeInMs / streams.length) * 1000);
|
|
6512
|
+
report.codec = streams[0].codec || '';
|
|
6513
|
+
report.codecPerTrackType = streams.reduce((acc, stream) => {
|
|
6514
|
+
if (stream.trackType) {
|
|
6515
|
+
acc[stream.trackType] = stream.codec || '';
|
|
6516
|
+
}
|
|
6517
|
+
return acc;
|
|
6518
|
+
}, {});
|
|
6519
|
+
}
|
|
6520
|
+
return report;
|
|
6521
|
+
};
|
|
6440
6522
|
|
|
6441
6523
|
class SfuStatsReporter {
|
|
6442
6524
|
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }) {
|
|
@@ -10320,7 +10402,6 @@ class DeviceManager {
|
|
|
10320
10402
|
}
|
|
10321
10403
|
}
|
|
10322
10404
|
async applySettingsToStream() {
|
|
10323
|
-
console.log('applySettingsToStream ');
|
|
10324
10405
|
await withCancellation(this.statusChangeConcurrencyTag, async (signal) => {
|
|
10325
10406
|
if (this.enabled) {
|
|
10326
10407
|
try {
|
|
@@ -11265,7 +11346,9 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11265
11346
|
})
|
|
11266
11347
|
.then((canAutoEnable) => {
|
|
11267
11348
|
if (canAutoEnable) {
|
|
11268
|
-
this.noiseCancellation?.enable()
|
|
11349
|
+
this.noiseCancellation?.enable().catch((err) => {
|
|
11350
|
+
this.logger.warn('Failed to enable noise cancellation', err);
|
|
11351
|
+
});
|
|
11269
11352
|
}
|
|
11270
11353
|
})
|
|
11271
11354
|
.catch((err) => {
|
|
@@ -11335,7 +11418,9 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11335
11418
|
canAutoEnable = await noiseCancellation.canAutoEnable();
|
|
11336
11419
|
}
|
|
11337
11420
|
if (canAutoEnable) {
|
|
11338
|
-
noiseCancellation.enable()
|
|
11421
|
+
noiseCancellation.enable().catch((err) => {
|
|
11422
|
+
this.logger.warn('Failed to enable noise cancellation', err);
|
|
11423
|
+
});
|
|
11339
11424
|
}
|
|
11340
11425
|
}
|
|
11341
11426
|
}
|
|
@@ -11415,10 +11500,14 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11415
11500
|
if (this.noiseCancellation) {
|
|
11416
11501
|
const disableAudioProcessing = profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
|
|
11417
11502
|
if (disableAudioProcessing) {
|
|
11418
|
-
this.noiseCancellation.disable()
|
|
11503
|
+
this.noiseCancellation.disable().catch((err) => {
|
|
11504
|
+
this.logger.warn('Failed to disable noise cancellation for music mode', err);
|
|
11505
|
+
}); // disable for high quality music mode
|
|
11419
11506
|
}
|
|
11420
11507
|
else {
|
|
11421
|
-
this.noiseCancellation.enable()
|
|
11508
|
+
this.noiseCancellation.enable().catch((err) => {
|
|
11509
|
+
this.logger.warn('Failed to enable noise cancellation', err);
|
|
11510
|
+
}); // restore it for other modes if available
|
|
11422
11511
|
}
|
|
11423
11512
|
}
|
|
11424
11513
|
}
|
|
@@ -14910,7 +14999,7 @@ class StreamClient {
|
|
|
14910
14999
|
this.getUserAgent = () => {
|
|
14911
15000
|
if (!this.cachedUserAgent) {
|
|
14912
15001
|
const { clientAppIdentifier = {} } = this.options;
|
|
14913
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15002
|
+
const { sdkName = 'js', sdkVersion = "1.38.0", ...extras } = clientAppIdentifier;
|
|
14914
15003
|
this.cachedUserAgent = [
|
|
14915
15004
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14916
15005
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -15502,5 +15591,27 @@ class StreamVideoClient {
|
|
|
15502
15591
|
}
|
|
15503
15592
|
StreamVideoClient._instances = new Map();
|
|
15504
15593
|
|
|
15505
|
-
|
|
15594
|
+
/**
|
|
15595
|
+
* Formats large numbers into a compact, human-friendly form: 1k, 1.5k, 2M, etc.
|
|
15596
|
+
*/
|
|
15597
|
+
const humanize = (n) => {
|
|
15598
|
+
if (n < 1000)
|
|
15599
|
+
return String(n);
|
|
15600
|
+
const units = [
|
|
15601
|
+
{ value: 1000000000, suffix: 'B' },
|
|
15602
|
+
{ value: 1000000, suffix: 'M' },
|
|
15603
|
+
{ value: 1000, suffix: 'k' },
|
|
15604
|
+
];
|
|
15605
|
+
for (const { value, suffix } of units) {
|
|
15606
|
+
if (n >= value) {
|
|
15607
|
+
const num = n / value;
|
|
15608
|
+
const precision = num < 100 ? 1 : 0; // show one decimal only for small leading numbers
|
|
15609
|
+
const formatted = num.toFixed(precision).replace(/\.0$/g, '');
|
|
15610
|
+
return `${formatted}${suffix}`;
|
|
15611
|
+
}
|
|
15612
|
+
}
|
|
15613
|
+
return String(n);
|
|
15614
|
+
};
|
|
15615
|
+
|
|
15616
|
+
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 };
|
|
15506
15617
|
//# sourceMappingURL=index.browser.es.js.map
|