@stream-io/video-client 1.37.3 → 1.38.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 +14 -0
- package/dist/index.browser.es.js +132 -16
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +132 -15
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +132 -16
- 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/dist/src/store/stateStore.d.ts +6 -0
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +2 -2
- package/src/devices/DeviceManager.ts +0 -1
- 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/src/store/CallState.ts +1 -1
- package/src/store/stateStore.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.38.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.38.0...@stream-io/video-client-1.38.1) (2025-12-08)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- added call state update for handling case when call.ring event as not triggered ([#2035](https://github.com/GetStream/stream-video-js/issues/2035)) ([3c79665](https://github.com/GetStream/stream-video-js/commit/3c79665323ad5172d3af35e9ee2f86655ac11670))
|
|
10
|
+
- **state:** ensure stable empty array for participant predicates ([#2036](https://github.com/GetStream/stream-video-js/issues/2036)) ([1aa72c8](https://github.com/GetStream/stream-video-js/commit/1aa72c8daf482bd157866960b4b9a92e272ac90b)), closes [#2034](https://github.com/GetStream/stream-video-js/issues/2034) [#2008](https://github.com/GetStream/stream-video-js/issues/2008)
|
|
11
|
+
|
|
12
|
+
## [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)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **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))
|
|
17
|
+
- **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))
|
|
18
|
+
|
|
5
19
|
## [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)
|
|
6
20
|
|
|
7
21
|
- 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))
|
package/dist/index.browser.es.js
CHANGED
|
@@ -4485,6 +4485,19 @@ class StreamVideoWriteableStateStore {
|
|
|
4485
4485
|
this.setCalls((calls) => [...calls, call]);
|
|
4486
4486
|
}
|
|
4487
4487
|
};
|
|
4488
|
+
/**
|
|
4489
|
+
* Registers a {@link Call} object if it doesn't exist, otherwise updates it.
|
|
4490
|
+
*
|
|
4491
|
+
* @param call the call to register or update.
|
|
4492
|
+
*/
|
|
4493
|
+
this.registerOrUpdateCall = (call) => {
|
|
4494
|
+
if (this.calls.find((c) => c.cid === call.cid)) {
|
|
4495
|
+
return this.setCalls((calls) => calls.map((c) => (c.cid === call.cid ? call : c)));
|
|
4496
|
+
}
|
|
4497
|
+
else {
|
|
4498
|
+
return this.registerCall(call);
|
|
4499
|
+
}
|
|
4500
|
+
};
|
|
4488
4501
|
/**
|
|
4489
4502
|
* Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
|
|
4490
4503
|
*
|
|
@@ -4878,7 +4891,7 @@ const createStableParticipantsFilter = (predicate) => {
|
|
|
4878
4891
|
return (participants) => {
|
|
4879
4892
|
// no need to filter if there are no participants
|
|
4880
4893
|
if (!participants.length)
|
|
4881
|
-
return
|
|
4894
|
+
return empty;
|
|
4882
4895
|
// return a stable empty array if there are no remote participants
|
|
4883
4896
|
// instead of creating an empty one
|
|
4884
4897
|
const filteredParticipants = participants.filter(predicate);
|
|
@@ -6000,7 +6013,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6000
6013
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6001
6014
|
};
|
|
6002
6015
|
|
|
6003
|
-
const version = "1.
|
|
6016
|
+
const version = "1.38.1";
|
|
6004
6017
|
const [major, minor, patch] = version.split('.');
|
|
6005
6018
|
let sdkInfo = {
|
|
6006
6019
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6270,17 +6283,26 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
|
|
|
6270
6283
|
getRawStatsForTrack('subscriber'),
|
|
6271
6284
|
publisher ? getRawStatsForTrack('publisher') : undefined,
|
|
6272
6285
|
]);
|
|
6273
|
-
const process = (report, kind) =>
|
|
6274
|
-
|
|
6286
|
+
const process = (report, kind) => {
|
|
6287
|
+
const videoStats = aggregate(transform(report, { kind, trackKind: 'video', publisher }));
|
|
6288
|
+
const audioStats = aggregateAudio(transform(report, { kind, trackKind: 'audio', publisher }));
|
|
6289
|
+
return {
|
|
6290
|
+
videoStats,
|
|
6291
|
+
audioStats,
|
|
6292
|
+
};
|
|
6293
|
+
};
|
|
6294
|
+
const subscriberResult = subscriberRawStats
|
|
6275
6295
|
? process(subscriberRawStats, 'subscriber')
|
|
6276
|
-
:
|
|
6277
|
-
const
|
|
6296
|
+
: { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
|
|
6297
|
+
const publisherResult = publisherRawStats
|
|
6278
6298
|
? process(publisherRawStats, 'publisher')
|
|
6279
|
-
:
|
|
6299
|
+
: { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
|
|
6280
6300
|
state.setCallStatsReport({
|
|
6281
6301
|
datacenter,
|
|
6282
|
-
publisherStats,
|
|
6283
|
-
|
|
6302
|
+
publisherStats: publisherResult.videoStats,
|
|
6303
|
+
publisherAudioStats: publisherResult.audioStats,
|
|
6304
|
+
subscriberStats: subscriberResult.videoStats,
|
|
6305
|
+
subscriberAudioStats: subscriberResult.audioStats,
|
|
6284
6306
|
subscriberRawStats,
|
|
6285
6307
|
publisherRawStats,
|
|
6286
6308
|
participants: participantStats,
|
|
@@ -6338,6 +6360,11 @@ const transform = (report, opts) => {
|
|
|
6338
6360
|
roundTripTime = candidatePair?.currentRoundTripTime;
|
|
6339
6361
|
}
|
|
6340
6362
|
let trackType;
|
|
6363
|
+
let audioLevel;
|
|
6364
|
+
let concealedSamples;
|
|
6365
|
+
let concealmentEvents;
|
|
6366
|
+
let packetsReceived;
|
|
6367
|
+
let packetsLost;
|
|
6341
6368
|
if (kind === 'publisher' && publisher) {
|
|
6342
6369
|
const firefox = isFirefox();
|
|
6343
6370
|
const mediaSource = stats.find((s) => s.type === 'media-source' &&
|
|
@@ -6345,7 +6372,22 @@ const transform = (report, opts) => {
|
|
|
6345
6372
|
(firefox ? true : s.id === rtcStreamStats.mediaSourceId));
|
|
6346
6373
|
if (mediaSource) {
|
|
6347
6374
|
trackType = publisher.getTrackType(mediaSource.trackIdentifier);
|
|
6375
|
+
if (trackKind === 'audio' &&
|
|
6376
|
+
typeof mediaSource.audioLevel === 'number') {
|
|
6377
|
+
audioLevel = mediaSource.audioLevel;
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
else if (kind === 'subscriber' && trackKind === 'audio') {
|
|
6382
|
+
const inboundStats = rtcStreamStats;
|
|
6383
|
+
const inboundLevel = inboundStats.audioLevel;
|
|
6384
|
+
if (typeof inboundLevel === 'number') {
|
|
6385
|
+
audioLevel = inboundLevel;
|
|
6348
6386
|
}
|
|
6387
|
+
concealedSamples = inboundStats.concealedSamples;
|
|
6388
|
+
concealmentEvents = inboundStats.concealmentEvents;
|
|
6389
|
+
packetsReceived = inboundStats.packetsReceived;
|
|
6390
|
+
packetsLost = inboundStats.packetsLost;
|
|
6349
6391
|
}
|
|
6350
6392
|
return {
|
|
6351
6393
|
bytesSent: rtcStreamStats.bytesSent,
|
|
@@ -6362,6 +6404,11 @@ const transform = (report, opts) => {
|
|
|
6362
6404
|
rid: rtcStreamStats.rid,
|
|
6363
6405
|
ssrc: rtcStreamStats.ssrc,
|
|
6364
6406
|
trackType,
|
|
6407
|
+
audioLevel,
|
|
6408
|
+
concealedSamples,
|
|
6409
|
+
concealmentEvents,
|
|
6410
|
+
packetsReceived,
|
|
6411
|
+
packetsLost,
|
|
6365
6412
|
};
|
|
6366
6413
|
});
|
|
6367
6414
|
return {
|
|
@@ -6370,7 +6417,7 @@ const transform = (report, opts) => {
|
|
|
6370
6417
|
timestamp: Date.now(),
|
|
6371
6418
|
};
|
|
6372
6419
|
};
|
|
6373
|
-
const
|
|
6420
|
+
const getEmptyVideoStats = (stats) => {
|
|
6374
6421
|
return {
|
|
6375
6422
|
rawReport: stats ?? { streams: [], timestamp: Date.now() },
|
|
6376
6423
|
totalBytesSent: 0,
|
|
@@ -6386,13 +6433,28 @@ const getEmptyStats = (stats) => {
|
|
|
6386
6433
|
timestamp: Date.now(),
|
|
6387
6434
|
};
|
|
6388
6435
|
};
|
|
6436
|
+
const getEmptyAudioStats = () => {
|
|
6437
|
+
return {
|
|
6438
|
+
totalBytesSent: 0,
|
|
6439
|
+
totalBytesReceived: 0,
|
|
6440
|
+
averageJitterInMs: 0,
|
|
6441
|
+
averageRoundTripTimeInMs: 0,
|
|
6442
|
+
codec: '',
|
|
6443
|
+
codecPerTrackType: {},
|
|
6444
|
+
timestamp: Date.now(),
|
|
6445
|
+
totalConcealedSamples: 0,
|
|
6446
|
+
totalConcealmentEvents: 0,
|
|
6447
|
+
totalPacketsReceived: 0,
|
|
6448
|
+
totalPacketsLost: 0,
|
|
6449
|
+
};
|
|
6450
|
+
};
|
|
6389
6451
|
/**
|
|
6390
6452
|
* Aggregates generic stats.
|
|
6391
6453
|
*
|
|
6392
6454
|
* @param stats the stats to aggregate.
|
|
6393
6455
|
*/
|
|
6394
6456
|
const aggregate = (stats) => {
|
|
6395
|
-
const aggregatedStats =
|
|
6457
|
+
const aggregatedStats = getEmptyVideoStats(stats);
|
|
6396
6458
|
let maxArea = -1;
|
|
6397
6459
|
const area = (w, h) => w * h;
|
|
6398
6460
|
const qualityLimitationReasons = new Set();
|
|
@@ -6437,6 +6499,39 @@ const aggregate = (stats) => {
|
|
|
6437
6499
|
}
|
|
6438
6500
|
return report;
|
|
6439
6501
|
};
|
|
6502
|
+
/**
|
|
6503
|
+
* Aggregates audio stats from a stats report.
|
|
6504
|
+
*
|
|
6505
|
+
* @param stats the stats report containing audio streams.
|
|
6506
|
+
* @returns aggregated audio stats.
|
|
6507
|
+
*/
|
|
6508
|
+
const aggregateAudio = (stats) => {
|
|
6509
|
+
const streams = stats.streams;
|
|
6510
|
+
const audioStats = getEmptyAudioStats();
|
|
6511
|
+
const report = streams.reduce((acc, stream) => {
|
|
6512
|
+
acc.totalBytesSent += stream.bytesSent || 0;
|
|
6513
|
+
acc.totalBytesReceived += stream.bytesReceived || 0;
|
|
6514
|
+
acc.averageJitterInMs += stream.jitter || 0;
|
|
6515
|
+
acc.averageRoundTripTimeInMs += stream.currentRoundTripTime || 0;
|
|
6516
|
+
acc.totalConcealedSamples += stream.concealedSamples || 0;
|
|
6517
|
+
acc.totalConcealmentEvents += stream.concealmentEvents || 0;
|
|
6518
|
+
acc.totalPacketsReceived += stream.packetsReceived || 0;
|
|
6519
|
+
acc.totalPacketsLost += stream.packetsLost || 0;
|
|
6520
|
+
return acc;
|
|
6521
|
+
}, audioStats);
|
|
6522
|
+
if (streams.length > 0) {
|
|
6523
|
+
report.averageJitterInMs = Math.round((report.averageJitterInMs / streams.length) * 1000);
|
|
6524
|
+
report.averageRoundTripTimeInMs = Math.round((report.averageRoundTripTimeInMs / streams.length) * 1000);
|
|
6525
|
+
report.codec = streams[0].codec || '';
|
|
6526
|
+
report.codecPerTrackType = streams.reduce((acc, stream) => {
|
|
6527
|
+
if (stream.trackType) {
|
|
6528
|
+
acc[stream.trackType] = stream.codec || '';
|
|
6529
|
+
}
|
|
6530
|
+
return acc;
|
|
6531
|
+
}, {});
|
|
6532
|
+
}
|
|
6533
|
+
return report;
|
|
6534
|
+
};
|
|
6440
6535
|
|
|
6441
6536
|
class SfuStatsReporter {
|
|
6442
6537
|
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }) {
|
|
@@ -10320,7 +10415,6 @@ class DeviceManager {
|
|
|
10320
10415
|
}
|
|
10321
10416
|
}
|
|
10322
10417
|
async applySettingsToStream() {
|
|
10323
|
-
console.log('applySettingsToStream ');
|
|
10324
10418
|
await withCancellation(this.statusChangeConcurrencyTag, async (signal) => {
|
|
10325
10419
|
if (this.enabled) {
|
|
10326
10420
|
try {
|
|
@@ -12156,7 +12250,7 @@ class Call {
|
|
|
12156
12250
|
}
|
|
12157
12251
|
if (this.streamClient._hasConnectionID()) {
|
|
12158
12252
|
this.watching = true;
|
|
12159
|
-
this.clientStore.
|
|
12253
|
+
this.clientStore.registerOrUpdateCall(this);
|
|
12160
12254
|
}
|
|
12161
12255
|
await this.applyDeviceConfig(response.call.settings, false);
|
|
12162
12256
|
return response;
|
|
@@ -12177,7 +12271,7 @@ class Call {
|
|
|
12177
12271
|
}
|
|
12178
12272
|
if (this.streamClient._hasConnectionID()) {
|
|
12179
12273
|
this.watching = true;
|
|
12180
|
-
this.clientStore.
|
|
12274
|
+
this.clientStore.registerOrUpdateCall(this);
|
|
12181
12275
|
}
|
|
12182
12276
|
await this.applyDeviceConfig(response.call.settings, false);
|
|
12183
12277
|
return response;
|
|
@@ -14918,7 +15012,7 @@ class StreamClient {
|
|
|
14918
15012
|
this.getUserAgent = () => {
|
|
14919
15013
|
if (!this.cachedUserAgent) {
|
|
14920
15014
|
const { clientAppIdentifier = {} } = this.options;
|
|
14921
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15015
|
+
const { sdkName = 'js', sdkVersion = "1.38.1", ...extras } = clientAppIdentifier;
|
|
14922
15016
|
this.cachedUserAgent = [
|
|
14923
15017
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14924
15018
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -15510,5 +15604,27 @@ class StreamVideoClient {
|
|
|
15510
15604
|
}
|
|
15511
15605
|
StreamVideoClient._instances = new Map();
|
|
15512
15606
|
|
|
15513
|
-
|
|
15607
|
+
/**
|
|
15608
|
+
* Formats large numbers into a compact, human-friendly form: 1k, 1.5k, 2M, etc.
|
|
15609
|
+
*/
|
|
15610
|
+
const humanize = (n) => {
|
|
15611
|
+
if (n < 1000)
|
|
15612
|
+
return String(n);
|
|
15613
|
+
const units = [
|
|
15614
|
+
{ value: 1000000000, suffix: 'B' },
|
|
15615
|
+
{ value: 1000000, suffix: 'M' },
|
|
15616
|
+
{ value: 1000, suffix: 'k' },
|
|
15617
|
+
];
|
|
15618
|
+
for (const { value, suffix } of units) {
|
|
15619
|
+
if (n >= value) {
|
|
15620
|
+
const num = n / value;
|
|
15621
|
+
const precision = num < 100 ? 1 : 0; // show one decimal only for small leading numbers
|
|
15622
|
+
const formatted = num.toFixed(precision).replace(/\.0$/g, '');
|
|
15623
|
+
return `${formatted}${suffix}`;
|
|
15624
|
+
}
|
|
15625
|
+
}
|
|
15626
|
+
return String(n);
|
|
15627
|
+
};
|
|
15628
|
+
|
|
15629
|
+
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 };
|
|
15514
15630
|
//# sourceMappingURL=index.browser.es.js.map
|