"use client"; import { useState, useRef, useCallback, useEffect } from "react"; import { Howl } from "howler"; interface AudioTrack { id: number; title: string; url: string; duration: number; } export function useAudioPlayer() { const [tracks, setTracks] = useState([]); const [currentTrackIndex, setCurrentTrackIndex] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [progress, setProgress] = useState(0); const [duration, setDuration] = useState(0); const [playbackRate, setPlaybackRate] = useState(1); const [continuousMode, setContinuousMode] = useState(false); const howlRef = useRef(null); const animFrameRef = useRef(0); const currentTrack = tracks[currentTrackIndex] ?? null; const destroyHowl = useCallback(() => { if (howlRef.current) { howlRef.current.unload(); howlRef.current = null; } cancelAnimationFrame(animFrameRef.current); }, []); const loadTrack = useCallback( (index: number) => { if (!tracks[index]) return; destroyHowl(); const howl = new Howl({ src: [tracks[index].url], html5: true, rate: playbackRate, onplay: () => { setIsPlaying(true); const updateProgress = () => { if (howl.playing()) { setProgress(howl.seek() as number); animFrameRef.current = requestAnimationFrame(updateProgress); } }; animFrameRef.current = requestAnimationFrame(updateProgress); }, onpause: () => setIsPlaying(false), onstop: () => setIsPlaying(false), onend: () => { setIsPlaying(false); if (continuousMode && index < tracks.length - 1) { setCurrentTrackIndex(index + 1); } }, onload: () => { setDuration(howl.duration()); }, }); howlRef.current = howl; setCurrentTrackIndex(index); setProgress(0); }, [tracks, playbackRate, continuousMode, destroyHowl] ); const play = useCallback(() => howlRef.current?.play(), []); const pause = useCallback(() => howlRef.current?.pause(), []); const toggle = useCallback(() => { if (isPlaying) pause(); else play(); }, [isPlaying, play, pause]); const seek = useCallback((seconds: number) => { howlRef.current?.seek(seconds); setProgress(seconds); }, []); const changeRate = useCallback( (rate: number) => { setPlaybackRate(rate); howlRef.current?.rate(rate); }, [] ); const goToTrack = useCallback( (index: number) => { loadTrack(index); setTimeout(() => howlRef.current?.play(), 100); }, [loadTrack] ); useEffect(() => { return () => destroyHowl(); }, [destroyHowl]); return { tracks, setTracks, currentTrack, currentTrackIndex, isPlaying, progress, duration, playbackRate, continuousMode, setContinuousMode, loadTrack, play, pause, toggle, seek, changeRate, goToTrack, }; }