Initial commit: party-mix-app with prefetch cache, audio preload optimizations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
41
apps/web/src/hooks/usePlayer.ts
Normal file
41
apps/web/src/hooks/usePlayer.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
'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 }
|
||||
}
|
||||
Reference in New Issue
Block a user