@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 +6 -0
- package/javascript/audio_output_hooks.js +60 -13
- package/package.json +1 -1
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
|
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.
|
|
808
|
-
|
|
809
|
-
|
|
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:
|
|
818
|
-
|
|
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
|
});
|