@telnyx/voice-agent-tester 0.4.5 → 0.4.6

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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.6](https://github.com/team-telnyx/voice-agent-tester/compare/v0.4.5...v0.4.6) (2026-03-18)
4
+
5
+ ### Bug Fixes
6
+
7
+ * silence detection + audio element discovery for ElevenLabs ([#29](https://github.com/team-telnyx/voice-agent-tester/issues/29)) ([789b98b](https://github.com/team-telnyx/voice-agent-tester/commit/789b98b2e91a8f0b9443110067dd17d50eaf2381))
8
+
3
9
  ## [0.4.5](https://github.com/team-telnyx/voice-agent-tester/compare/v0.4.4...v0.4.5) (2026-03-16)
4
10
 
5
11
  ### Bug Fixes
@@ -118,6 +118,7 @@ class AudioElementMonitor {
118
118
  this.scanExistingAudioElements();
119
119
  this.setupProgrammaticAudioInterception();
120
120
  this.setupShadowDomInterception();
121
+ this.startPeriodicScan();
121
122
  console.log("AudioElementMonitor initialized");
122
123
  }
123
124
 
@@ -324,6 +325,42 @@ class AudioElementMonitor {
324
325
  });
325
326
  }
326
327
 
328
+ /**
329
+ * Periodic scan for unmonitored audio elements.
330
+ * Catches elements that bypass interceptors (e.g., created in bundled code
331
+ * that captured native constructors, or appended to shadow DOMs not observed).
332
+ */
333
+ startPeriodicScan() {
334
+ setInterval(() => {
335
+ // Scan all audio elements in the main document
336
+ const allAudio = document.querySelectorAll('audio');
337
+ allAudio.forEach(audioEl => {
338
+ const elementId = this.getElementId(audioEl);
339
+ if (!this.monitoredElements.has(elementId) && (audioEl.srcObject || audioEl.src)) {
340
+ console.log(`Periodic scan found unmonitored audio element: ${elementId}`);
341
+ if (audioEl.srcObject && audioEl.srcObject instanceof MediaStream) {
342
+ this.monitorAudioElement(audioEl, elementId);
343
+ } else if (audioEl.src) {
344
+ this.monitorProgrammaticAudioElement(audioEl, elementId);
345
+ }
346
+ }
347
+ });
348
+
349
+ // Also scan programmatic Audio instances that were intercepted but never monitored
350
+ programmaticAudioInstances.forEach(audioEl => {
351
+ const elementId = this.getElementId(audioEl);
352
+ if (!this.monitoredElements.has(elementId) && (audioEl.srcObject || audioEl.src)) {
353
+ console.log(`Periodic scan found unmonitored programmatic audio: ${elementId}`);
354
+ if (audioEl.srcObject && audioEl.srcObject instanceof MediaStream) {
355
+ this.monitorAudioElement(audioEl, elementId);
356
+ } else if (audioEl.src) {
357
+ this.monitorProgrammaticAudioElement(audioEl, elementId);
358
+ }
359
+ }
360
+ });
361
+ }, 2000);
362
+ }
363
+
327
364
  handleAudioElement(audioElement) {
328
365
  const elementId = this.getElementId(audioElement);
329
366
 
@@ -662,10 +699,18 @@ class AudioElementMonitor {
662
699
  const { analyser, dataArray, silenceThreshold } = monitorData;
663
700
 
664
701
  monitorData.checkInterval = setInterval(() => {
665
- analyser.getByteFrequencyData(dataArray);
666
-
667
- const average = dataArray.reduce((sum, value) => sum + value, 0) / dataArray.length;
668
- const hasAudio = average > silenceThreshold;
702
+ // Use time-domain data for silence detection — more robust than frequency data.
703
+ // Time-domain bytes center at 128 for silence; we measure RMS deviation.
704
+ // This avoids false positives from FFT noise floor / RTP comfort noise.
705
+ analyser.getByteTimeDomainData(dataArray);
706
+
707
+ let sumSquares = 0;
708
+ for (let i = 0; i < dataArray.length; i++) {
709
+ const deviation = dataArray[i] - 128;
710
+ sumSquares += deviation * deviation;
711
+ }
712
+ const rms = Math.sqrt(sumSquares / dataArray.length);
713
+ const hasAudio = rms > silenceThreshold;
669
714
 
670
715
  // if (i++ % 10 == 0) {
671
716
  // console.log(`Average: ${average} hasAudio: ${hasAudio} elementId: ${elementId}`);
@@ -800,13 +845,16 @@ window.__getAudioDiagnostics = function() {
800
845
  audioMonitor.monitoredElements.forEach((monitorData, elementId) => {
801
846
  const { analyser, dataArray, silenceThreshold, isPlaying, lastAudioTime, isProgrammatic } = monitorData;
802
847
 
803
- // Get current audio level if analyser is available
804
- let currentLevel = null;
805
- let currentMaxLevel = null;
848
+ // Get current audio level if analyser is available (RMS of time-domain deviation)
849
+ let currentRms = null;
806
850
  if (analyser && dataArray) {
807
- analyser.getByteFrequencyData(dataArray);
808
- currentLevel = dataArray.reduce((sum, value) => sum + value, 0) / dataArray.length;
809
- currentMaxLevel = Math.max(...dataArray);
851
+ analyser.getByteTimeDomainData(dataArray);
852
+ let sumSquares = 0;
853
+ for (let j = 0; j < dataArray.length; j++) {
854
+ const deviation = dataArray[j] - 128;
855
+ sumSquares += deviation * deviation;
856
+ }
857
+ currentRms = Math.sqrt(sumSquares / dataArray.length);
810
858
  }
811
859
 
812
860
  diagnostics.elements.push({
@@ -814,9 +862,8 @@ window.__getAudioDiagnostics = function() {
814
862
  isPlaying,
815
863
  isProgrammatic: !!isProgrammatic,
816
864
  silenceThreshold,
817
- currentAudioLevel: currentLevel !== null ? currentLevel.toFixed(2) : 'unavailable',
818
- currentMaxLevel: currentMaxLevel !== null ? currentMaxLevel : 'unavailable',
819
- wouldTriggerAudioStart: currentLevel !== null ? currentLevel > silenceThreshold : 'unknown',
865
+ currentAudioLevel: currentRms !== null ? currentRms.toFixed(2) : 'unavailable',
866
+ wouldTriggerAudioStart: currentRms !== null ? currentRms > silenceThreshold : 'unknown',
820
867
  lastAudioTime,
821
868
  timeSinceLastAudio: lastAudioTime ? Date.now() - lastAudioTime : null
822
869
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telnyx/voice-agent-tester",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "A command-line tool to test voice agents using Puppeteer",
5
5
  "main": "src/index.js",
6
6
  "type": "module",