inos3910 / audio-visualizer-by-howlerjs

howler.jsを使ってオーディオビジュアライザーを作ってみる
5 stars 1 forks source link

Demo-4 Streaming Frequency data #2

Closed tutaru99 closed 3 years ago

tutaru99 commented 3 years ago

Hi again! I saw you made streaming audio visualizer work! Nice, really good job! I'm trying to recreate what you did but with no luck, it doesn't seem to work right for me and it's a weird bug I cant pinpoint why. So essentially in order to make frequency data stream and show I have to start the player 3 times for some reason. Each click (3 times) a new instance of music starts playing on top of each other. 1st click play - in console I get crossOrigin undefined. 1 2nd click - it starts getting frequencyData however its empty 2 3rd click - I start getting frequency from the radio.. but now I have 3 instances of music blasting at me :D. 3

I hope you could spot a mistake/what I'm doing wrong.

Starting to play the sound

 createVisualizerData() {
      var sound = new Howl({
        // src: require("@/assets/audio.mp3"),
            src : ['https://musicbird.leanstream.co/JCB068-MP3'],
             html5: true,
      });
      sound.play();
      this.sound = sound;
    },

Getting visualizer data

initAudioVisualizer() {
      // Create an analyser node in the Howler WebAudio context
      var analyser = Howler.ctx.createAnalyser();
      this.audio =  !this.audio ? Howler._html5AudioPool.slice(-1)[0] : this.audio;
      this.audio.crossOrigin = 'anonymous';

      this.sourceAudio = !this.sourceAudio ? Howler.ctx.createMediaElementSource(this.audio) : this.sourceAudio;
      this.sourceAudio.connect(analyser);

      this.frequency = new Uint8Array(analyser.frequencyBinCount);
      this.ffrequency = new Uint8Array(1);
      console.log(this.frequency);
      console.log(this.ffrequency);

      Howler.ctx.createGain = Howler.ctx.createGain || Howler.ctx.createGainNode;
      var gainNode = Howler.ctx.createGain();
      gainNode.gain.setValueAtTime(1, Howler.ctx.currentTime);
      Howler.masterGain.connect(analyser);
      analyser.connect(gainNode);
      gainNode.connect(Howler.ctx.destination);

      // Starting Playing Audio
      console.log("Radio Started Playing");

      // Defining variables so they can be used globally
      this.analyser = analyser;
      this.gainNode = gainNode;
    },

and drawing SVG from the data received previously

 drawAudioVisualizer() {
      // Animation ends when gain reaches 0
      if (this.gainNode.gain.value === 0) {
        if (this.drawTimer) {
          window.cancelAnimationFrame(this.drawTimer);
          return;
        }
      }
      // Set 0 to 1. Drawing becomes smoother when it is closer to 0
      this.analyser.smoothingTimeConstant = 0.05;
      // FFT size
      this.analyser.fftSize = 1024;
      // Store the waveform data in the frequency domain in an array of arguments
      this.analyser.getByteFrequencyData(this.frequency);
      this.analyser.getByteFrequencyData(this.ffrequency);

      console.log(this.ffrequency);
      console.log(this.frequency);

      // draw svg with frequency data
      const barWidth =
        (document.getElementById("js-svg").width.baseVal.value * 1.5) /
        this.analyser.frequencyBinCount;
      this.drawSvgPath(barWidth);

      // Draw every frame
      this.drawTimer = window.requestAnimationFrame(
        this.drawAudioVisualizer.bind(this)
      );
    },

repo branch/file where the full code lies - https://github.com/tutaru99/Internet-Radio-Player-Vue/blob/audio-visualizer/src/components/LocalSongs.vue

The issue: I have to click play 3 times for the visualizer to start working however in turn now I have 3 instances of music playing at me.

inos3910 commented 3 years ago

Hello again! I saw your code.

First of all, my code and your code are similar and different. That is the part below.

playAudio() {
      this.createVisualizerData();
      this.initAudioVisualizer();
      this.drawAudioVisualizer();
},

If you write it like this, every time you click it, howler.js will create an instance. In addition, if you execute the functions in this order, you will have to connect the nodes after playing the sound source, so I think these are probably the causes of the error.

The point when playing a sound source with HTML5 Audio using howler.js is an array called Howler._html5AudioPool. howler.js is designed to store the unlocked HTMLAudioElement (<audio>) in this Global object in the case of HTML5 Audio (html: true). Since the sound source is played by the HTMLAudioElement at the end of this array, howler._html5AudioPool.slice(-1)[0] gets the end element. The timing to get this element is important.

To make it similar to my sample code, load the sound source first using Vue.js's Lifecycle Hooks created() or mounted(), and when clicked, focus only on play and stop. I think it works correctly.

created() {
  this.createVisualizerData(); // Read the sound source first
},
methods: {
  createVisualizerData() {
    var sound = new Howl({
      src : ['https://musicbird.leanstream.co/JCB068-MP3'],
      html5: true,
    });
    // sound.play();     -> Delete this line
    this.sound = sound;
  },
  playAudio() {
    this.initAudioVisualizer();  // Connect the nodes before playing the sound source
    this.sound.play();
    this.drawAudioVisualizer(); // Draw after playing the sound source
  },
}

Probably, if the timing to read the sound source and other processing are in parallel, it will not work because the timing does not match the asynchronous processing internally performed in howler.js.

If you need to make fine adjustments at the start of playback or when loading a sound source, it is better to use events in howler.js.

tutaru99 commented 3 years ago

It worked! Thank you so much for taking your time and explaining this whole concept as well as even going through my code to point my mistakes and even give solutions! Thanks to you I understand this much better. Thank you so much!

inos3910 commented 3 years ago

I'm glad it worked! This issue was difficult to explain, but it was a great time for me to deepen my understanding. Thank you!