@stream-io/video-client 1.47.0 → 1.48.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,12 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [1.48.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.47.0...@stream-io/video-client-1.48.0) (2026-04-28)
6
+
7
+ ### Features
8
+
9
+ - **rn:** remove peer connection usage in speech detection ([#2200](https://github.com/GetStream/stream-video-js/issues/2200)) ([1c73d10](https://github.com/GetStream/stream-video-js/commit/1c73d10cc25761c08a8f9350e44137afaee33acf))
10
+
5
11
  ## [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
12
 
7
13
  ### Features
@@ -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.47.0";
6287
+ const version = "1.48.0";
6288
6288
  const [major, minor, patch] = version.split('.');
6289
6289
  let sdkInfo = {
6290
6290
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -11932,192 +11932,6 @@ const createNoAudioDetector = (audioStream, options) => {
11932
11932
  return stop;
11933
11933
  };
11934
11934
 
11935
- class RNSpeechDetector {
11936
- constructor(externalAudioStream) {
11937
- this.pc1 = new RTCPeerConnection({});
11938
- this.pc2 = new RTCPeerConnection({});
11939
- this.isStopped = false;
11940
- this.externalAudioStream = externalAudioStream;
11941
- }
11942
- /**
11943
- * Starts the speech detection.
11944
- */
11945
- async start(onSoundDetectedStateChanged) {
11946
- let detachListeners;
11947
- let unsubscribe;
11948
- try {
11949
- this.isStopped = false;
11950
- const audioStream = this.externalAudioStream != null
11951
- ? this.externalAudioStream
11952
- : await navigator.mediaDevices.getUserMedia({ audio: true });
11953
- this.audioStream = audioStream;
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) => {
11961
- e.streams[0].getTracks().forEach((track) => {
11962
- // In RN, the remote track is automatically added to the audio output device
11963
- // so we need to mute it to avoid hearing the audio back
11964
- // @ts-expect-error _setVolume is a private method in react-native-webrtc
11965
- track._setVolume(0);
11966
- });
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
- };
11976
- audioStream
11977
- .getTracks()
11978
- .forEach((track) => this.pc1.addTrack(track, audioStream));
11979
- const offer = await this.pc1.createOffer({});
11980
- await this.pc2.setRemoteDescription(offer);
11981
- await this.pc1.setLocalDescription(offer);
11982
- const answer = await this.pc2.createAnswer();
11983
- await this.pc1.setRemoteDescription(answer);
11984
- await this.pc2.setLocalDescription(answer);
11985
- unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
11986
- return () => {
11987
- detachListeners?.();
11988
- unsubscribe?.();
11989
- this.stop();
11990
- };
11991
- }
11992
- catch (error) {
11993
- detachListeners?.();
11994
- unsubscribe?.();
11995
- this.stop();
11996
- const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
11997
- logger.error('error handling permissions: ', error);
11998
- return () => { };
11999
- }
12000
- }
12001
- /**
12002
- * Stops the speech detection and releases all allocated resources.
12003
- */
12004
- stop() {
12005
- if (this.isStopped)
12006
- return;
12007
- this.isStopped = true;
12008
- this.pc1.close();
12009
- this.pc2.close();
12010
- if (this.externalAudioStream != null) {
12011
- this.externalAudioStream = undefined;
12012
- }
12013
- else {
12014
- this.cleanupAudioStream();
12015
- }
12016
- }
12017
- /**
12018
- * Public method that detects the audio levels and returns the status.
12019
- */
12020
- onSpeakingDetectedStateChange(onSoundDetectedStateChanged) {
12021
- const initialBaselineNoiseLevel = 0.13;
12022
- let baselineNoiseLevel = initialBaselineNoiseLevel;
12023
- let speechDetected = false;
12024
- let speechTimer;
12025
- let silenceTimer;
12026
- const audioLevelHistory = []; // Store recent audio levels for smoother detection
12027
- const historyLength = 10;
12028
- const silenceThreshold = 1.1;
12029
- const resetThreshold = 0.9;
12030
- const speechTimeout = 500; // Speech is set to true after 500ms of audio detection
12031
- const silenceTimeout = 5000; // Reset baseline after 5 seconds of silence
12032
- const checkAudioLevel = async () => {
12033
- try {
12034
- const stats = await this.pc1.getStats();
12035
- const report = flatten(stats);
12036
- // Audio levels are present inside stats of type `media-source` and of kind `audio`
12037
- const audioMediaSourceStats = report.find((stat) => stat.type === 'media-source' &&
12038
- stat.kind === 'audio');
12039
- if (audioMediaSourceStats) {
12040
- const { audioLevel } = audioMediaSourceStats;
12041
- if (audioLevel) {
12042
- // Update audio level history (with max historyLength sized array)
12043
- audioLevelHistory.push(audioLevel);
12044
- if (audioLevelHistory.length > historyLength) {
12045
- audioLevelHistory.shift();
12046
- }
12047
- // Calculate average audio level
12048
- const avgAudioLevel = audioLevelHistory.reduce((a, b) => a + b, 0) /
12049
- audioLevelHistory.length;
12050
- // Update baseline (if necessary) based on silence detection
12051
- if (avgAudioLevel < baselineNoiseLevel * silenceThreshold) {
12052
- if (!silenceTimer) {
12053
- silenceTimer = setTimeout(() => {
12054
- baselineNoiseLevel = Math.min(avgAudioLevel * resetThreshold, initialBaselineNoiseLevel);
12055
- }, silenceTimeout);
12056
- }
12057
- }
12058
- else {
12059
- clearTimeout(silenceTimer);
12060
- silenceTimer = undefined;
12061
- }
12062
- // Speech detection with hysteresis
12063
- if (avgAudioLevel > baselineNoiseLevel * 1.5) {
12064
- if (!speechDetected) {
12065
- speechDetected = true;
12066
- onSoundDetectedStateChanged({
12067
- isSoundDetected: true,
12068
- audioLevel,
12069
- });
12070
- }
12071
- clearTimeout(speechTimer);
12072
- speechTimer = setTimeout(() => {
12073
- speechDetected = false;
12074
- onSoundDetectedStateChanged({
12075
- isSoundDetected: false,
12076
- audioLevel: 0,
12077
- });
12078
- }, speechTimeout);
12079
- }
12080
- }
12081
- }
12082
- }
12083
- catch (error) {
12084
- const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
12085
- logger.error('error checking audio level from stats', error);
12086
- }
12087
- };
12088
- const intervalId = setInterval(checkAudioLevel, 250);
12089
- return () => {
12090
- clearInterval(intervalId);
12091
- clearTimeout(speechTimer);
12092
- clearTimeout(silenceTimer);
12093
- };
12094
- }
12095
- cleanupAudioStream() {
12096
- if (!this.audioStream) {
12097
- return;
12098
- }
12099
- this.audioStream.getTracks().forEach((track) => track.stop());
12100
- if (
12101
- // @ts-expect-error release() is present in react-native-webrtc
12102
- typeof this.audioStream.release === 'function') {
12103
- // @ts-expect-error called to dispose the stream in RN
12104
- this.audioStream.release();
12105
- }
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
- }
12119
- }
12120
-
12121
11935
  class MicrophoneManager extends AudioDeviceManager {
12122
11936
  constructor(call, devicePersistence, disableMode = 'stop-tracks') {
12123
11937
  super(call, new MicrophoneManagerState(disableMode, call.tracer), TrackType.AUDIO, devicePersistence);
@@ -12430,13 +12244,16 @@ class MicrophoneManager extends AudioDeviceManager {
12430
12244
  return;
12431
12245
  await this.teardownSpeakingWhileMutedDetection();
12432
12246
  if (isReactNative()) {
12433
- this.rnSpeechDetector = new RNSpeechDetector();
12434
- const unsubscribe = await this.rnSpeechDetector.start((event) => {
12247
+ const speechActivity = globalThis.streamRNVideoSDK?.nativeEvents?.speechActivity;
12248
+ if (!speechActivity) {
12249
+ this.logger.warn('Native speech activity not available, make sure the "@stream-io/react-native-webrtc" peer dependency version is satisfied');
12250
+ return;
12251
+ }
12252
+ const unsubscribe = speechActivity.subscribe((event) => {
12435
12253
  this.state.setSpeakingWhileMuted(event.isSoundDetected);
12436
12254
  });
12437
12255
  this.soundDetectorCleanup = async () => {
12438
12256
  unsubscribe();
12439
- this.rnSpeechDetector = undefined;
12440
12257
  };
12441
12258
  }
12442
12259
  else {
@@ -16151,7 +15968,7 @@ class StreamClient {
16151
15968
  this.getUserAgent = () => {
16152
15969
  if (!this.cachedUserAgent) {
16153
15970
  const { clientAppIdentifier = {} } = this.options;
16154
- const { sdkName = 'js', sdkVersion = "1.47.0", ...extras } = clientAppIdentifier;
15971
+ const { sdkName = 'js', sdkVersion = "1.48.0", ...extras } = clientAppIdentifier;
16155
15972
  this.cachedUserAgent = [
16156
15973
  `stream-video-${sdkName}-v${sdkVersion}`,
16157
15974
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -16787,5 +16604,5 @@ const humanize = (n) => {
16787
16604
  return String(n);
16788
16605
  };
16789
16606
 
16790
- export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, 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$1 as 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 };
16607
+ export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, 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$1 as 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 };
16791
16608
  //# sourceMappingURL=index.browser.es.js.map