Search code examples
javascriptdomweb-audio-api

Javascript - Web Audio API -- Range type Input is not dynamic in Firefox, but it is dynamic in Chrome -added reprex


  • The gain controller works dynamically on chrome, but in firefox it does not. I need to re-play audio file for browser to recognize new input value of the range input for firefox. I couldn't figure out why.
  • You need to put an mp3 file into "example.mp3" part by the way.
  • I'm new in here. Tried the reprex. Sorry if it is not as it should be

[HTML + Javascript]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div>
      <button class="button">click</button>
      <input
        class="input"
        type="range"
        step="0.01"
        min="0"
        max="1.20"
        value="0.60"
      />
    </div>
  </body>
  <script src="script.js" type="text/javascript"></script>
</html>
<script type="text/javascript">
  loopify("example.mp3", function (err, loop) {
    // If something went wrong, `err` is supplied
    document.querySelector(".button").dataset.playing = "false";
    if (err) {
      return console.err(err);
    }

    document.querySelector(".button").addEventListener("click", function () {
      if (this.dataset.playing === "false") {
        loop.play();

        this.dataset.playing = "true";
      } else if (this.dataset.playing === "true") {
        setTimeout(loop.stop, 250);
        this.dataset.playing = "false";
      }
    });
  });
</script>

[Javascript]

(function () {
  function loopify(uri, cb) {
    var context = new (window.AudioContext || window.webkitAudioContext)(),
      request = new XMLHttpRequest();
    const gainNode = context.createGain();

    request.responseType = "arraybuffer";
    request.open("GET", uri, true);

    // XHR failed
    request.onerror = function () {
      cb(new Error("Couldn't load audio from " + uri));
    };

    // XHR complete
    request.onload = function () {
      context.decodeAudioData(request.response, success, function (err) {
        // Audio was bad
        cb(new Error("Couldn't decode audio from " + uri));
      });
    };

    request.send();

    function success(buffer) {
      var source;

      // fade-out
      document.querySelector(".button").onclick = function () {
        if (this.dataset.playing === "true") {
          gainNode.gain.setTargetAtTime(0, context.currentTime, 0.25);
        }
      };
      function play() {
        // Stop if it's already playing
        stop();

        // Create a new source (can't replay an existing source)
        source = context.createBufferSource();
        source.connect(gainNode).connect(context.destination);
        // fade-in
        gainNode.gain.value = 0.1;
        gainNode.gain.setTargetAtTime(
          document.querySelector(".input").value,
          context.currentTime,
          0.25
        );
        //gain input assignment
        document.querySelector(".input").addEventListener(
          "input",
          function () {
            gainNode.gain.value = this.value;
          },
          false
        );

        // Set the buffer
        source.buffer = buffer;
        source.loop = true;

        // Play it

        source.start(0);
      }

      function stop() {
        // Stop and clear if it's playing
        if (source) {
          source.stop();
          source = null;
        }
      }

      cb(null, {
        play: play,
        stop: stop,
      });
    }
  }

  loopify.version = "0.1";

  if (typeof define === "function" && define.amd) {
    define(function () {
      return loopify;
    });
  } else if (typeof module === "object" && module.exports) {
    module.exports = loopify;
  } else {
    this.loopify = loopify;
  }
})();

[CSS]

div {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

Solution

  • After some experimentation and trying to reproduce your code, I've stumbled on something curious.

    Whenever we use gainNode.gain.setTargetAtTime we are unable to update Gain value by using the setter gainNode.gain.value. I could not find any explanation in the documentation of setTargetAtTime or value as to why this happens.

    Fortunately there is a fix without breaking the fade-in that setTargetAtTime provides.
    The solution lies in setting the gain value with setValueAtTime instead of with the setter.

    const input = document.querySelector(".input");
    
    gainNode.gain.value = 0.1;
    gainNode.gain.setTargetAtTime(
      input.value,
      context.currentTime,
      0.25
    );
    
    input.addEventListener(
      "input",
      function () {
        gainNode.gain.setValueAtTime(
          this.value,
          context.currentTime
        )
      },
      false
    );