Search code examples
javascriptweb-audio-api

Memory leaks in OfflineAudioContext


Memory leaks in OfflineAudioContext.

Launch Task Manager in chrome. After 10 times run createBuffer memory used 1.5GB. After the tab exceeds the RAM limit, it will break.

How can I avoid memory leaks?

Example on jsfiddle.

var audioCtx = new(window.AudioContext || window.webkitAudioContext)();

// define variables

var play = document.querySelector('.play');
var stop = document.querySelector('.stop');
var progress = document.querySelector('#progress');
var buffer = document.querySelector('.buffer');
var myBuffer = null;

var rendering = false;
// use XHR to load an audio track, and
// decodeAudioData to decode it and stick it in a buffer.
// Then we put the buffer into the source

function getData() {
  request = new XMLHttpRequest();

  request.open('GET', 'https://s3-ap-northeast-1.amazonaws.com/storage.cowrite.decodeapps.io/Materials/Media/Audio/5a0aca5f35965-20171114-105007.mp3', true);

  request.responseType = 'arraybuffer';

  request.onload = function() {
    progress.innerText = 'loaded';
    var audioData = request.response;
    audioCtx.decodeAudioData(audioData, function(buffer) {
      myBuffer = buffer;

      createBuffer();
    }).catch(function(err) {
      console.log('Rendering failed: ' + err);
      // Note: The promise should reject when startRendering is called a second time on an OfflineAudioContext
    });
  }
  request.send();
}
buffer.setAttribute('disabled', 'disabled');
progress.innerText = 'loading...';
var cntRenfered = 0;

function createBuffer() {
  var offlineCtx = new OfflineAudioContext(2, myBuffer.length, myBuffer.sampleRate);
  var source = offlineCtx.createBufferSource();
  source.buffer = myBuffer;
  source.connect(offlineCtx.destination);
  source.start();
  //source.loop = true;
  offlineCtx.oncomplete = function(e) {
    progress.innerText = 'completed';
    buffer.removeAttribute('disabled');
    source.disconnect(offlineCtx.destination);
    cntRenfered++;
    buffer.innerText = 'reCreateBuffer ' + cntRenfered;
  }
  offlineCtx.startRendering();
}

buffer.onclick = function() {
  progress.innerText = 'rendering...';
  buffer.setAttribute('disabled', 'disabled');
  createBuffer();
}

getData();
<div id="progress">
  loading
</div>


<button class="buffer">
  reCreateBuffer
</button>


Solution

  • I'm find workaround.

    Example on jsfiddle.

    var audioCtx = new(window.AudioContext || window.webkitAudioContext)();
    
    // define variables
    
    var play = document.querySelector('.play');
    var stop = document.querySelector('.stop');
    var progress = document.querySelector('#progress');
    var buffer = document.querySelector('.buffer');
    var myBuffer = null;
    
    var rendering = false;
    // use XHR to load an audio track, and
    // decodeAudioData to decode it and stick it in a buffer.
    // Then we put the buffer into the source
    
    function getData() {
      request = new XMLHttpRequest();
    
      request.open('GET', 'https://s3-ap-northeast-1.amazonaws.com/storage.cowrite.decodeapps.io/Materials/Media/Audio/5a0aca5f35965-20171114-105007.mp3', true);
    
      request.responseType = 'arraybuffer';
    
      request.onload = function() {
        progress.innerText = 'loaded';
        var audioData = request.response;
        audioCtx.decodeAudioData(audioData, function(b) {
          myBuffer = b;
          buffer.removeAttribute('disabled');
        }).catch(function(err) {
          console.log('Rendering failed: ' + err);
          // Note: The promise should reject when startRendering is called a second time on an OfflineAudioContext
        });
      }
      request.send();
    }
    buffer.setAttribute('disabled', 'disabled');
    progress.innerText = 'loading...';
    var cntRenfered = 0;
    
    function createBuffer() {
      var offlineCtx = getIFrameOfflineContext(2, myBuffer.length, myBuffer.sampleRate);
      var source = offlineCtx.createBufferSource();
      source.buffer = myBuffer;
      source.connect(offlineCtx.destination);
      source.start();
      //source.loop = true;
      offlineCtx.oncomplete = function(e) {
        console.log('Rendering completed successfully');
        progress.innerText = 'completed';
        buffer.removeAttribute('disabled');
        source.disconnect(offlineCtx.destination);
        cntRenfered++;
        buffer.innerText = 'reCreateBuffer ' + cntRenfered;
        setTimeout(function() {
          IFrameReload();
        });
      }
      offlineCtx.startRendering();
    }
    
    buffer.onclick = function() {
      progress.innerText = 'rendering...';
      buffer.setAttribute('disabled', 'disabled');
      createBuffer();
    }
    
    getData();
    var iFrame;
    
    function createIframe() {
      iFrame = document.createElement('iframe');
      iFrame.style.display = 'none';
      iFrame.onload = () => {
        const script = document.createElement('script');
        script.innerHTML =
          `
            function reload() {
              location.reload();
            }
    
            function createOfflineContext(NUMBER_OF_CHANNEL, duration, sampleRate) {
              return new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(NUMBER_OF_CHANNEL,duration,sampleRate);
            }
          `;
        iFrame.contentDocument.body.appendChild(script);
      };
      document.body.appendChild(iFrame);
    }
    
    function getIFrameOfflineContext(NUMBER_OF_CHANNEL, duration, sampleRate) {
      return iFrame.contentWindow.createOfflineContext(NUMBER_OF_CHANNEL, duration, sampleRate);
    }
    
    function IFrameReload() {
      iFrame.contentWindow.reload();
    }
    
    
    createIframe();
    <div id="progress">
      loading
    </div>
    
    <button class="buffer">
      reCreateBuffer
    </button>