chromium/third_party/blink/manual_tests/webaudio/audiocontext-onerror-device-error.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Test onerror event on device error in AudioContext</title>
  <style type="text/css">
    body {
      font-family: sans-serif;
      padding: 1em;
    }

    .row {
      margin-top: 1em;
    }

    #appView {
      margin-top: 1em;
      font-family: monospace;
    }

    #log {
      line-height: 2em;
      font-size: 1.0em;
      padding: 0.5em;
      border: 1px solid #aaa;
      color: #333;
      background-color: #ddd;
    }

    #device-dropdown {
      font-size: 1.0em;
      padding: 0.5em;
      border-radius: 0.25em;
    }

    #device-change {
      font-size: 1.0em;
      padding: 0.6em 1.0em;
      border: 0;
      border-radius: 0.25em;
      color: #fff;
      background-color: #2962ff;
    }

    #device-change:disabled {
      background-color: #bbdefb;
    }

    #inspector {
      white-space: pre-line;
    }
  </style>
</head>
<body>
  <h1>AudioContext.onerror Manual Test</h1>
    <p>Select a non-default & external (USB, etc) audio device from the
      dropdown list. Then press the START TESTING button to start rendering.
      When you here the audio (sine wave) disconnect the audio device.
    </p>
    <p>The expected result: your should see <code>onerror</code> and then
      <code>onstatechange</code> dispatched in that order.</p>
    <div id="appView">
      <div id="log"></div>
      <div class="row">
        <select id="device-dropdown" disabled>
          <option value="">Getting device information...</option>
        </select>
        <button id="device-change" disabled>START TESTING</button>
      </div>
      <div class="row">
        <div id="inspector"></div>
      </div>
    </div>
  <script type="text/javascript">
    // Handles the button click to activate `setSinkId()` method.
    const onTestStarted = async (event, audioContext, appView) => {
      let isOnErrorDispatched = false;

      audioContext.onerror = () => {
        isOnErrorDispatched = true;
        appView.inspector.textContent +=
            `Success: "onerror" dispatched correctly.\n`;
      };

      audioContext.onstatechange = () => {
        if (!isOnErrorDispatched) {
          return;
        }

        appView.inspector.textContent +=
            `Success: "onstatechange" dispatched correctly. `
            + `(context state: ${audioContext.state})\n`;
      };

      const deviceId = appView.dropdown.value;
      if (deviceId === 'default') {
        audioContext.setSinkId('');
      } else if (deviceId === 'silent') {
        audioContext.setSinkId({type: 'none'});
      } else {
        audioContext.setSinkId(deviceId);
      }

      appView.log.textContent =
          'Test started. Disconnect the device to see onerror and ' +
          'onstatechange are dispatched.';
    };

    // The manual test body
    const startManualTestApp = async () => {
      const appView = {
        log: document.getElementById('log'),
        inspector: document.getElementById('inspector'),
        dropdown: document.getElementById('device-dropdown'),
        changeButton: document.getElementById('device-change')
      };

      const audioContext = new AudioContext();
      const osc = new OscillatorNode(audioContext);
      const amp = new GainNode(audioContext, {gain: 0.25});
      osc.connect(amp).connect(audioContext.destination);
      osc.start();

      if (typeof audioContext.setSinkId !== 'function' ||
          typeof audioContext.sinkId != 'string') {
        appView.log.textContent =
            'This browser does not support AudioContext.setSinkId()';
        return;
      }

      // Get a permission, enumerate devices, and set up the UI.
      try {
        const stream =
            await navigator.mediaDevices.getUserMedia({ audio: true });
        if (stream) {
          const deviceList = await navigator.mediaDevices.enumerateDevices();
          let deviceListString = '';
          deviceList.forEach((device) => {
            if (device.kind === 'audiooutput') {
              deviceListString +=
                  `<option value="${device.deviceId}">${device.label}</option>`;
            }
          });
          deviceListString +=
              `<option value="silent">none (silent output)</option>`;
          appView.dropdown.innerHTML = deviceListString;
          appView.changeButton.addEventListener('click', (event) => {
            audioContext.resume();
            onTestStarted(event, audioContext, appView);
          });
          appView.dropdown.disabled = false;
          appView.changeButton.disabled = false;
          appView.log.textContent =
              'Device information successfully retrieved. Select ' +
              'a non-default device and start testing.';
        } else {
          appView.log.textContent =
              'navigator.mediaDevices.getUserMedia() failed.';
        }
      } catch (error) {
        // The permission dialog was dismissed, or acquiring a stream from
        // getUserMedia() failed.
        console.error(error);
        appView.log.textContent = `The initialization failed: ${error}`;
      }
    };

    // Entry point
    window.addEventListener('load', startManualTestApp);
  </script>
</body>
</html>