Mercurial
view react_games/src/API_works/music_player.tsx @ 215:c3df85159b31
removed a python library that isn't used for much.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sat, 28 Feb 2026 20:34:18 -0800 |
| parents | fb9bcd3145cb |
| children |
line wrap: on
line source
import { useCallback, useEffect, useRef, useState } from "react"; const CHAPTERS: string[] = [ "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3", "https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3", "https://download.samplelib.com/mp3/sample-3s.mp3", ]; type AudioComponentProp = { srcUrl: string; onNext?: () => void; onPrev?: () => void; }; function fmt(t: number) { if (!isFinite(t)) return "0:00"; const m = Math.floor(t / 60); const s = Math.floor(t % 60); return `${m}:${s.toString().padStart(2, "0")}`; } const AudioComponent = ({ srcUrl, onNext, onPrev }: AudioComponentProp) => { const audioRef = useRef<HTMLAudioElement>(null); const [isPlaying, setIsPlaying] = useState(false); const [current, setCurrent] = useState(0); const [duration, setDuration] = useState(0); // If true, auto-play after src changes and can play. const autoplayNextRef = useRef(false); // Wire up element events once. useEffect(() => { const el = audioRef.current; if (!el) return; const onPlay = () => setIsPlaying(true); const onPause = () => setIsPlaying(false); const onTime = () => setCurrent(el.currentTime || 0); const onMeta = () => setDuration(isFinite(el.duration) ? el.duration : 0); const onEnded = () => { if (!onNext) return; autoplayNextRef.current = true; // keep playing the next track onNext(); }; el.addEventListener("play", onPlay); el.addEventListener("pause", onPause); el.addEventListener("timeupdate", onTime); el.addEventListener("loadedmetadata", onMeta); el.addEventListener("ended", onEnded); // Prime initial state onMeta(); onTime(); setIsPlaying(!el.paused && !el.ended); return () => { el.removeEventListener("play", onPlay); el.removeEventListener("pause", onPause); el.removeEventListener("timeupdate", onTime); el.removeEventListener("loadedmetadata", onMeta); el.removeEventListener("ended", onEnded); }; }, [onNext]); // When src changes, load and maybe auto-play when ready. useEffect(() => { const el = audioRef.current; if (!el) return; el.load(); // pick up new src const tryPlay = () => { if (autoplayNextRef.current) { autoplayNextRef.current = false; el.play().catch(() => { // Autoplay might be blocked; leave paused state as-is. }); } }; // Play once ready (covers both cached and freshly loaded cases) el.addEventListener("canplay", tryPlay, { once: true }); // In case it's already buffered: tryPlay(); // Reset progress immediately on new track setCurrent(0); }, [srcUrl]); const toggle = useCallback(() => { const el = audioRef.current; if (!el) return; if (el.paused) el.play().catch(() => {}); else el.pause(); }, []); const seek = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const el = audioRef.current; if (!el) return; el.currentTime = Number(e.target.value); }, []); // Button handlers: set the autoplay flag so next track starts immediately. const handleNext = useCallback(() => { if (!onNext) return; autoplayNextRef.current = true; // user gesture → autoplay allowed onNext(); }, [onNext]); const handlePrev = useCallback(() => { if (!onPrev) return; autoplayNextRef.current = true; onPrev(); }, [onPrev]); const pct = duration > 0 ? (current / duration) * 100 : 0; return ( <> {/* remove "controls" if you want only custom UI */} <audio ref={audioRef} src={srcUrl} preload="metadata" /> <div style={{ display: "flex", gap: 8, alignItems: "center", margin: "8px 0" }}> <button onClick={toggle}>{isPlaying ? "Pause" : "Play"}</button> <button onClick={handlePrev} disabled={!onPrev}>Prev</button> <button onClick={handleNext} disabled={!onNext}>Next</button> " <span style={{ marginLeft: 8, width: 56, textAlign: "right" }}>{fmt(current)}</span>", <input type="range" min={0} max={duration || 0} step={0.01} value={current} onChange={seek} style={{ flex: 1 }} /> <span style={{ width: 56 }}>{fmt(duration)}</span> </div> {/* Optional thin progress bar */} <div style={{ height: 4, width: "100%", background: "#eee", borderRadius: 2 }}> <div style={{ height: "100%", width: `${pct}%`, background: "#999", borderRadius: 2 }} /> </div> </> ); }; const MusicPlayer = () => { const [musicIndex, setMusicIndex] = useState<number>(0); const onNext = useCallback( () => setMusicIndex((i) => Math.min(i + 1, CHAPTERS.length - 1)), [] ); const onPrev = useCallback(() => setMusicIndex((i) => Math.max(i - 1, 0)), []); return ( <AudioComponent srcUrl={CHAPTERS[musicIndex]} onNext={musicIndex === CHAPTERS.length - 1 ? undefined : onNext} onPrev={musicIndex === 0 ? undefined : onPrev} /> ); }; export { MusicPlayer, }