Problem with SoundWave class

The 2nd line randomly ends me up into the exception error. Trying to quickly switch between two songs often create this issue

  this.song = this._audInstance.play();   
   this.soundWave = new zim.SoundWave(50, this.song);  <<<<<<<<<<<<<<<<<

Error:
AudioPlayer-class.js:176 Failed to create soundWave: InvalidStateError: Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode.
at t.SoundWave.setInput (ZIM_min.js:7:1064237)
at new t.SoundWave (ZIM_min.js:7:1064315)
at AudioPlayer._playBtn_pressup (AudioPlayer-class.js:173:26)
at lib.playBtn. (AudioPlayer-class.js:32:46)
at createjs.js:29:2430
at e._dispatchEvent (createjs.js:29:3914)
at e.dispatchEvent (createjs.js:29:3273)
at e._dispatchMouseEvent (createjs.js:29:52893)
at e._handlePointerUp (createjs.js:29:50684)
at e._handleMouseUp (createjs.js:29:50328)

Just out for the evening... so... you already have a SoundWave object? And you are adding another?

Do you have this at the start:
createjs.Sound.registerPlugins([createjs.HTMLAudioPlugin]);

Here is the code

index.js


var canvas, frame, zimStage, anim_container, dom_overlay_container, fnStartAnimation;
var imagePath_Str;
var asset_arr;
var jsVars = null;
var totalLib_Int = 0;
var libLoaded_Int = 0;
var libId_arr = ["74D00A8DE4EC4F76AEA9E5C999BBCAED"];
var alib; var blib; var uilib;
var libName_Arr = ["uilib"];

function displayMessage(evt) 
{
    console.log("I got " + evt.data + " from " + evt.origin);

    if (evt.data.split) 
    {
        var arr = evt.data.split(",");
        window.loadFileToAudioPlayer2(arr[0], arr[1]);
    }
}

if (window.addEventListener) 
{
    window.addEventListener("message", displayMessage, false);
} 
else 
{
    window.attachEvent("onmessage", displayMessage);
}

function init() 
{
    totalLib_Int = libId_arr.length;
    loadLib(libId_arr[libLoaded_Int]);
}

function loadLib(compId, s) 
{
    var comp = AdobeAn.getComposition(compId);
    var ldr = new createjs.LoadQueue(false);
    var templib = comp.getLibrary();
    window[libName_Arr[libLoaded_Int]] = templib;
    ldr.addEventListener("fileload", function(evt) { handleFileLoad(evt, comp) });
    ldr.addEventListener("complete", function(evt) { handleComplete(evt, comp, templib) });
    ldr.loadManifest(templib.properties.manifest);
}

function handleFileLoad(evt, comp) 
{
    var images = comp.getImages();
    if (evt && (evt.item.type == "image")) {
        images[evt.item.id] = evt.result;
    }
}

function handleComplete(evt, comp, lib) 
{
    var ss = comp.getSpriteSheet();
    var queue = evt.target;
    var ssMetadata = lib.ssMetadata;
    for (i = 0; i < ssMetadata.length; i++) {
        ss[ssMetadata[i].name] = new createjs.SpriteSheet({
            "images": [queue.getResult(ssMetadata[i].name)],
            "frames": ssMetadata[i].frames
        });
    }

    libLoaded_Int += 1;
    if (libLoaded_Int < totalLib_Int) 
    {
        loadLib(libId_arr[libLoaded_Int], "x");
    }

    if (libLoaded_Int == totalLib_Int) 
    {
         
        misc.makeResponsive(true, 'both', true, 1,  window[libName_Arr[0]]);

        var zon = true;
        var zns = false;
        var ap = "./audio/";
        var ip = "./assets/images/";
        var fp = "./assets/fonts/";

        var assets = 
        [

            { font: "CourierNewPS-BoldMT", src: fp + "CourierNewPS-BoldMT.ttf" },
            ap + "ballad_of_legend_city.mp3",
            ap + "goodbye_my_lc_baby.mp3",
        ];

        var frame = new Frame(FIT, 370, 90, "#ffffff", "#ffffff", ready, "image.png", "assets/", new Waiter());


        createjs.Sound.registerPlugins([createjs.HTMLAudioPlugin]);


        function ready() 
        {

            zimStage = frame.stage;

            var audioPlayer = new AudioPlayer(frame, uilib, ap);

            zimStage.addChild(audioPlayer.getView());

            window.loadFileToAudioPlayer2 = function(fileName_str, songTitle_str) {
                audioPlayer.loadFile(fileName_str, songTitle_str);
            };

             audioPlayer.loadFile("ballad_of_legend_city", "test test test test");
            zimStage.update();

        }
	}
	}

AudioPlayer-class.js

class AudioPlayer {
  constructor(frame, uilib, ap) {
    this.frame = frame;
    this.zimstage = frame.stage;
    this.ap = ap;
    this.playing_Bool = false;
    this.song = null;
    this.currentAudioName_Str = "";
    this.currentAudioTitle_Str = "";
    this.defaultStatus = "Click any audio below to play the music.";
    this.bars_Zcnt = new zim.Container();
    this.soundWave = null;
    this._tickerListener = null;

    var view_Mc = new uilib.view_Mc();
    this.view_Mc = view_Mc;
    this.screen_Mc = view_Mc.screen_Mc;
    this.playBtn_Mc = view_Mc.playBtn_Mc;
    this.playBtn_Mc.name = "playBtn_Mc";
    this.replayBtn_Mc = view_Mc.replayBtn_Mc;
    this.replayBtn_Mc.name = "replayBtn_Mc";
    this.stopBtn_Mc = view_Mc.stopBtn_Mc;
    this.stopBtn_Mc.name = "stopBtn_Mc";

    this.title_Tf = view_Mc.title_Tf;
    this.status_Tf = view_Mc.status_Tf;
    this.time_Tf = view_Mc.time_Tf;

    this.status_Tf.text = this.defaultStatus;

    this._bindEvents();
  }

  _bindEvents() {
    /*
    this.playBtn_Mc.on('mousedown', () => { 
      zog("play button mousedown");
      this._playBtn_mousedown()
    }
    );
    this.playBtn_Mc.on('pressup', () => { 
      zog("play button pressup");
      this._playBtn_pressup()
    }
  );
    this.replayBtn_Mc.on('mousedown', () => this._replayBtn_mousedown());
    this.replayBtn_Mc.on('pressup', () => this._replayBtn_pressup());
    this.stopBtn_Mc.on('mousedown', () => this._stopBtn_mousedown());
    this.stopBtn_Mc.on('pressup', () => this._stopBtn_pressup());
    */
            this.zimstage.on("stagemouseup", ()=>
            {
                if (!this.validate()) {
                return;
           }
              const t = this.zimstage.getObjectUnderPoint(this.zimstage.mouseX, this.zimstage.mouseY, 1);
              //zog("stage mouse up" + t.parent);
              if(t.parent && t.parent.name == "playBtn_Mc") {
                this._playBtn_pressup();
              }
              if(t.parent && t.parent.name == "replayBtn_Mc") {
                this._replayBtn_pressup();
              }
              if(t.parent && t.parent.name == "stopBtn_Mc") {
                this._stopBtn_pressup();
              }
            }
          );
            this.zimstage.on("stagemousedown", ()=>
            { 
              if (!this.validate()) {
                return;
           }
              const t = this.zimstage.getObjectUnderPoint(this.zimstage.mouseX, this.zimstage.mouseY, 1);
              //zog("stage mouse down" + t.parent);
              if(t.parent && t.parent.name == "playBtn_Mc") {
                this._playBtn_mousedown();
              }
              if(t.parent && t.parent.name == "replayBtn_Mc") {
                this._replayBtn_mousedown();
              }
              if(t.parent && t.parent.name == "stopBtn_Mc") {
                this._stopBtn_mousedown();
              }
            }
          );
  }


  loadFile(pfileName_str, psongTitle_str) 
  {
    this.fileName_Str = pfileName_str;
    this.songTitle_Str = psongTitle_str;

    this.cleanup();

    if (!this.validate()) {
      return;
    }

    this.currentAudioTitle_Str = this.songTitle_Str || this.fileName_Str;
    this.playBtn_Mc.gotoAndStop(0);
    this.currentAudioName_Str = this.fileName_Str + '.mp3';
    
    this._audInstance = new Aud(this.ap + this.currentAudioName_Str);
    this._audReady = false;
     
    this._audInstance.on("ready", () => {
      this._audReady = true;
      this.playBtnMouseDown_Bool = true;    
      this._playBtn_pressup();
    });
    // If already ready (preloaded), trigger manually
    if (this._audInstance.ready) {
      this._audReady = true;
      this.playBtnMouseDown_Bool = true;    
      _playBtn_pressup();
    }
  }

   validate() {
        // Validation: check if fileName_str is valid
        if (!this.fileName_Str || typeof this.fileName_Str !== 'string' || this.fileName_Str.trim() === '') {
          this.status_Tf.text = "Invalid audio file.";
          return false;
        }



        return true;
      }

       cleanup() {
        if(this.bars_Zcnt) {
          if(this.view_Mc.contains(this.bars_Zcnt)) {
            this.view_Mc.removeChild(this.bars_Zcnt);
          }
          while (this.bars_Zcnt.numChildren > 0) {
            this.bars_Zcnt.removeChildAt(0);
          }
        }
        // Unload previous audio, soundWave, and ticker if exists
        if (this.song) {
          try {
            if (typeof this.song.stop === 'function') this.song.stop();
            this.song = null;
          } catch (e) {
            console.error('Error while unloading previous song:', e);
          }
        }
        this.dispose_sound_wave();
        if (this._tickerListener) {
          zim.Ticker.remove(this._tickerListener);
          this._tickerListener = null;
        }
      }
      dispose_sound_wave() {
               if (this.soundWave && typeof this.soundWave.dispose === 'function') {
          try { this.soundWave.dispose(); } catch (e) { console.error('Error disposing soundWave:', e); }
          this.soundWave = null;
        }

      }

  _playBtn_mousedown() 
  {
    

    if (!this._audReady) {
      
      this.status_Tf.text = "Loading audio wait...";
      this.zimstage.update();
      return;
    }


    this.playBtnMouseDown_Bool = true;    
    this.playBtn_Mc.gotoAndStop(this.playBtn_Mc.currentFrame + 1);
    this.zimstage.update();
  }

  _playBtn_pressup() {

    if(this.playBtnMouseDown_Bool == false) return;

    this.playBtnMouseDown_Bool = false;
    
    if(this.song==null)
    {
      this.song = this._audInstance.play();
            this.playBtn_Mc.gotoAndStop(2);
      this.status_Tf.text = "Playing";
      this.title_Tf.text = this.currentAudioTitle_Str;

    }
    else
    //if (this.playBtn_Mc.currentFrame == 3 || this.playBtn_Mc.currentFrame == 2) 
    if(this.song.paused === false)
      {
      this.playBtn_Mc.gotoAndStop(0);
      this.status_Tf.text = "Paused";
      if (this.song) {
          this.song.paused = true;
      }
      this.zimstage.update();

    } else 
      if(this.song.paused === true)
      //if (this.playBtn_Mc.currentFrame == 0 || this.playBtn_Mc.currentFrame == 1) 
      {
      
      this.playBtn_Mc.gotoAndStop(2);
      this.status_Tf.text = "Playing";
      this.title_Tf.text = this.currentAudioTitle_Str;
      this.zimstage.update();
          this.song.paused  = false;
      }

      try {
        //this.dispose_sound_wave();
        this.soundWave = new zim.SoundWave(50, this.song);
        this.soundWave.on("ready", () => this._soundWaveReady());
      } catch (e) {
        console.error('Failed to create soundWave:', e);
      }
    
  }

  _replayBtn_mousedown() 
  {
       
    this.replayBtn_Mc.gotoAndStop(this.replayBtn_Mc.currentFrame + 1);
    this.zimstage.update();
  }

  _replayBtn_pressup() 
  {
    this.replayBtn_Mc.gotoAndStop(0);
    this.playBtn_Mc.gotoAndStop(0);

    this.status_Tf.text = "Playing";
    this.song.position = 0;
    this.song.paused = true;
    this.playBtnMouseDown_Bool = true;        
    this._playBtn_pressup();

  }

  _stopBtn_mousedown() {
     
  
    this.stopBtn_Mc.gotoAndStop(this.stopBtn_Mc.currentFrame + 1);
    this.zimstage.update();
  }

  _stopBtn_pressup() {
    this.stopBtn_Mc.gotoAndStop(0);
    this.playBtn_Mc.gotoAndStop(0);

    this.status_Tf.text = "Stopped";
    this.song.position = 0;
    this.song.paused = true;
    
    if (this._tickerListener) {
      zim.Ticker.remove(this._tickerListener);
      this._tickerListener = null;
    }
      this.zimstage.update();
  }

  _soundWaveReady() {
    

    this.bars_Zcnt.alpha = 0.25;
    var width = this.screen_Mc.nominalBounds.width * 0.9;
    var gap = 1;

    zim.loop(this.soundWave.num, (i, total) => {
      var bar = new zim.Rectangle(width / total - gap, 300, this.frame.dark);
      bar.addTo(this.bars_Zcnt).mov(i * width / total).reg(0, 300);
    });

    this.bars_Zcnt.x = this.screen_Mc.x;
    this.bars_Zcnt.y = this.screen_Mc.y;
    this.view_Mc.addChild(this.bars_Zcnt);

    // Remove previous ticker if exists
    if (this._tickerListener) {
      zim.Ticker.remove(this._tickerListener);
    }
    this._tickerListener = this._updateTicker.bind(this);
    zim.Ticker.add(this._tickerListener);
    this.zimstage.update();
  }

  
    _updateTicker() 
    {

      if (this.song) {
        this.time_Tf.text = this._secToMinSecFormat(
          (this.song.position / this.song.duration).toFixed(2) * 100
        );
      }
      if(!this.soundWave) return; 
      var data = this.soundWave.calculate();
      zim.loop(this.bars_Zcnt, (bar, i) => {
        bar.heightOnly = data[i]*40;// / 10;
     
      });
    }

  _secToMinSecFormat(pTimeElapsedInSec_int) {
    var minute_int = Math.floor(pTimeElapsedInSec_int / 60);
    var second_int = Math.floor(pTimeElapsedInSec_int % 60);
    var prefix0_str = minute_int < 10 ? "0" : "";
    var prefix1_str = second_int < 10 ? "0" : "";
    return prefix0_str + minute_int + ":" + prefix1_str + second_int;
  }

  getView() {
    return this.view_Mc;
  }
}

The issue not only stops the Soundwave display. But even halts the whole program.
I consulted AI about the issue. And adding the following logic helped sorting out the Soundwave issue.


  _ensureSoundWave() {
    var cacheKey = this.currentAudioName_Str;

    if (!cacheKey || !this.song) {
      return;
    }

    if (this.soundWaveCache[cacheKey]) {
      this.soundWave = this.soundWaveCache[cacheKey];
      this._currentSoundWaveKey = cacheKey;
      this._soundWaveReady();
      return;
    }

    try {
      var soundWave = new zim.SoundWave(50, this.song);
      this.soundWaveCache[cacheKey] = soundWave;
      this.soundWave = soundWave;
      this._currentSoundWaveKey = cacheKey;

      soundWave.on("ready", () => {
        if (this.currentAudioName_Str !== cacheKey) {
          return;
        }
        this.soundWave = soundWave;
        this._currentSoundWaveKey = cacheKey;
        this._soundWaveReady();
      });
    } catch (e) {
      console.error('Failed to create soundWave:', e);
    }
  }

Okay - that is a lot of code. Just quickly, are you switching between audio sources and doing a SoundWave for each one?

Yes, Soundwave instance will be different for each song. That's how it gets instantiated.
The exception is generated by Soundwave class many times in console. But only at a particular time it halts the audio play too.
Eg.
Starting with song1 was working good
Switching to song2 too was working good
But switching back to song1 was creating issue with creating Soundwave instance.

The caching logic above sorted it out finally.

1 Like

Okay - so did it only break when going back to a song that it already played?

Yes it occurred when going back to the song already played.
Removing the Soundwave implementation entirely from the player class did not cause any error.

1 Like

I can't really tell... is the AI fix just remembering if you have already loaded that sound and if so, what is it doing to prevent the error?

I think Soundwave instance is wrongly returning the old instance already attached with old song, even when I pass instance of new song. Because it did solve the issue after applying the solution.

Have to race to class - but what does the AI fix do if it finds the song is already used? Does it just use an old instance? I don't see it adjusting anything. When or where do you call the AI code? Just tell me in words if you can - rather than show all the code. If we have to, we can recreate the issue - it seems familiar... maybe we have solved this before. But anyway, perhaps with you short answer, we can just go in and adjust the Soundwave class without recreating a test example.

Should I send you the project files so that you can run and see. I think it's just about removing the ensure_soundwave method, and you will recreate the issue.

That's okay for now - was just wondering if you knew what the fix is doing. I looked at it a couple times and could not tell. I might be able to test it now with my own sounds... your code is pretty involved.

Ah... it is okay switching sounds and even playing the same sound over again - the problem is if the sound finishes and then later you try and play the sound again. Okay, checking it out.

Not sure if you are using the same effect but with just different sounds. If you are, there is a setInput() method to change which sound is operating the SoundWave so you do not even have to remake it. We will still see if we can solve the starting a finished sound over again.

It is weird. SoundWave dispose()

disconnects the analyser (node) and if the sound is playing this works and a new SoundWave can be made after disposing. But if the song finishes then it does not seem to be able to disconnect the node and making a new SoundWave it says it already is connected. I tried disconnecting the source from everything. I checked to make sure the lastSource was still there even after the sound ends - and it is.

Even when we set a source, we check if there is one and remove it.

image

Thinking about it... maybe when the sound finishes, we can try removing the source then.

Nope. If we disconnect once the audio tag has played - which is how everyone says to do it... it still causes the error.

Maybe the audio tag has to be removed from the DOM and all references to it removed.

We do not just want to do the AI solution you have as I think that is just keeping track of whether we have done a soundwave on that sound. That would mean a lookup table as a static property on the SoundWave and internally not try and add an analyser if it was already used. It might come to that - but still thinking.

There is also closing the audioContext which is supposed to release all resources that it used. I tried context true for dispose (default is false) and nope - still error. I tried waiting 1 second after disposing before creating a new soundwave - nope - still error. To me, it seems like a bug in the JS WebAudio.

If I disconnect as the sound ends, the dispose gives an error saying it is not connected. If I do not disconnect as the sound ends, the dispose works and then the next time we run it - it says it is connected. If I disconnect as the sound ends and do not try and disconnect in the dispose it still gives the error.

So... will sleep on it.