Web Audio API
Route the player's audio through the Web Audio API to pan it, change its tone with an equalizer, and draw its spectrum.
Tip: open your browser's developer console and use the player and THEOplayer variables to interact with this demo.
Press play, then move the sliders. The audio context starts after you interact with the player.
| 0.00 | ||
| 0 dB | ||
| 0 dB | ||
| 0 dB | ||
| 100% |
player.audio.createAudioSourceNode(context) returns an audio node with the player's audio. Connect it to other nodes of the same AudioContext.
Once the player's audio goes through the audio graph, you only hear it if the graph ends at context.destination.
const context = new AudioContext();
const source = player.audio.createAudioSourceNode(context);
const panner = new StereoPannerNode(context);
const bass = new BiquadFilterNode(context, { type: 'lowshelf', frequency: 250 });
const volume = new GainNode(context);
const analyser = new AnalyserNode(context, { fftSize: 256 });
source.connect(panner).connect(bass).connect(volume).connect(analyser).connect(context.destination);
// Change values smoothly to avoid audible clicks.
bass.gain.setTargetAtTime(6, context.currentTime, 0.02);
Browsers start an AudioContext in the suspended state until the viewer interacts with the page, and the player is silent while the context is suspended.
Call context.resume() when playback starts or after a click or key press, as this example does.
See the Audio API and the Web Audio API documentation on MDN.
Code
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Web Audio API</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&display=swap" rel="stylesheet" />
<style>
html,
body {
margin: 0;
padding: 0;
font-family: 'Noto Sans', sans-serif;
}
theoplayer-default-ui:not(:defined) {
display: inline-block;
box-sizing: border-box;
}
theoplayer-default-ui {
width: 100%;
aspect-ratio: 16 / 9;
font-family: 'Noto Sans', sans-serif;
background: #000;
}
.spectrum {
display: block;
width: 100%;
height: 100px;
background: #000;
}
</style>
<script src="https://cdn.theoplayer.com/dash/theoplayer/THEOplayer.js"></script>
<script nomodule src="https://unpkg.com/@theoplayer/web-ui@2/polyfills"></script>
<script async src="https://unpkg.com/@theoplayer/web-ui@2"></script>
</head>
<body>
<!--
libraryLocation: For demonstration purposes, we use the THEOplayer CDN.
For production use, host the library yourself and update this URL and the script above.
licenseUrl: Point this to your license file, or replace it with a license property.
-->
<theoplayer-default-ui
configuration='{"libraryLocation":"https://cdn.theoplayer.com/dash/theoplayer/","licenseUrl":"../../../../theoplayer-license.txt"}'
source='{"sources":{"src":"https://cdn.theoplayer.com/video/elephants-dream.mp4","type":"video/mp4","crossOrigin":"anonymous"},"poster":"https://cdn.theoplayer.com/video/posters/elephants_dream.jpg","metadata":{"title":"Elephants Dream"}}'
></theoplayer-default-ui>
<canvas class="spectrum" aria-label="Audio spectrum"></canvas>
<script src="../utils.js"></script>
<script>
onPlayerReady((player) => {
const context = new AudioContext();
// Route the player's audio into the audio graph:
// source -> panner -> bass -> mid -> treble -> volume -> analyser -> speakers
const source = player.audio.createAudioSourceNode(context);
const panner = new StereoPannerNode(context);
const bass = new BiquadFilterNode(context, { type: 'lowshelf', frequency: 250 });
const mid = new BiquadFilterNode(context, { type: 'peaking', frequency: 1000, Q: 1 });
const treble = new BiquadFilterNode(context, { type: 'highshelf', frequency: 4000 });
const volume = new GainNode(context);
const analyser = new AnalyserNode(context, { fftSize: 256 });
panner.connect(bass).connect(mid).connect(treble).connect(volume).connect(analyser);
analyser.connect(context.destination);
function connectSource(bypass) {
source.disconnect();
source.connect(bypass ? analyser : panner);
}
connectSource(false);
// Browsers start an AudioContext suspended until the user interacts with the page.
// While it's suspended, the player's audio is silent, so resume it on playback and on any interaction.
function resumeContext() {
if (context.state === 'suspended') context.resume();
}
player.addEventListener('play', resumeContext);
document.addEventListener('pointerdown', resumeContext);
document.addEventListener('keydown', resumeContext);
// Change parameters smoothly with setTargetAtTime to avoid audible clicks.
const params = {
pan: panner.pan,
bass: bass.gain,
mid: mid.gain,
treble: treble.gain,
volume: volume.gain,
};
onParentMessage('web-audio-param', ({ name, value }) => {
params[name].setTargetAtTime(value, context.currentTime, 0.02);
});
onParentMessage('web-audio-bypass', ({ bypass }) => connectSource(bypass));
// Draw the frequency spectrum from the analyser.
const canvas = document.querySelector('.spectrum');
const canvasContext = canvas.getContext('2d');
const data = new Uint8Array(analyser.frequencyBinCount);
function draw() {
requestAnimationFrame(draw);
const width = (canvas.width = canvas.clientWidth * devicePixelRatio);
const height = (canvas.height = canvas.clientHeight * devicePixelRatio);
analyser.getByteFrequencyData(data);
const barWidth = width / data.length;
canvasContext.fillStyle = '#ffc50f';
data.forEach((value, index) => {
const barHeight = (value / 255) * height;
canvasContext.fillRect(index * barWidth, height - barHeight, Math.max(1, barWidth - 1), barHeight);
});
}
draw();
});
</script>
</body>
</html>