Search code examples
javascriptaudioweb-audio-api

Illegal invocation with start/noteOn using Web Audio API


So basically I've tested this in Chrome and maybe the order of codes is off or whatever, trying to cover some of the functionality of HTML5 audio using "web audio" due to the range requests bug, for the making of games with looping sound effects and music...

I get an "illegal exception" error. Here's the code:

    var url='example.mp3';

    var a_result=new Object();
    a_result.loaded=false;
    a_result.evalstring='';
    a_result.loop=false;
    a_result.play=function(){
        var asrc=a_result.source;
        asrc.loop=a_result.loop;
        //try{
            var playfunc=asrc.start||asrc.noteOn;
            playfunc(0);
        //}catch(e){
            /* do nothing */
        //}
    }
    a_result.pause=function(){
        var asrc=a_result.source;
        try{
            var stopfunc=asrc.stop||asrc.noteOff;
            stopfunc(0);
        }catch(e){
            /* do nothing */
        }
    }

    var asrc=actx.createBufferSource();
    asrc.connect(actx.destination);

    var req=new XMLHttpRequest();
    req.open('GET',url,true);
    req.responseType='arraybuffer';

    req.onload=function(){
        if(a_result.loaded==false){
            asrc.buffer=actx.createBuffer(req.response,false);
            a_result.source=asrc;
            a_result.loaded=true;
        }
        var cont=a_result;
        eval(cont.evalstring);
    }
    req.onerror = function() {
        if(a_result.loaded==false){
            a_result.loaded=true;
        }
    }
    try{
        req.send(null);
    }catch(e){
        req.onerror();
    }

    return a_result;

And then later on after the sound has loaded I do something like:

a_result.play();

And instead of playing it gives the error.

Here is a sound test that uses the above code with the fix suggested below, and it works in Chrome, successfully working around the range requests issue on a crappy web server. Here is another that has issues (throwing some kind of "invalid string" error at asrc.buffer=actx.createBuffer(req.response,false); in Iron and silently screwing up in Chrome).

Here is the code edited according to the suggestions:

    var url='example.mp3'

    var a_result=new Object();
    a_result.loaded=false;
    a_result.evalstring='';
    a_result.loop=false;
    a_result.play=function(){
        var asrc=a_result.source;
        asrc.loop=a_result.loop;
        try{
            if(asrc.start){
                asrc.start(0);
            }else{
                asrc.noteOn(0);
            }
        }catch(e){
            /* do nothing */
        }
    }
    a_result.pause=function(){
        var asrc=a_result.source;
        try{
            if(asrc.stop){
                asrc.stop(0);
            }else{
                asrc.noteOff(0);
            }
        }catch(e){
            /* do nothing */
        }
    }

    var asrc=actx.createBufferSource();
    asrc.connect(actx.destination);

    var req=new XMLHttpRequest();
    req.open('GET',url,true);
    req.responseType='arraybuffer';

    req.onload=function(){
        actx.decodeAudioData(req.response,function(buffer){
            if(buffer){
                if(a_result.loaded==false){
                    asrc.buffer=buffer;
                    a_result.source=asrc;
                    a_result.loaded=true;
                }
                var cont=a_result;
                eval(cont.evalstring);
            }
        });
    }
    req.onerror = function() {
        if(a_result.loaded==false){
            a_result.loaded=true;
        }
    }
    try{
        req.send(null);
    }catch(e){
        req.onerror();
    }

    return a_result;

It no longer appears to have a syntax error, however, it does not seem to solve the playability issues on Chrome (second test case above updated). Specifically, after a sound is stopped it does not want to play again. -- Apparently because it's in the spec. The buffer has to be applied to a new sound source every time you play.


Solution

  • In your new page, you're calling createBuffer(data, false) on the results of an XMLHTTPRequest. You almost certainly want to be calling decodeAudioData() on those results, instead - createBuffer doesn't have a 2-parameter version, and doesn't match the parameters you're passing even if you're trying to push arbitrary data into a buffer. It appears from a browse of your code that you're pulling down MP3 or Ogg files - you need to decode them.