Search code examples
javascriptxmlhttprequestblobweb-audio-api

javascript audio api play blob url


I worked out a testing function for Web Audio API to play url blob:

// trigger takes a sound play function
function loadSound(url, trigger) {
    let context = new (window.AudioContext || window.webkitAudioContext)();
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
        trigger(()=>{
            // play sound
            var source = context.createBufferSource(); // creates a sound source
            source.buffer = buffer;                    // tell the source which sound to play
            source.connect(context.destination);       // connect the source to the context's destination (the speakers)
            source.start();
        });
    }, e=>{
        console.log(e);
    });
    }
    request.send();
}



loadSound(url, fc=>{
    window.addEventListener('click', fc);
});

this is just for test, actually, I need a function to call to directly play the sound from url, if any current playing, quit it.

let ac;
function playSound(url) {
    if(ac){ac.suspend()}
    let context = new (window.AudioContext || window.webkitAudioContext)();
    let request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
        // play sound
        let source = context.createBufferSource();  // creates a sound source
        source.buffer = buffer; // tell the source which sound to play
        source.connect(context.destination);    // connect the source to the context's destination (the speakers)
        // source.noteOn(0);    // play the source now
        ac = context;
        source.start();
    }, e=>{
        console.log(e);
    });
    }
    request.send();
}


window.addEventListener('click',()=>{
    playSound(url);
});

I did not do much modification, however, the second version, triggers works fine, but always produces no sound.

I suspect it may be this variable scope issue, I will be very glad if you can help me debug it.

since the blob url is too long, I put two versions in code pen.

  1. working version

  2. not working version


Solution

  • Instead of calling supsend on the stored AudioContext, save a reference to the AudioBufferSourceNode that is currently playing. Then check if the reference exits and call stop() whenever you play a new sound.

    const context = new AudioContext();
    let bufferSource = null;
    
    function playSound(url) {
      if (bufferSource !== null) {
        bufferSource.stop();
        bufferSource = null;
      }
    
      let request = new XMLHttpRequest();
      request.open('GET', url, true);
      request.responseType = 'arraybuffer';
      request.onload = function() {
        context.decodeAudioData(request.response, (buffer) => {
          bufferSource = context.createBufferSource();
          bufferSource.buffer = buffer; 
          bufferSource.connect(context.destination); 
          bufferSource.start();
          bufferSource.addEventListener('ended', () => {
            bufferSource = null;
          });
        }, (error) => {
          console.log(error);
        });
      }
      request.send();
    }
    
    
    window.addEventListener('click', () => {
      playSound(url);
    });