42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
'use client'
|
|
|
|
import { useRef, useCallback } from 'react'
|
|
|
|
export function useAudioEngine() {
|
|
const audioRef = useRef<HTMLAudioElement | null>(null)
|
|
const audioCtxRef = useRef<AudioContext | null>(null)
|
|
const analyserRef = useRef<AnalyserNode | null>(null)
|
|
const sourceRef = useRef<MediaElementAudioSourceNode | null>(null)
|
|
|
|
const initAudioViz = useCallback(() => {
|
|
const audio = audioRef.current
|
|
if (!audio || typeof window === 'undefined') return
|
|
try {
|
|
if (!audioCtxRef.current) {
|
|
const AudioCtx = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext
|
|
const ctx = new AudioCtx()
|
|
audioCtxRef.current = ctx
|
|
const analyser = ctx.createAnalyser()
|
|
analyser.fftSize = 64
|
|
analyser.smoothingTimeConstant = 0.8
|
|
analyserRef.current = analyser
|
|
}
|
|
// One MediaElementAudioSourceNode per audio element — never recreate
|
|
if (!sourceRef.current) {
|
|
const source = audioCtxRef.current.createMediaElementSource(audio)
|
|
sourceRef.current = source
|
|
source.connect(analyserRef.current!)
|
|
analyserRef.current!.connect(audioCtxRef.current.destination)
|
|
}
|
|
} catch {}
|
|
}, [])
|
|
|
|
const resumeContext = useCallback(() => {
|
|
if (audioCtxRef.current?.state === 'suspended') {
|
|
audioCtxRef.current.resume()
|
|
}
|
|
}, [])
|
|
|
|
return { audioRef, analyserRef, audioCtxRef, initAudioViz, resumeContext }
|
|
}
|