@stream-io/video-client 1.37.3 → 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 CHANGED
@@ -2,6 +2,13 @@
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
+
5
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)
6
13
 
7
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))
@@ -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.37.3";
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) => aggregate(transform(report, { kind, trackKind: 'video', publisher }));
6274
- const subscriberStats = subscriberRawStats
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
- : getEmptyStats();
6277
- const publisherStats = publisherRawStats
6283
+ : { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
6284
+ const publisherResult = publisherRawStats
6278
6285
  ? process(publisherRawStats, 'publisher')
6279
- : getEmptyStats();
6286
+ : { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
6280
6287
  state.setCallStatsReport({
6281
6288
  datacenter,
6282
- publisherStats,
6283
- subscriberStats,
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 getEmptyStats = (stats) => {
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 = getEmptyStats(stats);
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 {
@@ -14918,7 +14999,7 @@ class StreamClient {
14918
14999
  this.getUserAgent = () => {
14919
15000
  if (!this.cachedUserAgent) {
14920
15001
  const { clientAppIdentifier = {} } = this.options;
14921
- const { sdkName = 'js', sdkVersion = "1.37.3", ...extras } = clientAppIdentifier;
15002
+ const { sdkName = 'js', sdkVersion = "1.38.0", ...extras } = clientAppIdentifier;
14922
15003
  this.cachedUserAgent = [
14923
15004
  `stream-video-${sdkName}-v${sdkVersion}`,
14924
15005
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -15510,5 +15591,27 @@ class StreamVideoClient {
15510
15591
  }
15511
15592
  StreamVideoClient._instances = new Map();
15512
15593
 
15513
- 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, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
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 };
15514
15617
  //# sourceMappingURL=index.browser.es.js.map