MinionAttack
MinionAttack

Reputation: 540

HTML5 Audio gets louder after pausing then playing again

I'm making an audio player with JavaScript, everything works fine until I add a sound visualizer. When I pause the song and then play it again, the sound gets more louder every time I do it, until it gets distorsionated.

I'm newbie with the HTML5 Audio API, I've tried to set the volume as a fixed value, but not works.

The code of the visualizer it's:

function visualizer(audio) {
    let context = new AudioContext();

    const gainNode = context.createGain();
    gainNode.gain.value = 1; // setting it to 100%
    gainNode.connect(context.destination);

    let src = context.createMediaElementSource(audio);
    let analyser = context.createAnalyser();

    let canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    let ctx = canvas.getContext("2d");

    src.connect(analyser);
    analyser.connect(context.destination);

    analyser.fftSize = 2048;

    let bufferLength = analyser.frequencyBinCount;

    let dataArray = new Uint8Array(bufferLength);

    let WIDTH = ctx.canvas.width;
    let HEIGHT = ctx.canvas.height;

    let barWidth = (WIDTH / bufferLength) * 1.5;
    let barHeight;
    let x = 0;

    let color = randomColor();

    function renderFrame() {
      requestAnimationFrame(renderFrame);
      x = 0;
      analyser.getByteFrequencyData(dataArray);
      ctx.clearRect(0, 0, WIDTH, HEIGHT);

      for (let i = 0; i < bufferLength; i++) {
        barHeight = dataArray[i];

        ctx.fillStyle = color;
        ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
        x += barWidth + 1;

      }
    }

    musicPlay();
    renderFrame();
  }

And:

  function musicPlay() {
    status = 'playing';
    audio.play();
  }

So, I don't know if I'm doing something wrong on the audio analyzer, I've tried to make a global context and don't do the new AudioContext(); every time I enter on the function, also I've tried to specify a fixed volume with:

audio.volume = 1; 

or with the GainNode as you can see on the function, but it's not working.

Where is my mistake and why the sound gets louder?

Regards!

--- Update 1 ---

The audio it's loaded from an URL:

function loadAudioElement(url) {
    return new Promise(function (resolve, reject) {
      let audio = new Audio();
      audio.addEventListener('canplay', function () {
        /* Resolve the promise, passing through the element. */
        resolve(audio);
      });
      /* Reject the promise on an error. */
      audio.addEventListener('error', reject);
      audio.src = url;
    });
  }

And on my player I have:

let playButtonFunction = function () {
      if (playstatus === 'pause') {
        loadAudioElement(audio.src).then(
          visualizer(audio)
        );
      } else if (playstatus === 'playing') {
        musicPause();
      }
    };

Upvotes: 1

Views: 508

Answers (2)

Ido Ayal
Ido Ayal

Reputation: 111

I had a similar issue, did you try to set the audio context to a global object?

This is what I found here:

https://developer.mozilla.org/en-US/docs/Web/API/AudioContext

It's recommended to create one AudioContext and reuse it instead of initializing a new one each time

The AudioContext interface represents an audio-processing graph built from audio modules linked together, each represented by an AudioNode.

An audio context controls both the creation of the nodes it contains and the execution of the audio processing, or decoding. You need to create an AudioContext before you do anything else, as everything happens inside a context. It's recommended to create one AudioContext and reuse it instead of initializing a new one each time, and it's OK to use a single AudioContext for several different audio sources and pipeline concurrently.

Upvotes: 1

MinionAttack
MinionAttack

Reputation: 540

Well, as Get Off My Lawn pointed, I was adding by mistake multiple audio elements.

The solution was taking the code of load the song outside the playButtonFunction and only do:

let playButtonFunction = function () {
      if (playstatus === 'pause') {
        musicPlay();
      } else if (playstatus === 'playing') {
        musicPause();
      }
    };

But I still had one problem, with the next/previous functions. In these cases I need call the loadAudioElement function because the song is changing (when you press play/pause no, it's the same song) but with this I have the same problem again.

Well, after a bit of digging, I found that if you want to play a playlist and visualize the music all the time, YOU HAVE TO RELEASE THE OLD CONTEXT BEFORE LOAD THE NEW SONG. Not only to avoid the increase of the song volume, the cpu and memory will also get increased after 3 - 4 songs and the browser will start to run slowly depending on the machine. So:

1 - I made a global variable called clearContextAudio = false;

2 - On my next/previous functions I added this code:

if (closeAudioContext) { //MANDATORY RELEASE THE PREVIOUS RESOURCES TO AVOID OBJECT OVERLAPPING AND CPU-MEMORY USE
      context.close();
      context = new AudioContext();
}
loadAudioElement(audio.src).then(
   visualizer(audio)
);

3 - On my visualizer(audio) function I changed:

let context = new AudioContext();

to

closeAudioContext = true; //MANDATORY RELEASE THE PREVIOUS RESOURCES TO AVOID OBJECT OVERLAPPING AND CPU-MEMORY USE

The value it's initialized to false because the first time there is no song playing, and after play a song you will always need to release the old resources, so the variable will always set to true. Now, you can skip all the times you want a song and not concern about the memory and the overlapping issues.

Hope this helps someone else trying to achieve the same thing! Regards!

Upvotes: 0

Related Questions