chromium/third_party/blink/manual_tests/webaudio/audiooutputtimestamp.html

<!doctype html>
<html>
  <head>
    <title>Test AudioContext.getOutputTimestamp() method</title>
    <style type="text/css">
      body {
        margin: 2em;
      }
      .manual-test-ui {
        font-family: Arial;
        padding: 1em;
        border: 1px solid #999;
      }
      .manual-test-ui button {
        padding: 1em;
        font-size: 1em;
      }
      .manual-test-ui div {
        padding: 10px;
        font-size: 1.5em;
      }
      .manual-test-ui-beep-indicator {
         padding: 10px;
         display: inline-block;
         border-radius: 50%;
         width: 10px;
         height: 10px;
         background: gray;
      }
    </style>
  </head>

  <body>
    <h1>Test AudioContext.getOutputTimestamp() method</h1>

    <p>Tests the values returned from AudioContext.getOutputTimestamp()</p>

    <p>Press "Start testing" to run the test. You should hear a sine tone (beeping) in 2 seconds.
       During the delay the color of "beeping indicator" is red.</p>

    <p>The color of "beeping indicator" is yellow when 'context.currentTime' is at beeping
       interval but the estimated output position is not there yet and thus beeping is not audible.</p>

    <p><b>Note: </b> the "beeping indicator" might never be seen yellow if the test is run in the
       environment with low latency audio (i.e. if the output latency is smaller than frame update
       period).</p>

    <p>The color of "beeping indicator" is green when the estimated output position is at beeping
       interval, this should happen exactly when beeping is audible.</p>

    <p>Press "Refresh values" to fetch the fresh output audio timestamp values
      and compare them to 'AudioContext.currentTime' and 'Performance.now()'. </p>

    <p>When context is running the obtained audio timestamp and performance timestamp values must be always
       slightly behind of 'AudioContext.currentTime' and 'Performance.now()' correspondingly.</p>
    
    <p>Please see <a href="https://webaudio.github.io/web-audio-api/#audiotimestamp">Web Audio
      API</a> for more details.</p>

    <div class="manual-test-ui">
      <div id="audioTime"></div>
      <div id="performanceTime"></div>
      <div id="timestampContextTime"></div>
      <div id="timestampPerformanceTime"></div>
      <div class="manual-test-ui-beep-indicator" id="beepIndicator"></div>

      <button id="startButton" onclick="startTesting()">Start testing</button>
      <button id="valuesButton" onclick="refreshValues()">Refresh values</button>
      <button onclick="context.suspend()">Suspend context</button>
      <button onclick="context.resume()">Resume context</button>
    </div>

    <script type="text/javascript">
      var context = new AudioContext();

      refreshValues();

      let startBeepingTime;
      let endBeepingTime;
      let indicator = document.querySelector('#beepIndicator');

      function inBeepingInterval(audioPosition) {
        return audioPosition >= startBeepingTime && audioPosition <= endBeepingTime;
      }

      function checkAudioOutputPosition() {
        if (!document.querySelector('#startButton').disabled)
           return;
        window.requestAnimationFrame(checkAudioOutputPosition);

        if (context.state == "suspended") {
          indicator.style.background = "green";
          return;
        }

        let timestamp = context.getOutputTimestamp();
        let audioOutputPosition = (performance.now() - timestamp.performanceTime) * 0.001 + timestamp.contextTime;

        if (inBeepingInterval(audioOutputPosition))
           indicator.style.background = "green";
        else if (inBeepingInterval(context.currentTime))
           indicator.style.background = "yellow";
      }

      function startTesting() {
        document.querySelector('#startButton').disabled = true;
        let oscillator = context.createOscillator();
        oscillator.connect(context.destination);
        startBeepingTime = context.currentTime + 2.0;
        endBeepingTime = startBeepingTime + 3.0;

        oscillator.start(startBeepingTime);
        oscillator.stop(endBeepingTime);
        indicator.style.background = "red";
        window.requestAnimationFrame(checkAudioOutputPosition);

        oscillator.onended = () => {
            document.querySelector('#startButton').disabled = false;
            indicator.style.background = "gray";
        }
      }

      function refreshValues() {
        document.querySelector("#audioTime").innerHTML = "context.currentTime: " + context.currentTime;
        document.querySelector("#performanceTime").innerHTML = "performance.now(): " + performance.now();

        let timestamp = context.getOutputTimestamp();
        document.querySelector("#timestampContextTime").innerHTML = "timestamp audio: " + timestamp.contextTime;
        document.querySelector("#timestampPerformanceTime").innerHTML = "timestamp performance: " + timestamp.performanceTime;
      }
    </script>
  </body>
</html>