1.2.1 • Published 6 months ago

webaudio-stream-player v1.2.1

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

webaudio-stream-player

A framework for creating an audio stream player using WebAudio.

  • Both AudioWorkletNode and ScriptProcessorNode are supported.
  • No SharedArrayBuffer is used so that cross-origin isolation is not required.

How to Use

See also: ./example.

Implement Decoder Worker

Create my-decoder-worker.ts like following. Implement your desired audio decoding procedure in process function.

import { AudioDecoderWorker } from 'webaudio-stream-player';

class MyDecoderWorker extends AudioDecoderWorker {
  constructor(worker: Worker) {
    super(worker);
  }

  override async init(args: any) {
    /* initialize if needed */
  }

  override async start(args: any) {
    /* This is called each time Audio.play(args) calls. */
  }

  override async process(): Promise<Array<Uint8Array|Int16Array|Int32Array|Float32Array> | null> {
    /**
     * Generare wave data here.
     * This method is called periodically until it returns null.
     * 
     * The amount of waveform data generated at one time is arbitrary, however, 
     * do not lock for long time here since `abort()` requests of AudioDecoderWorker 
     * will not be received while this method is processing.
     * 
     * It is recommended to return this method after an appropriate size of wave is generated. 
     */
    const channels = [];
    const leftFreq = 440.0, rightFreq = 660.0;
    for (let c = 0; c < this.numberOfChannels; c++) {
      channels.push(new Float32Array(this.sampleRate));
    }
    for (let i = 0; i < this.sampleRate; i++) {
      for (let c = 0; c < channels.length; c++) {
        const freq = c == 0 ? leftFreq : rightFreq;
        channels[c][i] = Math.sin((2 * Math.PI * freq * this.phase) / this.sampleRate);
      }
      this.phase++;
    }
    return channels;
  }

  override async dispose() {
    /* dispose resources here */
  }
}

const decoder = new MyDecoderWorker(self as Worker);

Implement Renderer Worklet Processor

Save the following code as my-renderer-worklet.ts. No modification is required. Note that webaudio-stream-player/dist/workers/audio-renderer-worklet-processor.js must be imported as the absolute path. This is a workaround to prevent Webpack from referencing variables that do not exist in the AudioWorkletGlobalScope.

import { runAudioWorklet, AudioRendererWorkletProcessor } from "webaudio-stream-player/dist/workers/audio-renderer-worklet-processor.js";

runAudioWorklet('renderer', AudioRendererWorkletProcessor);

Create Player

with Vite

import { AudioPlayer } from "webaudio-stream-player";

// `?worker` is a workaround to transpile .ts to .js
// See [this issue](https://github.com/vitejs/vite/issues/6979#issuecomment-1320394505)
import workletUrl from "./my-renderer-worklet.ts?worker&url";

export class MyPlayer extends AudioPlayer {
  constructor(rendererType: "script" | "worklet") {
    super({
      rendererType: rendererType,
      decoderWorkerFactory: () => {
        return new Worker(new URL("./my-decoder-worker.ts", import.meta.url), { type: "module" });
      },
      rendererWorkletUrl: workletUrl,
      rendererWorkletName: "renderer",
      numberOfChannels: 2,
    });
  }
}

with Webpack

WorkerUrl plugin is required. A typical Webpack configuration is ./example/webpack.config.js.

import { WorkerUrl } from "worker-url";
import { AudioPlayer } from "webaudio-stream-player";

// The `name` option of WorkerUrl is a marker to determine the webpack's chunkname (i.e. output filename).
// DO NOT USE A VARIABLE TO SPECIFY EITHER WORKER OR WORKLET NAME.
const decoderUrl = new WorkerUrl(new URL("./my-decoder-worker.js", import.meta.url), { name: "decorder" });
const workletUrl = new WorkerUrl(new URL("./my-renderer-worklet.js", import.meta.url), { name: "renderer" });

export class MyPlayer extends AudioPlayer {
  constructor(rendererType: "script" | "worklet") {
    super({
      rendererType: rendererType,
      decoderWorkerUrl: decoderUrl,
      rendererWorkletUrl: workletUrl,
      rendererWorkletName: "renderer",
      numberOfChannels: 2,
    });
  }
}

Play

const player = new MyPlayer("worklet");
const audioContext = new AudioContext();
player.connect(audioContext.destination);

document.getElementById('some-element').addEventListener('click', async () => {
  if (audioContext.state != 'running') {
    await audioContext.resume();
  }
  player.play();
});

Note

This library uses AudioWorklet that is only available in a secure context. Thus, if "worklet" renderer type is given to AudioPlayer, a page using the player must be served over HTTPS, or http://localhost.

1.2.1

6 months ago

1.1.0

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago