"use client"; import * as React from "react"; import { AnimatePresence, motion } from "framer-motion"; import { Square } from "lucide-react"; import { mmss, type Track } from "@/lib/api"; /** * 30-second Deezer preview. * * Deliberately NOT a library player. Playing your own music belongs in * Navidrome, which already does offline, CarPlay, gapless and scrobbling, and * has a dozen native clients. This exists for one job: confirm you picked the * right recording before a Soulseek search is committed to it — which is the * cheapest possible guard against importing the wrong take. */ type Ctx = { playing: number | null; toggle: (t: Track) => void; stop: () => void; }; const PlayerCtx = React.createContext({ playing: null, toggle: () => {}, stop: () => {}, }); export const usePlayer = () => React.useContext(PlayerCtx); export function PlayerProvider({ children }: { children: React.ReactNode }) { const audioRef = React.useRef(null); const [track, setTrack] = React.useState(null); const [pct, setPct] = React.useState(0); const [left, setLeft] = React.useState(30_000); if (typeof window !== "undefined" && !audioRef.current) { audioRef.current = new Audio(); audioRef.current.preload = "none"; } const stop = React.useCallback(() => { const a = audioRef.current; if (a) { a.pause(); a.removeAttribute("src"); a.load(); } setTrack(null); setPct(0); }, []); const toggle = React.useCallback( (t: Track) => { const a = audioRef.current; if (!a || !t.preview) return; if (track?.deezer_id === t.deezer_id) { stop(); return; } a.pause(); a.src = t.preview; void a.play().catch(() => undefined); setTrack(t); setPct(0); }, [track, stop], ); React.useEffect(() => { const a = audioRef.current; if (!a) return; const onTime = () => { const d = a.duration || 30; setPct((a.currentTime / d) * 100); setLeft((d - a.currentTime) * 1000); }; a.addEventListener("timeupdate", onTime); a.addEventListener("ended", stop); return () => { a.removeEventListener("timeupdate", onTime); a.removeEventListener("ended", stop); }; }, [stop]); // Stop playback when the tab is hidden — nobody wants a phantom 30s clip. React.useEffect(() => { const onHide = () => document.hidden && stop(); document.addEventListener("visibilitychange", onHide); return () => document.removeEventListener("visibilitychange", onHide); }, [stop]); const value = React.useMemo( () => ({ playing: track?.deezer_id ?? null, toggle, stop }), [track, toggle, stop], ); return ( {children} {track && ( {track.cover && ( /* eslint-disable-next-line @next/next/no-img-element */ )}
{track.title}
{track.artist}
{mmss(left)}
)}
); }