Skip to main content

Audio

Dependencies​

This library does not require any dependencies.

Usage​

Play audio on button click.

import { AudioManagerGlobal } from "@arclockproject/common/library/Audio";

function Example() {
return (
<div>
<button
onClick={() => {
AudioManagerGlobal.play("/assets/sound.mp4");
}}
/>
</div>
);
}

Play audio on element load and keep looping until element unmounts.

import { AudioManagerGlobal } from "@arclockproject/common/library/Audio";
import { useEffect } from "react";

function Example() {
useEffect(() => {
AudioManagerGlobal.play("/assets/sound.mp4", {
loop: true,
singleInstance: true,
});
return () => {
AudioManagerGlobal.stopInstance("/assets/sound.mp4", {
gradual: true,
});
};
}, []);
return (
<div>
<button onClick={() => {}} />
</div>
);
}

There are two kinds of sounds:

  • One-shot (default): every play starts the file from the beginning, and plays can overlap. Each overlapping play uses its own <audio> element. Good for clicks and effects.
  • Single instance (singleInstance: true): one shared player per file. It resumes where it stopped and is the only kind stopInstance can stop. Good for music.

The examples below are live. Edit the code to try your own values. beepUrl(frequency) is only available on this site. It makes a short beep so the examples don't need sound files.

Sound effects​

Click quickly: one-shot sounds overlap instead of cutting each other off.

▶ Show code (click to view and edit)
Live Editor
function SoundEffects() {
  return (
    <div>
      {[262, 330, 392, 523].map((frequency) => (
        <button key={frequency} onClick={() => AudioManagerGlobal.play(beepUrl(frequency))} style={{ marginRight: 4 }}>
          {frequency} Hz
        </button>
      ))}
    </div>
  );
}
Result
Loading...

Limiting overlapping sounds​

To cap how many copies of a one-shot sound can play at once, create your own AudioManager with the cache option:

const sfx = new AudioManager({ cache: { maxInstances: 5, onMax: "drop" } });

onMax decides what happens at the limit:

  • "drop" skips the sound.
  • "throw" throws an Error.
  • A function decides. Return true to skip the sound.

AudioManagerGlobal has no limit. It only warns in the console past 10.

Pick a limit, then play the same beep 5 times at once.

▶ Show code (click to view and edit)
Live Editor
function LimitPlayground() {
  const [maxInstances, setMaxInstances] = useState(3);
  const [mode, setMode] = useState("drop");
  const [log, setLog] = useState([]);

  const playFive = () => {
    let result;
    const onMax = {
      drop: (path, count) => {
        result = `skipped, ${count} busy`;
        return true;
      },
      warn: (path, count) => {
        result = `created element ${count + 1}`;
      },
      throw: "throw",
    }[mode];
    
    const manager = new AudioManager({ cache: { maxInstances, onMax } });
    const lines = [];
    for (let i = 1; i <= 5; i++) {
      result = "played";
      try {
        manager.play(beepUrl(440));
      } catch (error) {
        result = `threw "${error.message}"`;
      }
      lines.push(`Play ${i}: ${result}`);
    }
    setLog(lines);
  };

  return (
    <div>
      <label>
        maxInstances{" "}
        <input
          type="number" min={1} max={5} value={maxInstances}
          onChange={(e) => setMaxInstances(Number(e.target.value))}
          style={{ width: 50 }}
        />
      </label>{" "}
      <label>
        onMax{" "}
        <select value={mode} onChange={(e) => setMode(e.target.value)}>
          <option value="drop">drop (function returning true)</option>
          <option value="warn">warn (function returning nothing)</option>
          <option value="throw">"throw"</option>
        </select>
      </label>{" "}
      <button onClick={playFive}>Play 5 at once</button>
      <pre>{log.join("\n") || "Press the button."}</pre>
    </div>
  );
}
Result
Loading...

Music​

A looping single-instance track with fade in and out, volume and speed.

▶ Show code (click to view and edit)
Live Editor
function MusicPlayer() {
  const song = `${REMOTE.base}/PXQZNGjZIFo.m4a`;
  const [volume, setVolume] = useState(AudioManagerGlobal.maxVolume);
  const [speed, setSpeed] = useState(1);
  const [preservesPitch, setPreservesPitch] = useState(true);

  // Stop the music when this example is removed from the page.
  useEffect(() => () => AudioManagerGlobal.stopInstance(song), []);

  const play = () =>
    AudioManagerGlobal.play(song, { singleInstance: true, loop: true, gradual: true, speed, preservesPitch });

  return (
    <div>
      <button onClick={play}>Play / resume</button>{" "}
      <button onClick={() => AudioManagerGlobal.stopInstance(song, { gradual: true })}>Fade out</button>{" "}
      <button onClick={() => AudioManagerGlobal.stopInstance(song)}>Stop now</button>
      <p>
        Volume{" "}
        <input
          type="range" min={0} max={1} step={0.05} value={volume}
          onChange={(e) => {
            setVolume(Number(e.target.value));
            AudioManagerGlobal.setMaxVolume(Number(e.target.value));
          }}
        />{" "}
        {Math.round(volume * 100)}%
      </p>
      <p>
        Speed{" "}
        <input type="range" min={0.5} max={2} step={0.25} value={speed} onChange={(e) => setSpeed(Number(e.target.value))} />{" "}
        {speed}x{" "}
        <label>
          <input type="checkbox" checked={preservesPitch} onChange={() => setPreservesPitch(!preservesPitch)} /> keep pitch
        </label>
      </p>
      <small>Speed and pitch apply the next time you press Play.</small>
    </div>
  );
}
Result
Loading...

API​

See the Audio API reference for every prop, method and type.