Beezle-Bug
Beezle-Bug

Reputation: 167

Audiocontext is not playing any sound

as a follow-up on this post: How to rapidly play multiple copies of a soundfile in javascript I created a small demo page to illustrate the core of my problem.

My goal is to rapidly play the same audiofile over and over again while a user holds down a button, without crashing the browser ;-) (second line in the fiddle)

My initial method uses clone node to create multiple audio objects in the DOM. This works actually fine in chrome and edge, but safari and firefox run into problems after a while. This leads to dis-synchronized audios, and audios that keep on starting after the user has released the button.

So Codebreaker007 proposed to use audio-context instead, which resulted in a couple of different problems. Chrome replies:

The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page.

Chrome and firefox don't play the audio file. I then followed the google guide and got at least the error messages to be gone, but still no audible audio. Using the web audio plugin for chrome I could at one point see that the audio nodes are being created correctly. How can I make them audible? How can I get the Audio Context to start?

enter image description here

I think I'm quite close to the solution, so let's fix this together.

 <!doctype html>
<html class="h-100" lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Test</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script>
     AudioContext:true;


    var clickingBuffer = null;
    var timer
    var inStart = 0
    var inStop = 0
    var timer = null
    var type = ""
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    var context = new AudioContext();

// =========== Variant 1: Clone Node ========== //

var sound = new Audio("https://sounds4email.com/wav/hellobaby.mp3");
sound.preload = 'auto';
sound.load();

function triggersound(){
    console.log("triggerSound")
    var click=sound.cloneNode();
    click.volume=1;
    click.play();
}

// =========== Variant 2: AudioContext  ========== //


function loadClickSound(url) {
    console.log("loading sound")
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';
    // Decode asynchronously
    request.onload = function() {
        context.decodeAudioData(request.response, function(buffer) {
            if (!buffer) {
                console.log('Error decoding file data: ' + url);
                return;
            }
        clickingBuffer = buffer;
        });
    request.onerror = function() {
        console.log('BufferLoader: XHR error');        
        };
    console.og("sound buffered")
    request.send();
    };
}

function playSound(buffer, time, volume) {   
    console.log("playSound")          
    context.resume().then(() => {
        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)
        var gainNode = context.createGain();          // Create a gain node
        source.connect(gainNode);                     // Connect the source to the gain node
        gainNode.connect(context.destination);        // Connect the gain node to the destination
        gainNode.gain.value = volume;                  // Set the volume
        source.start(time);                           // play the source at the deisred time 0=now  
         console.log('Playback resumed successfully');
  }); 

}




// =========== RAPID FIRE ========== //


function stop() {
    console.log("stop")
    inStop = 1
}


// Initializing the spinning sequence. Blocking other user interaction
function start(tp) {
    type = tp
   console.log("active")
   context.resume().then(() => {
    console.log('Playback resumed successfully');
    if (null === timer) {
       timer = setInterval(timerCallback, 200)
       inStart = 1
       }
  });



}
/**
 * Timer callback
 */
 function timerCallback() {
 console.log(type + " " + inStart + " " + inStop)
  if (inStart) {
      if(type==="node"){
         triggersound()

      } else if(type==="context"){
        playSound(clickingBuffer, 0, 1)

      }
  }
  if (inStop) {
    inStop = 0
    clearTimeout(timer)
    timer = null
    console.log("stopped")
    }
}



// =========== DOC READY ========== //
$( document ).ready(function() {
    console.log( "ready!" );

    // You call with in your document ready
    loadClickSound("https://sounds4email.com/wav/hellobaby.mp3");
    // Fix up prefixi

});

// =================================================================================//
</script>
</head>

<body class="d-flex flex-column align-content-center align-items-center justify-content-center w-100 h-100" >
    <div class="row p-1 w-100">
        <div class="col">
            Click once:
        </div>

    <button id="clickNode" style="width: 100px; height: 100px;" class="col m-1" onclick="triggersound()">
        Clone Node
    </button>
    <button id="clickContext" style="width: 100px; height: 100px;" class="col m-1" onclick="playSound(clickingBuffer, 0, 1)">
        Audio Context
    </button>
</div>

<div class="row p-1 w-100">

    <div class="col">
        Press and hold:
    </div>
    <button id="autoNode" style="width: 100px; height: 100px;" class="col m-1" onmousedown="start('node')" onmouseup="stop()">
       Auto Clone Node
    </button>
    <button id="autoContext" style="width: 100px; height: 100px;" class="col m-1" onmousedown="start('context')" onmouseup="stop()">
       Auto Audio Context
    </button>
</div>
</body>
</html>

Upvotes: 4

Views: 3549

Answers (1)

Codebreaker007
Codebreaker007

Reputation: 2989

Ok here is the code you want for your function. This code can use a local file to test with to rule out all kind of security issues (the xhr code is included). It uses plain JS ES5 and has been tested with firefox and chrome on different OS. Please put this into an audio_test.html as it is to verify the function. One word of warning, don't mix html tags and java script function calls, use event listeners as I demostrate it in the code.The stop button function is just a starter, to relock after play extra code is necessary I didn't bother to write. Do not try to create buffers in an ongoing way because this fills up memory and crashes the browsers/OS. If you want overlapping sound this means using buffer arrays, but thats would be an other question.

<!DOCTYPE html>
<!-- Author: codebreaker007 @ stackoverflow -->
<html class="h-100" lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->

  <title>Web Audio API: load + play</title>
</head>
<body>
  <p>Example of using the Web Audio API to load a sound file and </br>play once, play continous on mousedown or stop play
  and start playing on user-click.</p>
  Tested in Firefox and Chrome</p>
  <input type="file" accept="audio/*" value="">
  <button id="playonce" disabled=disabled>Play once</button>
  <button id="playstop" disabled=disabled>Stop play</button>
  <button id="playcont" disabled=disabled>Play cont</button>
<script>
    /* global AudioContext:true,
*/
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var source = null;
var clickingBuffer = null;
var mouseIsDown = false;
var buttonPo = document.getElementById("playonce");
var buttonPs = document.getElementById("playstop");
var buttonPc = document.getElementById("playcont");

if (document.readyState!="loading") docReady();
/* Modern browsers */
else document.addEventListener("DOMContentLoaded", docReady);
function docReady() {

    buttonPo.addEventListener("click", function(e){
        playSound(clickingBuffer, 0, 0, 0.8);
        buttonPs.disabled = false;
    });
    buttonPs.addEventListener("click", function(e){
    if (source) {
        source.stop(0);
      }
       buttonPs.disabled = true;
    });

    buttonPc.addEventListener("mousedown", function(e){
        playSound(clickingBuffer, 1, 0, 1);
        //  while(mouseIsDown)   playSound(clickingBuffer, 0, 1);
    });
    buttonPc.addEventListener("mouseup", function(e){
       if (source) {
        source.stop(0);
        }
    });
}

function playSound(buffer2play, isLoop, time, volume) { 
  console.log("playsound called");
   source = context.createBufferSource();   // creates a sound source
  source.buffer = buffer2play;                     // tell the source which sound to play
  if (isLoop) source.loop = true;
  else  source.loop = false;
  source.connect(context.destination);          // connect the source to the context's destination (the speakers)
  var gainNode = context.createGain();          // Create a gain node
  source.connect(gainNode);                     // Connect the source to the gain node
  gainNode.connect(context.destination);        // Connect the gain node to the destination
  gainNode.gain.value = volume;                  // Set the volume
  source.start(time);                           // play the source at the deisred time 0=now  
  console.log("playSound");    
 }

function initSound(arrayBuffer) {
  context.decodeAudioData(arrayBuffer, function(buffer) {
    // clickingBuffer is global to reuse the decoded audio later.
    clickingBuffer = buffer;
// Test routine activate buttons    
     buttonPo.disabled = false;
     buttonPc.disabled = false;
  }, function(e) {
    console.log('Error decoding file', e);
  }); 
}

// User selects file, read it as an ArrayBuffer and pass to the API.
var fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', function(e) {  
  var reader = new FileReader();
  reader.onload = function(e) {
    initSound(this.result);
  };
  reader.readAsArrayBuffer(this.files[0]);
}, false);

// Load file from a URL as an ArrayBuffer.
// Example: loading via xhr2: loadSoundFile('sounds/test.mp3');
function loadClickSound(url) {
    console.log("loading sound");
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';
    // Decode asynchronously
    request.onload = function() {
    // Decode asynchronously
    initSound(this.response); // this.response is an ArrayBuffer.
  };
  xhr.send();
}

</script>
</body>
</html>

Upvotes: 1

Related Questions