@stream-io/video-client 1.15.5 → 1.15.7

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
@@ -5500,7 +5500,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
5500
5500
  rid,
5501
5501
  width: Math.round(width / downscaleFactor),
5502
5502
  height: Math.round(height / downscaleFactor),
5503
- maxBitrate: maxBitrate / bitrateFactor || defaultBitratePerRid[rid],
5503
+ maxBitrate: Math.round(maxBitrate / bitrateFactor) || defaultBitratePerRid[rid],
5504
5504
  maxFramerate: fps,
5505
5505
  };
5506
5506
  if (svcCodec) {
@@ -7358,7 +7358,7 @@ const aggregate = (stats) => {
7358
7358
  return report;
7359
7359
  };
7360
7360
 
7361
- const version = "1.15.5";
7361
+ const version = "1.15.7";
7362
7362
  const [major, minor, patch] = version.split('.');
7363
7363
  let sdkInfo = {
7364
7364
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -9339,7 +9339,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
9339
9339
  }
9340
9340
 
9341
9341
  const DETECTION_FREQUENCY_IN_MS = 500;
9342
- const AUDIO_LEVEL_THRESHOLD$1 = 150;
9342
+ const AUDIO_LEVEL_THRESHOLD = 150;
9343
9343
  const FFT_SIZE = 128;
9344
9344
  /**
9345
9345
  * Creates a new sound detector.
@@ -9350,7 +9350,7 @@ const FFT_SIZE = 128;
9350
9350
  * @returns a clean-up function which once invoked stops the sound detector.
9351
9351
  */
9352
9352
  const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options = {}) => {
9353
- const { detectionFrequencyInMs = DETECTION_FREQUENCY_IN_MS, audioLevelThreshold = AUDIO_LEVEL_THRESHOLD$1, fftSize = FFT_SIZE, destroyStreamOnStop = true, } = options;
9353
+ const { detectionFrequencyInMs = DETECTION_FREQUENCY_IN_MS, audioLevelThreshold = AUDIO_LEVEL_THRESHOLD, fftSize = FFT_SIZE, destroyStreamOnStop = true, } = options;
9354
9354
  const audioContext = new AudioContext();
9355
9355
  const analyser = audioContext.createAnalyser();
9356
9356
  analyser.fftSize = fftSize;
@@ -9391,7 +9391,6 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
9391
9391
  };
9392
9392
  };
9393
9393
 
9394
- const AUDIO_LEVEL_THRESHOLD = 0.2;
9395
9394
  class RNSpeechDetector {
9396
9395
  constructor() {
9397
9396
  this.pc1 = new RTCPeerConnection({});
@@ -9400,7 +9399,7 @@ class RNSpeechDetector {
9400
9399
  /**
9401
9400
  * Starts the speech detection.
9402
9401
  */
9403
- async start() {
9402
+ async start(onSoundDetectedStateChanged) {
9404
9403
  try {
9405
9404
  this.cleanupAudioStream();
9406
9405
  const audioStream = await navigator.mediaDevices.getUserMedia({
@@ -9413,6 +9412,14 @@ class RNSpeechDetector {
9413
9412
  this.pc2.addEventListener('icecandidate', async (e) => {
9414
9413
  await this.pc1.addIceCandidate(e.candidate);
9415
9414
  });
9415
+ this.pc2.addEventListener('track', (e) => {
9416
+ e.streams[0].getTracks().forEach((track) => {
9417
+ // In RN, the remote track is automatically added to the audio output device
9418
+ // so we need to mute it to avoid hearing the audio back
9419
+ // @ts-ignore _setVolume is a private method in react-native-webrtc
9420
+ track._setVolume(0);
9421
+ });
9422
+ });
9416
9423
  audioStream
9417
9424
  .getTracks()
9418
9425
  .forEach((track) => this.pc1.addTrack(track, audioStream));
@@ -9422,12 +9429,16 @@ class RNSpeechDetector {
9422
9429
  const answer = await this.pc2.createAnswer();
9423
9430
  await this.pc1.setRemoteDescription(answer);
9424
9431
  await this.pc2.setLocalDescription(answer);
9425
- const audioTracks = audioStream.getAudioTracks();
9426
- // We need to mute the audio track for this temporary stream, or else you will hear yourself twice while in the call.
9427
- audioTracks.forEach((track) => (track.enabled = false));
9432
+ const unsub = this.onSpeakingDetectedStateChange(onSoundDetectedStateChanged);
9433
+ return () => {
9434
+ unsub();
9435
+ this.stop();
9436
+ };
9428
9437
  }
9429
9438
  catch (error) {
9430
- console.error('Error connecting and negotiating between PeerConnections:', error);
9439
+ const logger = getLogger(['RNSpeechDetector']);
9440
+ logger('error', 'error handling permissions: ', error);
9441
+ return () => { };
9431
9442
  }
9432
9443
  }
9433
9444
  /**
@@ -9437,40 +9448,85 @@ class RNSpeechDetector {
9437
9448
  this.pc1.close();
9438
9449
  this.pc2.close();
9439
9450
  this.cleanupAudioStream();
9440
- if (this.intervalId) {
9441
- clearInterval(this.intervalId);
9442
- }
9443
9451
  }
9444
9452
  /**
9445
9453
  * Public method that detects the audio levels and returns the status.
9446
9454
  */
9447
9455
  onSpeakingDetectedStateChange(onSoundDetectedStateChanged) {
9448
- this.intervalId = setInterval(async () => {
9449
- const stats = (await this.pc1.getStats());
9450
- const report = flatten(stats);
9451
- // Audio levels are present inside stats of type `media-source` and of kind `audio`
9452
- const audioMediaSourceStats = report.find((stat) => stat.type === 'media-source' &&
9453
- stat.kind === 'audio');
9454
- if (audioMediaSourceStats) {
9455
- const { audioLevel } = audioMediaSourceStats;
9456
- if (audioLevel) {
9457
- if (audioLevel >= AUDIO_LEVEL_THRESHOLD) {
9458
- onSoundDetectedStateChanged({
9459
- isSoundDetected: true,
9460
- audioLevel,
9461
- });
9462
- }
9463
- else {
9464
- onSoundDetectedStateChanged({
9465
- isSoundDetected: false,
9466
- audioLevel: 0,
9467
- });
9456
+ const initialBaselineNoiseLevel = 0.13;
9457
+ let baselineNoiseLevel = initialBaselineNoiseLevel;
9458
+ let speechDetected = false;
9459
+ let intervalId;
9460
+ let speechTimer;
9461
+ let silenceTimer;
9462
+ let audioLevelHistory = []; // Store recent audio levels for smoother detection
9463
+ const historyLength = 10;
9464
+ const silenceThreshold = 1.1;
9465
+ const resetThreshold = 0.9;
9466
+ const speechTimeout = 500; // Speech is set to true after 500ms of audio detection
9467
+ const silenceTimeout = 5000; // Reset baseline after 5 seconds of silence
9468
+ const checkAudioLevel = async () => {
9469
+ try {
9470
+ const stats = (await this.pc1.getStats());
9471
+ const report = flatten(stats);
9472
+ // Audio levels are present inside stats of type `media-source` and of kind `audio`
9473
+ const audioMediaSourceStats = report.find((stat) => stat.type === 'media-source' &&
9474
+ stat.kind === 'audio');
9475
+ if (audioMediaSourceStats) {
9476
+ const { audioLevel } = audioMediaSourceStats;
9477
+ if (audioLevel) {
9478
+ // Update audio level history (with max historyLength sized array)
9479
+ audioLevelHistory.push(audioLevel);
9480
+ if (audioLevelHistory.length > historyLength) {
9481
+ audioLevelHistory.shift();
9482
+ }
9483
+ // Calculate average audio level
9484
+ const avgAudioLevel = audioLevelHistory.reduce((a, b) => a + b, 0) /
9485
+ audioLevelHistory.length;
9486
+ // Update baseline (if necessary) based on silence detection
9487
+ if (avgAudioLevel < baselineNoiseLevel * silenceThreshold) {
9488
+ if (!silenceTimer) {
9489
+ silenceTimer = setTimeout(() => {
9490
+ baselineNoiseLevel = Math.min(avgAudioLevel * resetThreshold, initialBaselineNoiseLevel);
9491
+ }, silenceTimeout);
9492
+ }
9493
+ }
9494
+ else {
9495
+ clearTimeout(silenceTimer);
9496
+ silenceTimer = undefined;
9497
+ }
9498
+ // Speech detection with hysteresis
9499
+ if (avgAudioLevel > baselineNoiseLevel * 1.5) {
9500
+ if (!speechDetected) {
9501
+ speechDetected = true;
9502
+ onSoundDetectedStateChanged({
9503
+ isSoundDetected: true,
9504
+ audioLevel,
9505
+ });
9506
+ }
9507
+ clearTimeout(speechTimer);
9508
+ speechTimer = setTimeout(() => {
9509
+ speechDetected = false;
9510
+ onSoundDetectedStateChanged({
9511
+ isSoundDetected: false,
9512
+ audioLevel: 0,
9513
+ });
9514
+ }, speechTimeout);
9515
+ }
9468
9516
  }
9469
9517
  }
9470
9518
  }
9471
- }, 1000);
9519
+ catch (error) {
9520
+ const logger = getLogger(['RNSpeechDetector']);
9521
+ logger('error', 'error checking audio level from stats', error);
9522
+ }
9523
+ };
9524
+ // Call checkAudioLevel periodically (every 100ms)
9525
+ intervalId = setInterval(checkAudioLevel, 100);
9472
9526
  return () => {
9473
- clearInterval(this.intervalId);
9527
+ clearInterval(intervalId);
9528
+ clearTimeout(speechTimer);
9529
+ clearTimeout(silenceTimer);
9474
9530
  };
9475
9531
  }
9476
9532
  cleanupAudioStream() {
@@ -9488,9 +9544,7 @@ class RNSpeechDetector {
9488
9544
  }
9489
9545
 
9490
9546
  class MicrophoneManager extends InputMediaDeviceManager {
9491
- constructor(call, disableMode = isReactNative()
9492
- ? 'disable-tracks'
9493
- : 'stop-tracks') {
9547
+ constructor(call, disableMode = 'stop-tracks') {
9494
9548
  super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
9495
9549
  this.speakingWhileMutedNotificationEnabled = true;
9496
9550
  this.soundDetectorConcurrencyTag = Symbol('soundDetectorConcurrencyTag');
@@ -9671,13 +9725,11 @@ class MicrophoneManager extends InputMediaDeviceManager {
9671
9725
  await this.stopSpeakingWhileMutedDetection();
9672
9726
  if (isReactNative()) {
9673
9727
  this.rnSpeechDetector = new RNSpeechDetector();
9674
- await this.rnSpeechDetector.start();
9675
- const unsubscribe = this.rnSpeechDetector?.onSpeakingDetectedStateChange((event) => {
9728
+ const unsubscribe = await this.rnSpeechDetector.start((event) => {
9676
9729
  this.state.setSpeakingWhileMuted(event.isSoundDetected);
9677
9730
  });
9678
9731
  this.soundDetectorCleanup = () => {
9679
9732
  unsubscribe();
9680
- this.rnSpeechDetector?.stop();
9681
9733
  this.rnSpeechDetector = undefined;
9682
9734
  };
9683
9735
  }
@@ -12863,7 +12915,7 @@ class StreamClient {
12863
12915
  return await this.wsConnection.connect(this.defaultWSTimeout);
12864
12916
  };
12865
12917
  this.getUserAgent = () => {
12866
- const version = "1.15.5";
12918
+ const version = "1.15.7";
12867
12919
  return (this.userAgent ||
12868
12920
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
12869
12921
  };