# 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.

| Pan    | 0 | 0.00 |
| ------ | - | ---- |
| Bass   | 0 | 0 dB |
| Mid    | 0 | 0 dB |
| Treble | 0 | 0 dB |
| Volume | 1 | 100% |

\[ ]Bypass effectsReset

`player.audio.createAudioSourceNode(context)` returns an audio node with the player's audio. Connect it to other nodes of the same [`AudioContext`](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext). Once the player's audio goes through the audio graph, you only hear it if the graph ends at `context.destination`.

```js
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](https://docs-preview.optiview.dolby.com/pr-913/theoplayer/v11/api-reference/web/interfaces/Audio) and the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) documentation on MDN.

Code

demo.html

```html
<!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>
```
