@stream-io/video-client 1.42.3 → 1.43.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,19 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.43.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.42.3...@stream-io/video-client-1.43.0) (2026-02-20)
6
+
7
+ - **client:** trace updatePublishOptions overrides ([#2136](https://github.com/GetStream/stream-video-js/issues/2136)) ([bcc1e92](https://github.com/GetStream/stream-video-js/commit/bcc1e92ac89374324a57d1df85be38a2661a4c53))
8
+
9
+ ### Features
10
+
11
+ - **client:** add list recording APIs and deprecate query methods ([#2135](https://github.com/GetStream/stream-video-js/issues/2135)) ([5331cb5](https://github.com/GetStream/stream-video-js/commit/5331cb5205466dc052c729fb07d84209208af362))
12
+
13
+ ### Bug Fixes
14
+
15
+ - **client:** harden flat-line no-audio detection ([#2131](https://github.com/GetStream/stream-video-js/issues/2131)) ([9c2aa22](https://github.com/GetStream/stream-video-js/commit/9c2aa222b189c5e24510430dfddbf164555abf1c))
16
+ - **client:** prevent stale speaking-while-muted detector ([#2130](https://github.com/GetStream/stream-video-js/issues/2130)) ([e5c408d](https://github.com/GetStream/stream-video-js/commit/e5c408d73de1b8f20e775642b0b19eb0ffd979a8))
17
+
5
18
  ## [1.42.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.42.2...@stream-io/video-client-1.42.3) (2026-02-16)
6
19
 
7
20
  ### Bug Fixes
@@ -6231,7 +6231,7 @@ const getSdkVersion = (sdk) => {
6231
6231
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6232
6232
  };
6233
6233
 
6234
- const version = "1.42.3";
6234
+ const version = "1.43.0";
6235
6235
  const [major, minor, patch] = version.split('.');
6236
6236
  let sdkInfo = {
6237
6237
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -11480,12 +11480,21 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
11480
11480
  };
11481
11481
 
11482
11482
  /**
11483
- * Analyzes frequency data to determine if audio is being captured.
11483
+ * Analyzes time-domain waveform data to determine if audio is being captured.
11484
+ * Uses the waveform RMS around the 128 midpoint for robust silence detection.
11484
11485
  */
11485
- const hasAudio = (analyser, threshold) => {
11486
- const data = new Uint8Array(analyser.frequencyBinCount);
11487
- analyser.getByteFrequencyData(data);
11488
- return data.some((value) => value > threshold);
11486
+ const hasAudio = (analyser) => {
11487
+ const data = new Uint8Array(analyser.fftSize);
11488
+ analyser.getByteTimeDomainData(data);
11489
+ let squareSum = 0;
11490
+ for (const sample of data) {
11491
+ const centered = sample - 128;
11492
+ // Ignore tiny quantization/jitter around midpoint (e.g. 127/128 samples).
11493
+ const signal = Math.abs(centered) <= 1 ? 0 : centered;
11494
+ squareSum += signal * signal;
11495
+ }
11496
+ const rms = Math.sqrt(squareSum / data.length);
11497
+ return rms > 0;
11489
11498
  };
11490
11499
  /** Helper for "no event" transitions */
11491
11500
  const noEmit = (nextState) => ({
@@ -11499,9 +11508,9 @@ const emit = (capturesAudio, nextState) => ({ shouldEmit: true, nextState, captu
11499
11508
  */
11500
11509
  const transitionState = (state, audioDetected, options) => {
11501
11510
  if (audioDetected) {
11502
- return state.kind === 'IDLE' || state.kind === 'EMITTING'
11503
- ? emit(true, state)
11504
- : noEmit(state);
11511
+ // Any observed audio means the microphone is capturing.
11512
+ // Emit recovery/success and let the caller stop the detector.
11513
+ return emit(true, { kind: 'IDLE' });
11505
11514
  }
11506
11515
  const { noAudioThresholdMs, emitIntervalMs } = options;
11507
11516
  const now = Date.now();
@@ -11542,16 +11551,17 @@ const createAudioAnalyzer = (audioStream, fftSize) => {
11542
11551
  * @returns a cleanup function which once invoked stops the no-audio detector.
11543
11552
  */
11544
11553
  const createNoAudioDetector = (audioStream, options) => {
11545
- const { detectionFrequencyInMs = 350, audioLevelThreshold = 0, fftSize = 256, onCaptureStatusChange, } = options;
11554
+ const { detectionFrequencyInMs = 350, fftSize = 512, onCaptureStatusChange, } = options;
11546
11555
  let state = { kind: 'IDLE' };
11547
11556
  const { audioContext, analyser } = createAudioAnalyzer(audioStream, fftSize);
11548
11557
  const detectionIntervalId = setInterval(() => {
11549
- const [audioTrack] = audioStream.getAudioTracks();
11550
- if (!audioTrack?.enabled || audioTrack.readyState === 'ended') {
11558
+ const [track] = audioStream.getAudioTracks();
11559
+ if (track && !track.enabled) {
11551
11560
  state = { kind: 'IDLE' };
11552
11561
  return;
11553
11562
  }
11554
- const audioDetected = hasAudio(analyser, audioLevelThreshold);
11563
+ // Missing or ended track is treated as no-audio to surface abrupt capture loss.
11564
+ const audioDetected = track?.readyState === 'live' && hasAudio(analyser);
11555
11565
  const transition = transitionState(state, audioDetected, options);
11556
11566
  state = transition.nextState;
11557
11567
  if (!transition.shouldEmit)
@@ -12024,6 +12034,9 @@ class MicrophoneManager extends AudioDeviceManager {
12024
12034
  }
12025
12035
  async startSpeakingWhileMutedDetection(deviceId) {
12026
12036
  await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
12037
+ if (this.soundDetectorCleanup && this.soundDetectorDeviceId === deviceId)
12038
+ return;
12039
+ await this.teardownSpeakingWhileMutedDetection();
12027
12040
  if (isReactNative()) {
12028
12041
  this.rnSpeechDetector = new RNSpeechDetector();
12029
12042
  const unsubscribe = await this.rnSpeechDetector.start((event) => {
@@ -12044,16 +12057,23 @@ class MicrophoneManager extends AudioDeviceManager {
12044
12057
  this.state.setSpeakingWhileMuted(event.isSoundDetected);
12045
12058
  });
12046
12059
  }
12060
+ this.soundDetectorDeviceId = deviceId;
12047
12061
  });
12048
12062
  }
12049
12063
  async stopSpeakingWhileMutedDetection() {
12050
12064
  await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
12051
- if (!this.soundDetectorCleanup)
12052
- return;
12053
- const soundDetectorCleanup = this.soundDetectorCleanup;
12054
- this.soundDetectorCleanup = undefined;
12055
- this.state.setSpeakingWhileMuted(false);
12056
- await soundDetectorCleanup();
12065
+ return this.teardownSpeakingWhileMutedDetection();
12066
+ });
12067
+ }
12068
+ async teardownSpeakingWhileMutedDetection() {
12069
+ const soundDetectorCleanup = this.soundDetectorCleanup;
12070
+ this.soundDetectorCleanup = undefined;
12071
+ this.soundDetectorDeviceId = undefined;
12072
+ this.state.setSpeakingWhileMuted(false);
12073
+ if (!soundDetectorCleanup)
12074
+ return;
12075
+ await soundDetectorCleanup().catch((err) => {
12076
+ this.logger.warn('Failed to stop speaking while muted detector', err);
12057
12077
  });
12058
12078
  }
12059
12079
  async hasPermission(permissionState) {
@@ -13706,6 +13726,7 @@ class Call {
13706
13726
  if (this.state.callingState === CallingState.JOINED) {
13707
13727
  this.logger.warn('Updating publish options after joining the call does not have an effect');
13708
13728
  }
13729
+ this.tracer.trace('updatePublishOptions', options);
13709
13730
  this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
13710
13731
  };
13711
13732
  /**
@@ -14131,6 +14152,15 @@ class Call {
14131
14152
  type: this.type,
14132
14153
  });
14133
14154
  };
14155
+ /**
14156
+ * Query call participants with optional filters.
14157
+ *
14158
+ * @param data the request data.
14159
+ * @param params optional query parameters.
14160
+ */
14161
+ this.queryParticipants = async (data = {}, params = {}) => {
14162
+ return this.streamClient.post(`${this.streamClientBasePath}/participants`, data, params);
14163
+ };
14134
14164
  /**
14135
14165
  * Will update the call members.
14136
14166
  *
@@ -14185,20 +14215,59 @@ class Call {
14185
14215
  * Otherwise, all recordings for the current call will be returned.
14186
14216
  *
14187
14217
  * @param callSessionId the call session id to retrieve recordings for.
14218
+ * @deprecated use {@link listRecordings} instead.
14188
14219
  */
14189
14220
  this.queryRecordings = async (callSessionId) => {
14221
+ return this.listRecordings(callSessionId);
14222
+ };
14223
+ /**
14224
+ * Retrieves the list of recordings for the current call or call session.
14225
+ *
14226
+ * If `callSessionId` is provided, it will return the recordings for that call session.
14227
+ * Otherwise, all recordings for the current call will be returned.
14228
+ *
14229
+ * @param callSessionId the call session id to retrieve recordings for.
14230
+ */
14231
+ this.listRecordings = async (callSessionId) => {
14190
14232
  let endpoint = this.streamClientBasePath;
14191
14233
  if (callSessionId) {
14192
14234
  endpoint = `${endpoint}/${callSessionId}`;
14193
14235
  }
14194
14236
  return this.streamClient.get(`${endpoint}/recordings`);
14195
14237
  };
14238
+ /**
14239
+ * Deletes a recording for the given call session.
14240
+ *
14241
+ * @param callSessionId the call session id that the recording belongs to.
14242
+ * @param filename the recording filename.
14243
+ */
14244
+ this.deleteRecording = async (callSessionId, filename) => {
14245
+ return this.streamClient.delete(`${this.streamClientBasePath}/${encodeURIComponent(callSessionId)}/recordings/${encodeURIComponent(filename)}`);
14246
+ };
14247
+ /**
14248
+ * Deletes a transcription for the given call session.
14249
+ *
14250
+ * @param callSessionId the call session id that the transcription belongs to.
14251
+ * @param filename the transcription filename.
14252
+ */
14253
+ this.deleteTranscription = async (callSessionId, filename) => {
14254
+ return this.streamClient.delete(`${this.streamClientBasePath}/${encodeURIComponent(callSessionId)}/transcriptions/${encodeURIComponent(filename)}`);
14255
+ };
14196
14256
  /**
14197
14257
  * Retrieves the list of transcriptions for the current call.
14198
14258
  *
14199
14259
  * @returns the list of transcriptions.
14260
+ * @deprecated use {@link listTranscriptions} instead.
14200
14261
  */
14201
14262
  this.queryTranscriptions = async () => {
14263
+ return this.listTranscriptions();
14264
+ };
14265
+ /**
14266
+ * Retrieves the list of transcriptions for the current call.
14267
+ *
14268
+ * @returns the list of transcriptions.
14269
+ */
14270
+ this.listTranscriptions = async () => {
14202
14271
  return this.streamClient.get(`${this.streamClientBasePath}/transcriptions`);
14203
14272
  };
14204
14273
  /**
@@ -15601,7 +15670,7 @@ class StreamClient {
15601
15670
  this.getUserAgent = () => {
15602
15671
  if (!this.cachedUserAgent) {
15603
15672
  const { clientAppIdentifier = {} } = this.options;
15604
- const { sdkName = 'js', sdkVersion = "1.42.3", ...extras } = clientAppIdentifier;
15673
+ const { sdkName = 'js', sdkVersion = "1.43.0", ...extras } = clientAppIdentifier;
15605
15674
  this.cachedUserAgent = [
15606
15675
  `stream-video-${sdkName}-v${sdkVersion}`,
15607
15676
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),