Monday 30 August 2021

AudioContext sound does not start immediately

Platform: Electron 13.1.7

Extra Library: Storyteller Engine https://github.com/hb432/storyteller

I created this XPlayer class (as part of storyteller) so I could play audio with the AudioContext API.

/** An audio player based on the AudioContext API. */
class XPlayer {
   /** The buffer this player should use as a source. */
   buffer: AudioBuffer;
   /**
    * Controls the speed and pitch of the audio being played. A value of `1` will result in no alterations to the
    * source audio, a value of `2` will half the speed and increase the pitch by an octive, etc.
    */
   rate: AudioParam;
   /** The audio router to use for this object. */
   router: XRouter;
   /** Controls the master volume of the audio being played. */
   volume: AudioParam;
   /**
    * This player's state. Contains the base context, base gain node, placeholder rate node, and all currently active
    * `AudioBufferSourceNode` instances that are currently active and/or awaiting closure.
    */
   state = (() => {
      const context = new AudioContext();
      return {
         context,
         gain: context.createGain(),
         rate: context.createGain().gain,
         sources: [] as AudioBufferSourceNode[]
      };
   })();
   constructor (
      {
         buffer,
         rate = 1,
         router = (context, source) => source.connect(context.destination),
         volume = 1
      }: XPlayerProperties = {}
   ) {
      this.buffer = buffer || this.state.context.createBuffer(1, 1, 8000);
      this.rate = this.state.rate;
      this.router = router;
      this.volume = this.state.gain.gain;
      this.state.rate.value = rate;
      this.state.gain.gain.value = volume;
      this.router(this.state.context, this.state.gain);
      addEventListener('beforeunload', () => {
         this.stop();
         this.state.context.close();
         // @ts-expect-error
         this.state.context = null;
      });
   }
   /** Returns the audio source most recently initialized by this player. */
   source (): AudioBufferSourceNode | void {
      return this.state.sources[this.state.sources.length - 1];
   }
   /**
    * Initializes a new audio source and starts the audio from the beginning. If `stop` is specified as true, the
    * `player.stop` method will be called before the new audio source is initialized.
    */
   start (stop?: boolean) {
      stop && this.stop();
      const source = Object.assign(this.state.context.createBufferSource(), { buffer: this.buffer });
      source.connect(this.state.gain);
      source.playbackRate.value = this.rate.value;
      this.rate = source.playbackRate;
      source.start();
      this.state.sources.push(source);
      return source;
   }
   /** Stops, de-activates, and flushes any currently active audio sources. */
   stop () {
      for (const source of this.state.sources.splice(0, this.state.sources.length)) {
         source.stop();
         source.disconnect(this.state.gain);
      }
   }
   /** Returns the current time of this player's associated audio context. */
   time () {
      return this.state.context.currentTime;
   }
}

However, I am having trouble actually starting the audio when the page is loaded. There seems to be some kind of "global delay" impacting the start time of all AudioContexts equally. I can queue up several XPlayer.start() calls, but no audio comes out until a specific moment, at which point every queued up bit of audio starts all at once. After that, audio works as intended.

Even stranger than that, the actual time it takes for the audio to start is ALWAYS the same across any reload -- precisely 30 seconds. No more, no less. It's always 30 seconds before the delayed audio plays.

I inspected the value of context.currentTime and it seems it remains stationary until the audio does its thing at 30 seconds. Something is preventing the timer from moving forward. There are no errors in console during all of this.



from AudioContext sound does not start immediately

No comments:

Post a Comment