@stream-io/video-client 1.46.1 → 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/dist/index.cjs.js CHANGED
@@ -6304,7 +6304,7 @@ const getSdkVersion = (sdk) => {
6304
6304
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
6305
6305
  };
6306
6306
 
6307
- const version = "1.46.1";
6307
+ const version = "1.48.0";
6308
6308
  const [major, minor, patch] = version.split('.');
6309
6309
  let sdkInfo = {
6310
6310
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -10885,8 +10885,14 @@ class DeviceManager {
10885
10885
  this.handleDisconnectedOrReplacedDevices();
10886
10886
  }
10887
10887
  if (this.devicePersistence.enabled) {
10888
- this.subscriptions.push(createSubscription(rxjs.combineLatest([this.state.selectedDevice$, this.state.status$]), ([selectedDevice, status]) => {
10889
- if (!status)
10888
+ this.subscriptions.push(createSubscription(rxjs.combineLatest([
10889
+ this.state.selectedDevice$,
10890
+ this.state.status$,
10891
+ this.state.browserPermissionState$,
10892
+ ]), ([selectedDevice, status, browserPermissionState]) => {
10893
+ if (!status ||
10894
+ (this.isTrackStoppedDueToTrackEnd && status === 'disabled') ||
10895
+ browserPermissionState !== 'granted')
10890
10896
  return;
10891
10897
  this.persistPreference(selectedDevice, status);
10892
10898
  }));
@@ -11651,7 +11657,10 @@ class CameraManager extends DeviceManager {
11651
11657
  const shouldApplyDefaults = this.state.status === undefined &&
11652
11658
  this.state.optimisticStatus === undefined;
11653
11659
  let persistedPreferencesApplied = false;
11654
- if (shouldApplyDefaults && this.devicePersistence.enabled) {
11660
+ const permissionState = await rxjs.firstValueFrom(this.state.browserPermissionState$);
11661
+ if (shouldApplyDefaults &&
11662
+ this.devicePersistence.enabled &&
11663
+ permissionState === 'granted') {
11655
11664
  persistedPreferencesApplied =
11656
11665
  await this.applyPersistedPreferences(enabledInCallType);
11657
11666
  }
@@ -11943,192 +11952,6 @@ const createNoAudioDetector = (audioStream, options) => {
11943
11952
  return stop;
11944
11953
  };
11945
11954
 
11946
- class RNSpeechDetector {
11947
- constructor(externalAudioStream) {
11948
- this.pc1 = new RTCPeerConnection({});
11949
- this.pc2 = new RTCPeerConnection({});
11950
- this.isStopped = false;
11951
- this.externalAudioStream = externalAudioStream;
11952
- }
11953
- /**
11954
- * Starts the speech detection.
11955
- */
11956
- async start(onSoundDetectedStateChanged) {
11957
- let detachListeners;
11958
- let unsubscribe;
11959
- try {
11960
- this.isStopped = false;
11961
- const audioStream = this.externalAudioStream != null
11962
- ? this.externalAudioStream
11963
- : await navigator.mediaDevices.getUserMedia({ audio: true });
11964
- this.audioStream = audioStream;
11965
- const onPc1IceCandidate = (e) => {
11966
- this.forwardIceCandidate(this.pc2, e.candidate);
11967
- };
11968
- const onPc2IceCandidate = (e) => {
11969
- this.forwardIceCandidate(this.pc1, e.candidate);
11970
- };
11971
- const onTrackPc2 = (e) => {
11972
- e.streams[0].getTracks().forEach((track) => {
11973
- // In RN, the remote track is automatically added to the audio output device
11974
- // so we need to mute it to avoid hearing the audio back
11975
- // @ts-expect-error _setVolume is a private method in react-native-webrtc
11976
- track._setVolume(0);
11977
- });
11978
- };
11979
- this.pc1.addEventListener('icecandidate', onPc1IceCandidate);
11980
- this.pc2.addEventListener('icecandidate', onPc2IceCandidate);
11981
- this.pc2.addEventListener('track', onTrackPc2);
11982
- detachListeners = () => {
11983
- this.pc1.removeEventListener('icecandidate', onPc1IceCandidate);
11984
- this.pc2.removeEventListener('icecandidate', onPc2IceCandidate);
11985
- this.pc2.removeEventListener('track', onTrackPc2);
11986
- };
11987
- audioStream
11988
- .getTracks()
11989
- .forEach((track) => this.pc1.addTrack(track, audioStream));
11990
- const offer = await this.pc1.createOffer({});
11991
- await this.pc2.setRemoteDescription(offer);
11992
- await this.pc1.setLocalDescription(offer);
11993
- const answer = await this.pc2.createAnswer();
11994
- await this.pc1.setRemoteDescription(answer);
11995
- await this.pc2.setLocalDescription(answer);
11996
- unsubscribe = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
11997
- return () => {
11998
- detachListeners?.();
11999
- unsubscribe?.();
12000
- this.stop();
12001
- };
12002
- }
12003
- catch (error) {
12004
- detachListeners?.();
12005
- unsubscribe?.();
12006
- this.stop();
12007
- const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
12008
- logger.error('error handling permissions: ', error);
12009
- return () => { };
12010
- }
12011
- }
12012
- /**
12013
- * Stops the speech detection and releases all allocated resources.
12014
- */
12015
- stop() {
12016
- if (this.isStopped)
12017
- return;
12018
- this.isStopped = true;
12019
- this.pc1.close();
12020
- this.pc2.close();
12021
- if (this.externalAudioStream != null) {
12022
- this.externalAudioStream = undefined;
12023
- }
12024
- else {
12025
- this.cleanupAudioStream();
12026
- }
12027
- }
12028
- /**
12029
- * Public method that detects the audio levels and returns the status.
12030
- */
12031
- onSpeakingDetectedStateChange(onSoundDetectedStateChanged) {
12032
- const initialBaselineNoiseLevel = 0.13;
12033
- let baselineNoiseLevel = initialBaselineNoiseLevel;
12034
- let speechDetected = false;
12035
- let speechTimer;
12036
- let silenceTimer;
12037
- const audioLevelHistory = []; // Store recent audio levels for smoother detection
12038
- const historyLength = 10;
12039
- const silenceThreshold = 1.1;
12040
- const resetThreshold = 0.9;
12041
- const speechTimeout = 500; // Speech is set to true after 500ms of audio detection
12042
- const silenceTimeout = 5000; // Reset baseline after 5 seconds of silence
12043
- const checkAudioLevel = async () => {
12044
- try {
12045
- const stats = await this.pc1.getStats();
12046
- const report = flatten(stats);
12047
- // Audio levels are present inside stats of type `media-source` and of kind `audio`
12048
- const audioMediaSourceStats = report.find((stat) => stat.type === 'media-source' &&
12049
- stat.kind === 'audio');
12050
- if (audioMediaSourceStats) {
12051
- const { audioLevel } = audioMediaSourceStats;
12052
- if (audioLevel) {
12053
- // Update audio level history (with max historyLength sized array)
12054
- audioLevelHistory.push(audioLevel);
12055
- if (audioLevelHistory.length > historyLength) {
12056
- audioLevelHistory.shift();
12057
- }
12058
- // Calculate average audio level
12059
- const avgAudioLevel = audioLevelHistory.reduce((a, b) => a + b, 0) /
12060
- audioLevelHistory.length;
12061
- // Update baseline (if necessary) based on silence detection
12062
- if (avgAudioLevel < baselineNoiseLevel * silenceThreshold) {
12063
- if (!silenceTimer) {
12064
- silenceTimer = setTimeout(() => {
12065
- baselineNoiseLevel = Math.min(avgAudioLevel * resetThreshold, initialBaselineNoiseLevel);
12066
- }, silenceTimeout);
12067
- }
12068
- }
12069
- else {
12070
- clearTimeout(silenceTimer);
12071
- silenceTimer = undefined;
12072
- }
12073
- // Speech detection with hysteresis
12074
- if (avgAudioLevel > baselineNoiseLevel * 1.5) {
12075
- if (!speechDetected) {
12076
- speechDetected = true;
12077
- onSoundDetectedStateChanged({
12078
- isSoundDetected: true,
12079
- audioLevel,
12080
- });
12081
- }
12082
- clearTimeout(speechTimer);
12083
- speechTimer = setTimeout(() => {
12084
- speechDetected = false;
12085
- onSoundDetectedStateChanged({
12086
- isSoundDetected: false,
12087
- audioLevel: 0,
12088
- });
12089
- }, speechTimeout);
12090
- }
12091
- }
12092
- }
12093
- }
12094
- catch (error) {
12095
- const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
12096
- logger.error('error checking audio level from stats', error);
12097
- }
12098
- };
12099
- const intervalId = setInterval(checkAudioLevel, 250);
12100
- return () => {
12101
- clearInterval(intervalId);
12102
- clearTimeout(speechTimer);
12103
- clearTimeout(silenceTimer);
12104
- };
12105
- }
12106
- cleanupAudioStream() {
12107
- if (!this.audioStream) {
12108
- return;
12109
- }
12110
- this.audioStream.getTracks().forEach((track) => track.stop());
12111
- if (
12112
- // @ts-expect-error release() is present in react-native-webrtc
12113
- typeof this.audioStream.release === 'function') {
12114
- // @ts-expect-error called to dispose the stream in RN
12115
- this.audioStream.release();
12116
- }
12117
- }
12118
- forwardIceCandidate(destination, candidate) {
12119
- if (this.isStopped ||
12120
- !candidate ||
12121
- destination.signalingState === 'closed') {
12122
- return;
12123
- }
12124
- destination.addIceCandidate(candidate).catch(() => {
12125
- // silently ignore the error
12126
- const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
12127
- logger.info('cannot add ice candidate - ignoring');
12128
- });
12129
- }
12130
- }
12131
-
12132
11955
  class MicrophoneManager extends AudioDeviceManager {
12133
11956
  constructor(call, devicePersistence, disableMode = 'stop-tracks') {
12134
11957
  super(call, new MicrophoneManagerState(disableMode, call.tracer), TrackType.AUDIO, devicePersistence);
@@ -12393,7 +12216,10 @@ class MicrophoneManager extends AudioDeviceManager {
12393
12216
  const shouldApplyDefaults = this.state.status === undefined &&
12394
12217
  this.state.optimisticStatus === undefined;
12395
12218
  let persistedPreferencesApplied = false;
12396
- if (shouldApplyDefaults && this.devicePersistence.enabled) {
12219
+ const permissionState = await rxjs.firstValueFrom(this.state.browserPermissionState$);
12220
+ if (shouldApplyDefaults &&
12221
+ this.devicePersistence.enabled &&
12222
+ permissionState === 'granted') {
12397
12223
  persistedPreferencesApplied = await this.applyPersistedPreferences(true);
12398
12224
  }
12399
12225
  const canPublish = this.call.permissionsContext.canPublish(this.trackType);
@@ -12438,13 +12264,16 @@ class MicrophoneManager extends AudioDeviceManager {
12438
12264
  return;
12439
12265
  await this.teardownSpeakingWhileMutedDetection();
12440
12266
  if (isReactNative()) {
12441
- this.rnSpeechDetector = new RNSpeechDetector();
12442
- const unsubscribe = await this.rnSpeechDetector.start((event) => {
12267
+ const speechActivity = globalThis.streamRNVideoSDK?.nativeEvents?.speechActivity;
12268
+ if (!speechActivity) {
12269
+ this.logger.warn('Native speech activity not available, make sure the "@stream-io/react-native-webrtc" peer dependency version is satisfied');
12270
+ return;
12271
+ }
12272
+ const unsubscribe = speechActivity.subscribe((event) => {
12443
12273
  this.state.setSpeakingWhileMuted(event.isSoundDetected);
12444
12274
  });
12445
12275
  this.soundDetectorCleanup = async () => {
12446
12276
  unsubscribe();
12447
- this.rnSpeechDetector = undefined;
12448
12277
  };
12449
12278
  }
12450
12279
  else {
@@ -12777,7 +12606,12 @@ class SpeakerManager {
12777
12606
  }));
12778
12607
  }
12779
12608
  if (!isReactNative() && this.devicePersistence.enabled) {
12780
- this.subscriptions.push(createSubscription(this.state.selectedDevice$, (selectedDevice) => {
12609
+ this.subscriptions.push(createSubscription(rxjs.combineLatest([
12610
+ this.state.selectedDevice$,
12611
+ getAudioBrowserPermission(this.call.tracer).asStateObservable(),
12612
+ ]), ([selectedDevice, browserPermissionState]) => {
12613
+ if (!selectedDevice || browserPermissionState !== 'granted')
12614
+ return;
12781
12615
  this.persistSpeakerDevicePreference(selectedDevice);
12782
12616
  }));
12783
12617
  }
@@ -16152,7 +15986,7 @@ class StreamClient {
16152
15986
  this.getUserAgent = () => {
16153
15987
  if (!this.cachedUserAgent) {
16154
15988
  const { clientAppIdentifier = {} } = this.options;
16155
- const { sdkName = 'js', sdkVersion = "1.46.1", ...extras } = clientAppIdentifier;
15989
+ const { sdkName = 'js', sdkVersion = "1.48.0", ...extras } = clientAppIdentifier;
16156
15990
  this.cachedUserAgent = [
16157
15991
  `stream-video-${sdkName}-v${sdkVersion}`,
16158
15992
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -16827,7 +16661,6 @@ exports.MicrophoneManager = MicrophoneManager;
16827
16661
  exports.MicrophoneManagerState = MicrophoneManagerState;
16828
16662
  exports.NoiseCancellationSettingsModeEnum = NoiseCancellationSettingsModeEnum;
16829
16663
  exports.OwnCapability = OwnCapability;
16830
- exports.RNSpeechDetector = RNSpeechDetector;
16831
16664
  exports.RTMPBroadcastRequestQualityEnum = RTMPBroadcastRequestQualityEnum;
16832
16665
  exports.RTMPSettingsRequestQualityEnum = RTMPSettingsRequestQualityEnum;
16833
16666
  exports.RawRecordingSettingsRequestModeEnum = RawRecordingSettingsRequestModeEnum;