Minimal Integration
Single test run using the mediarecorder backend. The simplest way to validate the component works.
Configuration
<latency-test id="minimal-tester"></latency-test>
<script>
const tester = document.querySelector('#minimal-tester')
tester.addEventListener('latency-result', e => {
console.log(`Latency: ${e.detail.latency} ms`)
})
tester.start()
</script>
Multi-Run with Aggregates
Configure the number of test runs. Results are accumulated and displayed with mean, standard deviation, min, and max.
Configuration
<!-- Option 1: HTML attribute -->
<latency-test id="multi-tester" number-of-tests="5"></latency-test>
<script>
const tester = document.querySelector('#multi-tester')
tester.addEventListener('latency-complete', e => {
console.log(`Mean: ${e.detail.mean} ms (${e.detail.results.length} runs)`)
})
tester.start()
</script>
<!-- Option 2: JS property -->
<latency-test id="multi-tester"></latency-test>
<script>
const tester = document.querySelector('#multi-tester')
tester.numberOfTests = 5
tester.addEventListener('latency-complete', e => {
console.log(`Mean: ${e.detail.mean} ms (${e.detail.results.length} runs)`)
})
tester.start()
</script>
Host-Managed AudioContext & Stream
The host creates its own AudioContext and MediaStream, then passes them via properties. The component never closes or stops what it didn't create.
Configuration
<latency-test id="context-tester"></latency-test>
<script>
const tester = document.querySelector('#context-tester')
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: false, channelCount: 1 }
})
const ac = new AudioContext({ latencyHint: 0 })
tester.audioContext = ac
tester.inputStream = stream
tester.start()
// Component never closes/stops host-provided resources
</script>
Capture Backend Comparison
Run MediaRecorder and AudioWorklet back-to-back on the same source stream for direct A/B comparison.
MediaRecorder
AudioWorklet
Configuration
<latency-test id="mode-mr" recording-mode="mediarecorder"></latency-test>
<latency-test id="mode-aw" recording-mode="audioworklet"></latency-test>
<script>
const mr = document.querySelector('#mode-mr')
const aw = document.querySelector('#mode-aw')
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: false, channelCount: 1 }
})
const ac = new AudioContext({ latencyHint: 0 })
mr.audioContext = aw.audioContext = ac
mr.inputStream = aw.inputStream = stream
mr.addEventListener('latency-complete', e => {
console.log(`MR: ${e.detail.mean.toFixed(2)} ms`)
})
aw.addEventListener('latency-complete', e => {
console.log(`AW: ${e.detail.mean.toFixed(2)} ms`)
})
mr.start()
</script>
Lifecycle Event Log
All six lifecycle events are logged in real-time with timestamps.
Configuration
<latency-test id="lifecycle-tester"></latency-test>
<script>
const tester = document.querySelector('#lifecycle-tester')
tester.addEventListener('latency-start', e => console.log('start'))
tester.addEventListener('latency-recording', e => console.log('recording'))
tester.addEventListener('latency-processing', e => console.log('processing'))
tester.addEventListener('latency-result', e => console.log(e.detail))
tester.addEventListener('latency-complete', e => console.log('complete'))
tester.addEventListener('latency-error', e => console.error(e.detail))
tester.start()
</script>
Debug Mode
console.debug('[latency-test]', …) logging at internal checkpoints. Having debug on — or DevTools open — can perturb browser scheduling and affect latency accuracy. Do not use for measurements you intend to record.
Main-thread checkpoints only — cross-correlation worker logs appear in DevTools but not here.
Configuration
<!-- Option 1: HTML attribute (debug on from page load) -->
<latency-test id="tester" debug></latency-test>
<script>
const tester = document.querySelector('#tester')
tester.addEventListener('latency-result', e => {
// [latency-test] lines appear in DevTools console
console.log(`Latency: ${e.detail.latency.toFixed(2)} ms`)
})
tester.start()
</script>
<!-- Option 2: Runtime toggle (enable before start() for full logs) -->
<latency-test id="tester"></latency-test>
<script>
const tester = document.querySelector('#tester')
tester.debug = true // enable before start() to capture all checkpoints
tester.start()
</script>
Host-Controlled Input Gain
The host builds a GainNode chain and passes the processed stream to the component. Use this when your recording setup has low mic levels — common on Safari ≥ 16 with echoCancellation: false (empirical value: 50×). A ChannelSplitterNode isolates the left channel, which also handles wired earpods on Safari that force a stereo stream with signal only on the left.
Configuration
<latency-test id="tester" recording-mode="mediarecorder"></latency-test>
<script>
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: false, noiseSuppression: false,
autoGainControl: false, channelCount: 1 }
})
const ac = new AudioContext({ latencyHint: 0 })
// Set gainValue to 50 for Safari >= 16 with echoCancellation: false
const gainValue = 1
const source = ac.createMediaStreamSource(stream)
const splitter = ac.createChannelSplitter(2)
const gainNode = ac.createGain()
gainNode.gain.value = gainValue
const dest = ac.createMediaStreamDestination()
dest.channelCount = 1
source.connect(splitter)
splitter.connect(gainNode, 0) // left channel only
gainNode.connect(dest)
const tester = document.querySelector('#tester')
tester.audioContext = ac
tester.inputStream = dest.stream
tester.start()
</script>