Skip to content
JavaScriptVanilla JS Integration

No framework required. Import the package and use the element directly in HTML.


Basic usage

The recommended pattern is a two-step flow: connect audio first, then run tests. This keeps the mic stream warm between repeated clicks and avoids cold-start instability.

html
<!DOCTYPE html>
<html lang="en">
<head>
  <script type="module" src="https://cdn.jsdelivr.net/npm/@adasp/latency-test@1.2.0/dist/latency-test.esm.js"></script>
</head>
<body>
  <button id="connect-btn">Connect Audio</button>

  <div id="test-ui" hidden>
    <button id="start-btn">Test Latency</button>
    <p id="result"></p>
    <p id="stats"></p>
  </div>

  <latency-test id="lt" number-of-tests="5"></latency-test>

  <script>
    const lt = document.getElementById('lt')
    const connectBtn = document.getElementById('connect-btn')
    const testUi = document.getElementById('test-ui')
    const startBtn = document.getElementById('start-btn')
    const result = document.getElementById('result')
    const stats = document.getElementById('stats')

    const MIC_CONSTRAINTS = {
      audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false, channelCount: 1 }
    }

    let micStream = null
    let audioCtx = null

    connectBtn.addEventListener('click', async () => {
      connectBtn.disabled = true
      try {
        audioCtx = new AudioContext({ latencyHint: 0 })
        micStream = await navigator.mediaDevices.getUserMedia(MIC_CONSTRAINTS)
        lt.inputStream = micStream
        lt.audioContext = audioCtx
        connectBtn.hidden = true
        testUi.hidden = false
      } catch (e) {
        micStream?.getTracks().forEach(t => t.stop())
        micStream = null
        connectBtn.disabled = false
        result.textContent = `Could not access mic: ${e.message}`
      }
    })

    window.addEventListener('beforeunload', () => {
      micStream?.getTracks().forEach(t => t.stop())
      audioCtx?.close()
    })

    startBtn.addEventListener('click', () => lt.start())

    lt.addEventListener('latency-result', (e) => {
      const { latency, ratio, reliable } = e.detail
      result.textContent = `${latency.toFixed(2)} ms — ratio: ${ratio.toFixed(2)} dB${reliable ? '' : ' ⚠️ unreliable'}`
    })

    lt.addEventListener('latency-complete', (e) => {
      const { results, mean, std, min, max } = e.detail
      if (results.length > 1)
        stats.textContent = `Mean: ${mean.toFixed(2)} ms | SD: ${std.toFixed(2)} | Min: ${min.toFixed(2)} | Max: ${max.toFixed(2)}`
    })

    lt.addEventListener('latency-error', (e) => {
      result.textContent = `Error: ${e.detail.message}`
    })
  </script>
</body>
</html>

Real-world use: In an application that already manages a mic stream and AudioContext (e.g. a Web Audio DAW), pass both directly — no Connect Audio step needed. See Sharing audio resources from a host app.


Sharing audio resources from a host app

When your application already owns a mic stream and AudioContext, pass both to the element. The component will not stop the stream or close the context — the host owns both lifetimes.

html
<latency-test id="lt"></latency-test>

<script type="module">
import '@adasp/latency-test'

// Host already has a stream and AudioContext (e.g. a DAW)
const lt = document.getElementById('lt')
lt.inputStream = existingStream
lt.audioContext = existingAudioContext

lt.addEventListener('latency-result', (e) => {
  console.log(e.detail.latency, 'ms')
})

document.getElementById('startBtn').addEventListener('click', () => lt.start())
</script>

Stopping an in-progress test

js
const lt = document.querySelector('latency-test')

document.getElementById('stopBtn').addEventListener('click', () => {
  lt.stop()
})

TypeScript

Types ship with the package. querySelector returns the correct type automatically:

ts
import '@adasp/latency-test'

const el = document.querySelector('latency-test') // → LatencyTestElement
el?.start()        // ✅ typed
el?.audioContext   // ✅ typed
el?.addEventListener('latency-result', (e) => {
  console.log(e.detail.latency) // ✅ typed
})

MIT License