Skip to content Skip to sidebar Skip to footer

Possible Memory Leak Or Something Else?

I've made a visualizer in javascript that when you select a music directory your're able to select files within that directory to play and have the visualizer move to. But it would

Solution 1:

Like noted by yuriy636, you are starting a new animation for every new song, without never stopping the previous one. So when you played 5 songs, you still have 5 visualizations rendering loop running at every frame, and 5 analyzers.

The best to do here is to refactor your code :

  • create a single analyzer, only update which stream feeds it
  • make the canvas animation autonomous, declare it once at first load
  • since you've got only one <canvas>, start only one rendering animation

When using a single analyzer, your render doesn't use anything new when you change the source, it's always the same canvas, the same analyzer, the same visualization.

Here is a quick proof of concept, really dirty, but I hope you'll be able to undertstand what I did and why.

window.onload = function() {
  var input = document.getElementById("file");
  var audio = document.getElementById("audio");
  var selectLabel = document.querySelector("label[for=select]");
  var audioLabel = document.querySelector("label[for=audio]");
  var select = document.querySelector("select");


  var viz = null;

  // removed all the IDK what it was meant for directory special handlers

  function displayFiles() {
    select.innerHTML = "";
    // that's all synchronous, why Promises ?
    res = Array.prototype.slice.call(input.files);
    res.forEach(function(file, index) {
      if (/^audio/.test(file.type)) {
        var option = new Option(file.name, index);
        select.appendChild(option);
      }
    });

    if (res.length) {
      var analyser = initAudioAnalyser();
      viz = initVisualization(analyser);
      // pre-select the first song ?
      handleSelectedSong();
      audio.pause();
    }
  }

  function handleSelectedSong(event) {
    if (res.length) {
      var index = select.value;
      var track = res[index];
      playMusic(track)
        .then(function(filename) {
          console.log(filename + " playback completed")
        })
      viz.play();
    } else {
      console.log("No songs to play")
    }
  }

  function playMusic(file) {
    return new Promise(function(resolve) {
      var url = audio.src;
      audio.pause();
      audio.onended = function() {
        audio.onended = null;
        // arguablily useless here since blobURIs are just pointers to real file on the user's system
        if (url) URL.revokeObjectURL(url);
        resolve(file.name);
      }

      if (url) URL.revokeObjectURL(url);
      url = URL.createObjectURL(file);
      //      audio.load(); // would just set a 404 since you revoked the URL just before
      audio.src = url;
      audio.play();
      audioLabel.textContent = file.name;

    });
  }

  function initAudioAnalyser() {
    var context = new AudioContext();
    var analyser = context.createAnalyser();
    analyser.fftSize = 16384;

    var src = context.createMediaElementSource(audio);
    src.connect(analyser);
    src.connect(context.destination);

    return analyser;

  }

  function initVisualization(analyser) {

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

    var bufferLength = analyser.frequencyBinCount;

    var dataArray = new Uint8Array(bufferLength);
    var WIDTH = canvas.width;
    var HEIGHT = canvas.height;

    var barWidth = (WIDTH / bufferLength) * 32;
    var barHeight;
    var x = 0;

    var paused = true;
    
    function renderFrame() {
      if (!paused) {
        requestAnimationFrame(renderFrame);
      } else {
        return;
      }
      x = 0;

      analyser.getByteFrequencyData(dataArray);

      ctx.fillStyle = "#1b1b1b";
      ctx.fillRect(0, 0, WIDTH, HEIGHT);

      ctx.fillStyle = "rgb(5,155,45)"
      ctx.beginPath();
      for (var i = 0; i < bufferLength; i++) {
        barHeight = dataArray[i];
        // micro-optimisation, but concatenating all the rects in a single shape is easier for the CPU
        ctx.rect(x, (((HEIGHT - barHeight - 5 % barHeight) + (20 % HEIGHT - barHeight))), barWidth, barHeight + 20 % HEIGHT);
        x += barWidth + 2;
      }
      ctx.fill();
    }
    var viz = window.viz = {
      play: function() {
        if(paused){
          paused = false;
          renderFrame();
          }
      },
      pause: function() {
        paused = true;
        clearTimeout(pauseTimeout);
        pauseTimeout = null;
      },
    };
    // we can even add auto pause linked to the audio element
    var pauseTimeout = null;
    audio.onpause = function() {
      // let's really do it in 2s to keep the tear down effect
      pauseTimeout = setTimeout(viz.pause, 2000);
    }
    audio.onplaying = function() {
      clearTimeout(pauseTimeout);
      // we were not playing
      if(!pauseTimeout){
        viz.play();
        }
    }
    return viz;
  }

  input.addEventListener("change", displayFiles);
  select.addEventListener("change", handleSelectedSong);
}
<canvas id="canvas" width="window.innerWidth" height="window.innerHeight"></canvas>
<div id="content">
  <label class="custom-file-upload">
  Select Music directory <input id="file" type="file" accept="audio/*" directory allowdirs webkitdirectory/>
   <p style="color: rgb(5,195,5);">Now playing:<label for="audio"></label></p>

  <p style="color: rgb(5,195,5);">Select Song</p>
  <select id="select">
    </select>
  <audio id="audio" controls></audio>

Post a Comment for "Possible Memory Leak Or Something Else?"