@stream-io/video-client 1.46.0 → 1.47.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 +18 -0
- package/dist/index.browser.es.js +65 -21
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +65 -21
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +65 -21
- package/dist/index.es.js.map +1 -1
- package/dist/src/gen/coordinator/index.d.ts +6 -0
- package/dist/src/helpers/RNSpeechDetector.d.ts +4 -2
- package/package.json +1 -1
- package/src/devices/CameraManager.ts +9 -2
- package/src/devices/DeviceManager.ts +13 -3
- package/src/devices/MicrophoneManager.ts +8 -1
- package/src/devices/SpeakerManager.ts +16 -4
- package/src/devices/__tests__/CameraManager.test.ts +32 -0
- package/src/devices/__tests__/DeviceManager.test.ts +71 -0
- package/src/devices/__tests__/MicrophoneManager.test.ts +23 -0
- package/src/devices/__tests__/SpeakerManager.test.ts +28 -0
- package/src/gen/coordinator/index.ts +6 -0
- package/src/helpers/RNSpeechDetector.ts +52 -16
- package/src/helpers/__tests__/RNSpeechDetector.test.ts +52 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.47.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.46.1...@stream-io/video-client-1.47.0) (2026-04-15)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **client:** JoinCall with hints for high scale livestream ([#2199](https://github.com/GetStream/stream-video-js/issues/2199)) ([704681a](https://github.com/GetStream/stream-video-js/commit/704681ad9ce7a0013325b6db91644e1907d0db0b))
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- **client:** align device preference persistence with permission and track end events ([#2196](https://github.com/GetStream/stream-video-js/issues/2196)) ([b4ed7c2](https://github.com/GetStream/stream-video-js/commit/b4ed7c2c6bc6fb6777a411b69747ccc36aa82f44))
|
|
14
|
+
|
|
15
|
+
## [1.46.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.46.0...@stream-io/video-client-1.46.1) (2026-04-09)
|
|
16
|
+
|
|
17
|
+
- remove listeners and stop even on permission error - rn speech detector ([f4fdd9e](https://github.com/GetStream/stream-video-js/commit/f4fdd9e1a008b52011ef18562152aad60a1f7936))
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
- ignore late ICE candidates after cleanup for RN speech detector ([#2193](https://github.com/GetStream/stream-video-js/issues/2193)) ([f8735d6](https://github.com/GetStream/stream-video-js/commit/f8735d604d86fc476b9b7e01eed0af03176625be))
|
|
22
|
+
|
|
5
23
|
## [1.46.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.45.0...@stream-io/video-client-1.46.0) (2026-04-09)
|
|
6
24
|
|
|
7
25
|
### Features
|
package/dist/index.browser.es.js
CHANGED
|
@@ -6284,7 +6284,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6284
6284
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6285
6285
|
};
|
|
6286
6286
|
|
|
6287
|
-
const version = "1.
|
|
6287
|
+
const version = "1.47.0";
|
|
6288
6288
|
const [major, minor, patch] = version.split('.');
|
|
6289
6289
|
let sdkInfo = {
|
|
6290
6290
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -10865,8 +10865,14 @@ class DeviceManager {
|
|
|
10865
10865
|
this.handleDisconnectedOrReplacedDevices();
|
|
10866
10866
|
}
|
|
10867
10867
|
if (this.devicePersistence.enabled) {
|
|
10868
|
-
this.subscriptions.push(createSubscription(combineLatest([
|
|
10869
|
-
|
|
10868
|
+
this.subscriptions.push(createSubscription(combineLatest([
|
|
10869
|
+
this.state.selectedDevice$,
|
|
10870
|
+
this.state.status$,
|
|
10871
|
+
this.state.browserPermissionState$,
|
|
10872
|
+
]), ([selectedDevice, status, browserPermissionState]) => {
|
|
10873
|
+
if (!status ||
|
|
10874
|
+
(this.isTrackStoppedDueToTrackEnd && status === 'disabled') ||
|
|
10875
|
+
browserPermissionState !== 'granted')
|
|
10870
10876
|
return;
|
|
10871
10877
|
this.persistPreference(selectedDevice, status);
|
|
10872
10878
|
}));
|
|
@@ -11631,7 +11637,10 @@ class CameraManager extends DeviceManager {
|
|
|
11631
11637
|
const shouldApplyDefaults = this.state.status === undefined &&
|
|
11632
11638
|
this.state.optimisticStatus === undefined;
|
|
11633
11639
|
let persistedPreferencesApplied = false;
|
|
11634
|
-
|
|
11640
|
+
const permissionState = await firstValueFrom(this.state.browserPermissionState$);
|
|
11641
|
+
if (shouldApplyDefaults &&
|
|
11642
|
+
this.devicePersistence.enabled &&
|
|
11643
|
+
permissionState === 'granted') {
|
|
11635
11644
|
persistedPreferencesApplied =
|
|
11636
11645
|
await this.applyPersistedPreferences(enabledInCallType);
|
|
11637
11646
|
}
|
|
@@ -11927,35 +11936,43 @@ class RNSpeechDetector {
|
|
|
11927
11936
|
constructor(externalAudioStream) {
|
|
11928
11937
|
this.pc1 = new RTCPeerConnection({});
|
|
11929
11938
|
this.pc2 = new RTCPeerConnection({});
|
|
11939
|
+
this.isStopped = false;
|
|
11930
11940
|
this.externalAudioStream = externalAudioStream;
|
|
11931
11941
|
}
|
|
11932
11942
|
/**
|
|
11933
11943
|
* Starts the speech detection.
|
|
11934
11944
|
*/
|
|
11935
11945
|
async start(onSoundDetectedStateChanged) {
|
|
11946
|
+
let detachListeners;
|
|
11947
|
+
let unsubscribe;
|
|
11936
11948
|
try {
|
|
11949
|
+
this.isStopped = false;
|
|
11937
11950
|
const audioStream = this.externalAudioStream != null
|
|
11938
11951
|
? this.externalAudioStream
|
|
11939
11952
|
: await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
11940
11953
|
this.audioStream = audioStream;
|
|
11941
|
-
|
|
11942
|
-
this.pc2
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
// do nothing
|
|
11949
|
-
});
|
|
11950
|
-
});
|
|
11951
|
-
this.pc2.addEventListener('track', (e) => {
|
|
11954
|
+
const onPc1IceCandidate = (e) => {
|
|
11955
|
+
this.forwardIceCandidate(this.pc2, e.candidate);
|
|
11956
|
+
};
|
|
11957
|
+
const onPc2IceCandidate = (e) => {
|
|
11958
|
+
this.forwardIceCandidate(this.pc1, e.candidate);
|
|
11959
|
+
};
|
|
11960
|
+
const onTrackPc2 = (e) => {
|
|
11952
11961
|
e.streams[0].getTracks().forEach((track) => {
|
|
11953
11962
|
// In RN, the remote track is automatically added to the audio output device
|
|
11954
11963
|
// so we need to mute it to avoid hearing the audio back
|
|
11955
11964
|
// @ts-expect-error _setVolume is a private method in react-native-webrtc
|
|
11956
11965
|
track._setVolume(0);
|
|
11957
11966
|
});
|
|
11958
|
-
}
|
|
11967
|
+
};
|
|
11968
|
+
this.pc1.addEventListener('icecandidate', onPc1IceCandidate);
|
|
11969
|
+
this.pc2.addEventListener('icecandidate', onPc2IceCandidate);
|
|
11970
|
+
this.pc2.addEventListener('track', onTrackPc2);
|
|
11971
|
+
detachListeners = () => {
|
|
11972
|
+
this.pc1.removeEventListener('icecandidate', onPc1IceCandidate);
|
|
11973
|
+
this.pc2.removeEventListener('icecandidate', onPc2IceCandidate);
|
|
11974
|
+
this.pc2.removeEventListener('track', onTrackPc2);
|
|
11975
|
+
};
|
|
11959
11976
|
audioStream
|
|
11960
11977
|
.getTracks()
|
|
11961
11978
|
.forEach((track) => this.pc1.addTrack(track, audioStream));
|
|
@@ -11965,13 +11982,17 @@ class RNSpeechDetector {
|
|
|
11965
11982
|
const answer = await this.pc2.createAnswer();
|
|
11966
11983
|
await this.pc1.setRemoteDescription(answer);
|
|
11967
11984
|
await this.pc2.setLocalDescription(answer);
|
|
11968
|
-
|
|
11985
|
+
unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
|
|
11969
11986
|
return () => {
|
|
11970
|
-
|
|
11987
|
+
detachListeners?.();
|
|
11988
|
+
unsubscribe?.();
|
|
11971
11989
|
this.stop();
|
|
11972
11990
|
};
|
|
11973
11991
|
}
|
|
11974
11992
|
catch (error) {
|
|
11993
|
+
detachListeners?.();
|
|
11994
|
+
unsubscribe?.();
|
|
11995
|
+
this.stop();
|
|
11975
11996
|
const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
|
|
11976
11997
|
logger.error('error handling permissions: ', error);
|
|
11977
11998
|
return () => { };
|
|
@@ -11981,6 +12002,9 @@ class RNSpeechDetector {
|
|
|
11981
12002
|
* Stops the speech detection and releases all allocated resources.
|
|
11982
12003
|
*/
|
|
11983
12004
|
stop() {
|
|
12005
|
+
if (this.isStopped)
|
|
12006
|
+
return;
|
|
12007
|
+
this.isStopped = true;
|
|
11984
12008
|
this.pc1.close();
|
|
11985
12009
|
this.pc2.close();
|
|
11986
12010
|
if (this.externalAudioStream != null) {
|
|
@@ -12080,6 +12104,18 @@ class RNSpeechDetector {
|
|
|
12080
12104
|
this.audioStream.release();
|
|
12081
12105
|
}
|
|
12082
12106
|
}
|
|
12107
|
+
forwardIceCandidate(destination, candidate) {
|
|
12108
|
+
if (this.isStopped ||
|
|
12109
|
+
!candidate ||
|
|
12110
|
+
destination.signalingState === 'closed') {
|
|
12111
|
+
return;
|
|
12112
|
+
}
|
|
12113
|
+
destination.addIceCandidate(candidate).catch(() => {
|
|
12114
|
+
// silently ignore the error
|
|
12115
|
+
const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
|
|
12116
|
+
logger.info('cannot add ice candidate - ignoring');
|
|
12117
|
+
});
|
|
12118
|
+
}
|
|
12083
12119
|
}
|
|
12084
12120
|
|
|
12085
12121
|
class MicrophoneManager extends AudioDeviceManager {
|
|
@@ -12346,7 +12382,10 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12346
12382
|
const shouldApplyDefaults = this.state.status === undefined &&
|
|
12347
12383
|
this.state.optimisticStatus === undefined;
|
|
12348
12384
|
let persistedPreferencesApplied = false;
|
|
12349
|
-
|
|
12385
|
+
const permissionState = await firstValueFrom(this.state.browserPermissionState$);
|
|
12386
|
+
if (shouldApplyDefaults &&
|
|
12387
|
+
this.devicePersistence.enabled &&
|
|
12388
|
+
permissionState === 'granted') {
|
|
12350
12389
|
persistedPreferencesApplied = await this.applyPersistedPreferences(true);
|
|
12351
12390
|
}
|
|
12352
12391
|
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
@@ -12730,7 +12769,12 @@ class SpeakerManager {
|
|
|
12730
12769
|
}));
|
|
12731
12770
|
}
|
|
12732
12771
|
if (!isReactNative() && this.devicePersistence.enabled) {
|
|
12733
|
-
this.subscriptions.push(createSubscription(
|
|
12772
|
+
this.subscriptions.push(createSubscription(combineLatest([
|
|
12773
|
+
this.state.selectedDevice$,
|
|
12774
|
+
getAudioBrowserPermission(this.call.tracer).asStateObservable(),
|
|
12775
|
+
]), ([selectedDevice, browserPermissionState]) => {
|
|
12776
|
+
if (!selectedDevice || browserPermissionState !== 'granted')
|
|
12777
|
+
return;
|
|
12734
12778
|
this.persistSpeakerDevicePreference(selectedDevice);
|
|
12735
12779
|
}));
|
|
12736
12780
|
}
|
|
@@ -16107,7 +16151,7 @@ class StreamClient {
|
|
|
16107
16151
|
this.getUserAgent = () => {
|
|
16108
16152
|
if (!this.cachedUserAgent) {
|
|
16109
16153
|
const { clientAppIdentifier = {} } = this.options;
|
|
16110
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
16154
|
+
const { sdkName = 'js', sdkVersion = "1.47.0", ...extras } = clientAppIdentifier;
|
|
16111
16155
|
this.cachedUserAgent = [
|
|
16112
16156
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
16113
16157
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|