My goal was to make playing sounds in my React game easy and efficient. Using <audio> tags wasn't an option, because on mobile it creates huge delays between the action and the sound. I'm just starting with the Web Audio API, so please forgive me if I did something silly. It works though.
I decided to make it a React hook called useAudio():
import { useMemo, useCallback } from "react";
const useAudio = (paths: Map<unknown, string>) => {
const audioContext = useMemo(
() => new AudioContext(),
[]
);
const arrayBuffers = useMemo(
() => new Map([ ...paths ].map(
([ key, path ]) => [
key,
fetch(path).then(response => response.arrayBuffer())
]
)),
[paths]
);
const decodedTracks = useMemo(
() => new Map([ ...arrayBuffers ].map(
([ key, arrayBufferPromise ]) => [
key,
arrayBufferPromise.then(
arrayBuffer => audioContext.decodeAudioData(arrayBuffer)
)
]
)),
[arrayBuffers, audioContext]
);
const play = useCallback(
async (key: unknown) => {
if (!decodedTracks.has(key)) {
throw new Error("Invalid track key");
}
const decodedTrack = await decodedTracks.get(key)!;
const bufferSource = audioContext.createBufferSource();
bufferSource.buffer = decodedTrack;
bufferSource.connect(audioContext.destination);
bufferSource.start();
},
[audioContext, decodedTracks]
);
return [ play ] as const;
};
export default useAudio;
And here's how you'd use it in your app:
const audioFiles = new Map([
["cat", "./audio/Meow.mp3"],
[true, "https://pikachu.test/pikapika.wav"], // You can use any key you wish!
[777, "./lucky.aac"],
]);
const Harmony = () => {
const [ playAudio ] = useAudio(audioFiles);
return (
<nav>
<button
type="button"
onClick={() => playAudio("cat")}
>
Meow!
</button>
<button
type="button"
onClick={() => playAudio(true)}
>
Pika pika
</button>
<button
type="button"
onClick={() => playAudio(123)}
>
Gonna throw an error at ya!
</button>
</nav>
);
};
I wanted to allow using any format of a key, hence using Map of unknown keys.
The main reason I'm asking for your reviews is to understand if I'm using Web Audio API correctly and if I could further optimize it, or perhaps I over-optimized something at the cost of clarity? I tried to memoize and reuse as much as I knew was possible.