@stream-io/video-client 1.42.2 → 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 +19 -0
- package/dist/index.browser.es.js +98 -24
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +99 -25
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +98 -24
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +42 -1
- package/dist/src/devices/MicrophoneManager.d.ts +2 -0
- package/dist/src/helpers/no-audio-detector.d.ts +1 -7
- package/package.json +1 -1
- package/src/Call.ts +90 -9
- package/src/__tests__/Call.test.ts +22 -15
- package/src/devices/MicrophoneManager.ts +21 -5
- package/src/devices/__tests__/MicrophoneManager.test.ts +52 -0
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +26 -1
- package/src/devices/__tests__/web-audio.mocks.ts +6 -2
- package/src/helpers/__tests__/no-audio-detector.test.ts +54 -28
- package/src/helpers/no-audio-detector.ts +25 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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
|
+
|
|
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)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- guard from parallel accept/reject invocations ([#2127](https://github.com/GetStream/stream-video-js/issues/2127)) ([621218f](https://github.com/GetStream/stream-video-js/commit/621218f4ab6b4623370fd66f1b02b8cb7cb1baad))
|
|
23
|
+
|
|
5
24
|
## [1.42.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.42.1...@stream-io/video-client-1.42.2) (2026-02-13)
|
|
6
25
|
|
|
7
26
|
### Bug Fixes
|
package/dist/index.browser.es.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
11486
|
-
const data = new Uint8Array(analyser.
|
|
11487
|
-
analyser.
|
|
11488
|
-
|
|
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
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
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,
|
|
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 [
|
|
11550
|
-
if (
|
|
11558
|
+
const [track] = audioStream.getAudioTracks();
|
|
11559
|
+
if (track && !track.enabled) {
|
|
11551
11560
|
state = { kind: 'IDLE' };
|
|
11552
11561
|
return;
|
|
11553
11562
|
}
|
|
11554
|
-
|
|
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
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
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) {
|
|
@@ -12446,6 +12466,7 @@ class Call {
|
|
|
12446
12466
|
this.hasJoinedOnce = false;
|
|
12447
12467
|
this.deviceSettingsAppliedOnce = false;
|
|
12448
12468
|
this.initialized = false;
|
|
12469
|
+
this.acceptRejectConcurrencyTag = Symbol('acceptRejectTag');
|
|
12449
12470
|
this.joinLeaveConcurrencyTag = Symbol('joinLeaveConcurrencyTag');
|
|
12450
12471
|
/**
|
|
12451
12472
|
* A list hooks/functions to invoke when the call is left.
|
|
@@ -12860,8 +12881,10 @@ class Call {
|
|
|
12860
12881
|
* Unless you are implementing a custom "ringing" flow, you should not use this method.
|
|
12861
12882
|
*/
|
|
12862
12883
|
this.accept = async () => {
|
|
12863
|
-
this.
|
|
12864
|
-
|
|
12884
|
+
return withoutConcurrency(this.acceptRejectConcurrencyTag, () => {
|
|
12885
|
+
this.tracer.trace('call.accept', '');
|
|
12886
|
+
return this.streamClient.post(`${this.streamClientBasePath}/accept`);
|
|
12887
|
+
});
|
|
12865
12888
|
};
|
|
12866
12889
|
/**
|
|
12867
12890
|
* Marks the incoming call as rejected.
|
|
@@ -12873,8 +12896,10 @@ class Call {
|
|
|
12873
12896
|
* @param reason the reason for rejecting the call.
|
|
12874
12897
|
*/
|
|
12875
12898
|
this.reject = async (reason = 'decline') => {
|
|
12876
|
-
this.
|
|
12877
|
-
|
|
12899
|
+
return withoutConcurrency(this.acceptRejectConcurrencyTag, () => {
|
|
12900
|
+
this.tracer.trace('call.reject', reason);
|
|
12901
|
+
return this.streamClient.post(`${this.streamClientBasePath}/reject`, { reason });
|
|
12902
|
+
});
|
|
12878
12903
|
};
|
|
12879
12904
|
/**
|
|
12880
12905
|
* Will start to watch for call related WebSocket events and initiate a call session with the server.
|
|
@@ -13701,6 +13726,7 @@ class Call {
|
|
|
13701
13726
|
if (this.state.callingState === CallingState.JOINED) {
|
|
13702
13727
|
this.logger.warn('Updating publish options after joining the call does not have an effect');
|
|
13703
13728
|
}
|
|
13729
|
+
this.tracer.trace('updatePublishOptions', options);
|
|
13704
13730
|
this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
|
|
13705
13731
|
};
|
|
13706
13732
|
/**
|
|
@@ -14126,6 +14152,15 @@ class Call {
|
|
|
14126
14152
|
type: this.type,
|
|
14127
14153
|
});
|
|
14128
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
|
+
};
|
|
14129
14164
|
/**
|
|
14130
14165
|
* Will update the call members.
|
|
14131
14166
|
*
|
|
@@ -14180,20 +14215,59 @@ class Call {
|
|
|
14180
14215
|
* Otherwise, all recordings for the current call will be returned.
|
|
14181
14216
|
*
|
|
14182
14217
|
* @param callSessionId the call session id to retrieve recordings for.
|
|
14218
|
+
* @deprecated use {@link listRecordings} instead.
|
|
14183
14219
|
*/
|
|
14184
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) => {
|
|
14185
14232
|
let endpoint = this.streamClientBasePath;
|
|
14186
14233
|
if (callSessionId) {
|
|
14187
14234
|
endpoint = `${endpoint}/${callSessionId}`;
|
|
14188
14235
|
}
|
|
14189
14236
|
return this.streamClient.get(`${endpoint}/recordings`);
|
|
14190
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
|
+
};
|
|
14191
14256
|
/**
|
|
14192
14257
|
* Retrieves the list of transcriptions for the current call.
|
|
14193
14258
|
*
|
|
14194
14259
|
* @returns the list of transcriptions.
|
|
14260
|
+
* @deprecated use {@link listTranscriptions} instead.
|
|
14195
14261
|
*/
|
|
14196
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 () => {
|
|
14197
14271
|
return this.streamClient.get(`${this.streamClientBasePath}/transcriptions`);
|
|
14198
14272
|
};
|
|
14199
14273
|
/**
|
|
@@ -15596,7 +15670,7 @@ class StreamClient {
|
|
|
15596
15670
|
this.getUserAgent = () => {
|
|
15597
15671
|
if (!this.cachedUserAgent) {
|
|
15598
15672
|
const { clientAppIdentifier = {} } = this.options;
|
|
15599
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15673
|
+
const { sdkName = 'js', sdkVersion = "1.43.0", ...extras } = clientAppIdentifier;
|
|
15600
15674
|
this.cachedUserAgent = [
|
|
15601
15675
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15602
15676
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|