Search code examples
javascriptaudioweb-audio-api

Web audio API, problem using the panNode, the sounds only play once


I am trying to integrate some panning effects to some sounds in a small testing app. It works fine except for one important issue: each sound only plays once!

I have tried several ways to attempt to bypass that issue without any success. The thing is, I can't pinpoint where the problem comes from. Here is my code, and a little explanation bellow.

const audio = new Audio('audio/background.mp3');
const footstep = new Audio('audio/footstep1.mp3');
const bumpWall1 = new Audio(`audio/bump-wall1.mp3`);
const bumpWall2 = new Audio(`audio/bump-wall2.mp3`);
const bumpWall3 = new Audio(`audio/bump-wall3.mp3`);
const bumpWall4 = new Audio(`audio/bump-wall4.mp3`);
const bumpWallArray = [bumpWall1, bumpWall2, bumpWall3, bumpWall4];

audio.volume = 0.5;

function play(sound, dir) {
  let audioContext = new AudioContext();
  let pre = document.querySelector('pre');
  let myScript = document.querySelector('script');

  let source = audioContext.createMediaElementSource(sound);

  let panNode = audioContext.createStereoPanner();
  source.connect(panNode);
  panNode.connect(audioContext.destination);
  if (dir === "left") {
    panNode.pan.value = -0.8
  } else if (dir === "right") {
    panNode.pan.value = 0.8;
  } else {
    panNode.pan.value = 0;
  }
  sound.play();
}

So basically, when you call the play() function it plays the sound either on the left, the right, or the middle. But each sound is only played once. For example, if the footstep was played one time, it is never played again if I call the play() function on it.

Can anyone help me with that?


Solution

  • In your developer console, you should have a message stating something along the lines of

    Uncaught InvalidStateError: Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode.

    (At least in Chrome,) you can't connect a MediaElement several times to a MediaElementSourceNode.

    To avoid this, you would have to disconnect this MediaElement from the MediaElementSourceNode, but this isn't possible...

    The best in your situation is probably to use directly AudioBuffers rather than HTMLAudioElements, moreover if you don't append them in the doc.

    let audio;
    const sel = document.getElementById( 'sel' );
    // create a single AudioContext, these are not small objects
    const audioContext = new AudioContext();
    fetch( 'https://dl.dropboxusercontent.com/s/agepbh2agnduknz/camera.mp3' ).then( resp => resp.arrayBuffer() )
    .then( buf => audioContext.decodeAudioData( buf ) )
    .then( audioBuffer => audio = audioBuffer )
    .then( () => sel.disabled = false )
    .catch( console.error );
    
    function play(sound, dir) {
    
      let source = audioContext.createBufferSource();
      source.buffer = sound;
      
      let panNode = audioContext.createStereoPanner();
      source.connect( panNode );
      panNode.connect( audioContext.destination );
      if (dir === "left") {
        panNode.pan.value = -0.8
      } else if (dir === "right") {
        panNode.pan.value = 0.8;
      } else {
        panNode.pan.value = 0;
      }
      source.start( 0 );
      
    }
    
    sel.onchange = evt => play( audio, sel.value );
    <select id="sel" disabled>
      <option>left</option>
      <option>center</option>
      <option>right</option>
    </select>