javascripthtmlhtml5-audiowebkitaudiocontext

HTML5 Audio API - "audio resources unavailable for AudioContext construction"


I'm trying to create a graphic equalizer type visualization for HTML5 audio - Chrome only at this point, using webkitAudioContext.

I'm finding unusual and unpredictable behaviour when I try to change the source of the audio i.e. to play a different song. I read somewhere I should wait until the "canplay" event on the audio is triggered before connecting it to the context / analyser:

var context, sourceNode, analyser, javascriptNode, audio;

var ctx = $("#songcanvas").get()[0].getContext("2d");    


function loadSong(url) { 
    if (audio!=undefined) { audio.pause(); }
    audio = new Audio();
    audio.src = url;
    audio.addEventListener("canplay", function(e) {
         setupAudioNodes();
    }, false);
}

function setupAudioNodes() {
        context = new webkitAudioContext();
        javascriptNode = context.createJavaScriptNode(2048, 1, 1);
        javascriptNode.connect(context.destination);

        analyser = context.createAnalyser();
        analyser.smoothingTimeConstant = 0.3;
        analyser.fftSize = 512;

        sourceNode = context.createMediaElementSource(audio);
        sourceNode.connect(analyser);
        analyser.connect(javascriptNode);

        sourceNode.connect(context.destination);

        javascriptNode.onaudioprocess = function() {
            var array =  new Uint8Array(analyser.frequencyBinCount);
            analyser.getByteFrequencyData(array);
            ctx.clearRect(0, 0, 1000, 325);
            ctx.fillStyle="rgba(32, 45, 21,1)";
            drawSpectrum(array);
        }
        audio.play();
}

function drawSpectrum(array) {
    for ( var i = 0; i < (array.length); i++ ){
        var value = array[i];
        ctx.fillRect(i*5,325-value,3,325);
    }
};

The first three or four times I change the source, it works, then eventually fails with "Uncaught SyntaxError: audio resources unavailable for AudioContext construction"

Full demo here http://jsfiddle.net/eAgQN/


Solution

  • I'd recommend staying away from creating a JavaScriptNode if you're simply going to be drawing the spectrum. Instead try utilizing the requestAnimationFrame method as it will save CPU cycles and will get you closer to a constant 60fps (is especially useful if your window/tab is in the background as any function wrapped in a requestAnimationFrame will wait until the window is in focus again before firing).

    The reason you're probably getting that error is because A) you can only create a single AudioContext per window, B) you should be disconnecting the MediaElementSource when you're finished using it, and C) you should also be removing the actual Audio element too.

    Here's a your demo with the changes I outlined above: http://jsbin.com/acolet/1/

    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    var context = new AudioContext(),
        audioAnimation, sourceNode, analyser, audio,
        songs  = document.getElementById('songs'),
        canvas = document.getElementById('songcanvas'),
        WIDTH  = canvas.width,
        HEIGHT = canvas.height,
        // get the context from the canvas to draw on
        ctx = canvas.getContext('2d'),
        gradient = ctx.createLinearGradient(0, 0, 0, HEIGHT),
        bar = { width: 2, gap: 2, ratio: HEIGHT / 256 };
    
    gradient.addColorStop(1.00,'#000000');
    gradient.addColorStop(0.75,'#ff0000');
    gradient.addColorStop(0.25,'#ffff00');
    gradient.addColorStop(0.00,'#ffff00');
    ctx.fillStyle = gradient;
    
    songs.addEventListener('click', loadSong, false);
    
    function loadSong(e) {
      e.preventDefault();
      var url = e.target.href;
      if (!url) return false;
      if (audio) audio.remove();
      if (sourceNode) sourceNode.disconnect();
      cancelAnimationFrame(audioAnimation);
      audio = new Audio();
      audio.src = url;
      audio.addEventListener('canplay', setupAudioNodes, false);
    }
    
    function setupAudioNodes() {
      analyser = (analyser || context.createAnalyser());
      analyser.smoothingTimeConstant = 0.8;
      analyser.fftSize = 512;
    
      sourceNode = context.createMediaElementSource(audio);
      sourceNode.connect(analyser);
      sourceNode.connect(context.destination);
      
      audio.play();
      drawSpectrum();
    }
    
    function drawSpectrum() {
      var freq = new Uint8Array(analyser.frequencyBinCount);
      analyser.getByteFrequencyData(freq);
      ctx.clearRect(0, 0, WIDTH, HEIGHT);
      audioAnimation = requestAnimationFrame(drawSpectrum);
      for ( var i = freq.length - 1; i >= 0 ; i-- ){
        var x = i * (bar.width + bar.gap);
        var y = HEIGHT - (freq[i] * bar.ratio);
        ctx.fillRect(x, y, bar.width, HEIGHT);
      }
    }
    body { font-family: sans-serif; background-color: #000; }
    a { color: #fff; text-decoration: none; }
    ul { padding: 20px 0; width: 100px; }
    ul, canvas { float: left; }
    <ul id="songs">
      <li><a class="song" href="http://upload.wikimedia.org/wikipedia/en/4/45/ACDC_-_Back_In_Black-sample.ogg">ACDC</a></li>
      <li><a class="song" href="http://upload.wikimedia.org/wikipedia/en/9/9f/Sample_of_%22Another_Day_in_Paradise%22.ogg">Phil Collins</a></li>
      <li><a class="song" href="http://upload.wikimedia.org/wikipedia/en/1/1f/%22Layla%22%2C_Derek_and_the_Dominos_song_%28sample%29.ogg">Clapton</a></li>
    </ul>
    <canvas id="songcanvas" width="400" height="128"></canvas>