@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 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))
@@ -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 participants;
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.37.3";
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) => aggregate(transform(report, { kind, trackKind: 'video', publisher }));
6274
- const subscriberStats = subscriberRawStats
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
- : getEmptyStats();
6277
- const publisherStats = publisherRawStats
6296
+ : { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
6297
+ const publisherResult = publisherRawStats
6278
6298
  ? process(publisherRawStats, 'publisher')
6279
- : getEmptyStats();
6299
+ : { videoStats: getEmptyVideoStats(), audioStats: getEmptyAudioStats() };
6280
6300
  state.setCallStatsReport({
6281
6301
  datacenter,
6282
- publisherStats,
6283
- subscriberStats,
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 getEmptyStats = (stats) => {
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 = getEmptyStats(stats);
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.registerCall(this);
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.registerCall(this);
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.37.3", ...extras } = clientAppIdentifier;
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
- 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 };
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