125 lines
3.0 KiB
TypeScript
125 lines
3.0 KiB
TypeScript
"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<AudioTrack[]>([]);
|
|
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<Howl | null>(null);
|
|
const animFrameRef = useRef<number>(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,
|
|
};
|
|
}
|