Skip to content

Keeping audio and visuals in sync with the Web Audio API

Audio and visuals out of sync when using the Web Audio API? There’s a property for that.

There was an issue with the animated piano keyboard I built for my website JazzKeys.fyi in that the audio and visuals were out of sync when listening on Bluetooth headphones. I had assumed that latency would be taken care of automatically by the OS — there are no such issues when viewing an embedded HTML <video>, for example — but it seems that the Web Audio API does not handle this by default.

It turns out, however, that the Web Audio API’s AudioContext interface has a new property that returns an estimate in seconds of the output latency. The property, outputLatency, is currently supported in newer versions of Firefox and Chrome1, and allows us to delay starting the visuals and have them be in sync with the audio.

Testing it out permalink

You can test it by running the code below in the browser console.

First check the latency while your audio output is set to the built-in speakers or a pair of wired headphones:

var audioCtx = new AudioContext()
audioCtx.outputLatency

I get 0 (zero) when I just checked in Firefox on macOS.2

Now set the audio output to a pair of Bluetooth headphones and check the latency again:

audioCtx.outputLatency

I get 0.17780041666666666 seconds.

If I then switch back to the built-in speaker it‘s 0.02485258333333333 seconds. I can’t explain that; feel free to leave a comment if you know the reason.

Testing the outputLatency property of the Web Audio API AudioContext when interface the audio output is set to the MacBook’s built-in speakers vs AirPods.
Testing the outputLatency property of the Web Audio API AudioContext interface when the audio output is set to the MacBook’s built-in speakers vs AirPods.

You can alternatively test it on an updated version of the animated keyboard (below). Output latency will be shown beneath the controls when you press Play.

The audio and visuals should now be in sync no matter what the audio output is set to. In the code, we check the latency each time the user clicks the Play button and pass this value to the Tone.js’s Transport.Schedule() function, delaying the start of the animations so that they’re in sync with the audio.

Safari and older browsers permalink

Because browser support is incomplete, when using outputLatency we should first check that the property exists:3

const hasOutputLatency = window.AudioContext && 'outputLatency' in window.AudioContext.prototype ? true : false;

if (hasOutputLatency) {
console.log('AudioContext.outputLatency is available');
}

Regarding Safari, I see there is a fixme in the WebKit code, so hopefully the feature will be available soon on iPhones and iPads.

Further reading permalink

There is some great technical background in this post by a Paul Adenot, an engineer at Mozilla who added outputLatency (along with baseLatency and getOutputTimestamp) to Firefox.


  1. This does not include Firefox and Chrome on iOS and iPadOS: they use WebKit under the hood. ↩︎

  2. When outputLatency is queried in the Codesandbox of my animated keyboard, the (non-Bluetooth headphones) value returned is 0.0154195s in Firefox and 0.024s in Chrome, so it looks like it depends on browser and maybe what else is running in the program context (the Codesandbox has a whole bunch of other JavaScript running). It can also vary by OS and device ↩︎

  3. If you want to ignore Internet Explorer, you can remove the check for window.AudioContext ↩︎