import { useCallback, useEffect, useRef } from 'react'; interface AudioContextOptions { audioElementRef: React.MutableRefObject; } /** * Create and return an audio context instance for a given audio element [0]. * Also returns an associated audio source, a gain node, and play and pause actions * which should be used instead of `audioElementRef.current.play/pause()`. * * [0] https://developer.mozilla.org/en-US/docs/Web/API/AudioContext */ export const useAudioContext = ({ audioElementRef }: AudioContextOptions) => { const audioContextRef = useRef(); const sourceRef = useRef(); const gainNodeRef = useRef(); useEffect(() => { if (!audioElementRef.current || typeof AudioContext === 'undefined') { return; } const context = audioContextRef.current ?? new AudioContext(); const source = sourceRef.current ?? context.createMediaElementSource(audioElementRef.current); const gainNode = context.createGain(); gainNode.connect(context.destination); source.connect(gainNode); audioContextRef.current = context; gainNodeRef.current = gainNode; sourceRef.current = source; return () => { if (context.state !== 'closed') { void context.close(); } }; }, [audioElementRef]); const playAudio = useCallback(() => { if (audioContextRef.current && audioElementRef.current) { void audioElementRef.current.play(); void audioContextRef.current.resume(); } }, [audioElementRef]); const pauseAudio = useCallback(() => { if (audioContextRef.current && audioElementRef.current) { audioElementRef.current.pause(); void audioContextRef.current.suspend(); } }, [audioElementRef]); return { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio, }; };