import React, { useCallback, useEffect, useState, useRef, memo } from 'react'; import { Play, Pause, SkipBack, SkipForward, ChevronDown, Repeat, Repeat1, Square, Music } from 'lucide-react'; import { usePlayerStore } from '../store/playerStore'; import { buildCoverArtUrl, coverArtCacheKey, getArtistInfo } from '../api/subsonic'; import CachedImage, { useCachedUrl } from './CachedImage'; import { useTranslation } from 'react-i18next'; function formatTime(seconds: number): string { if (!seconds || isNaN(seconds)) return '0:00'; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m}:${s.toString().padStart(2, '0')}`; } function MarqueeTitle({ title }: { title: string }) { const containerRef = useRef(null); const textRef = useRef(null); const [scrollAmount, setScrollAmount] = useState(0); const measure = useCallback(() => { const container = containerRef.current; const text = textRef.current; if (!container || !text) return; // Temporarily make span inline-block to get its natural width text.style.display = 'inline-block'; const textWidth = text.getBoundingClientRect().width; text.style.display = ''; const overflow = textWidth - container.clientWidth; setScrollAmount(overflow > 4 ? Math.ceil(overflow) : 0); }, []); useEffect(() => { measure(); const ro = new ResizeObserver(measure); if (containerRef.current) ro.observe(containerRef.current); return () => ro.disconnect(); }, [title, measure]); return (
0 ? 'fs-title-marquee' : ''} style={scrollAmount > 0 ? { '--scroll-amount': `-${scrollAmount}px` } as React.CSSProperties : {}} > {title}
); } // ─── Crossfading blurred background ─────────────────────────────────────────── const FsBg = memo(function FsBg({ url }: { url: string }) { const [layers, setLayers] = useState>(() => url ? [{ url, id: 0, visible: true }] : [] ); const counterRef = useRef(1); useEffect(() => { if (!url) return; const id = counterRef.current++; setLayers(prev => [...prev, { url, id, visible: false }]); const t1 = setTimeout(() => { setLayers(prev => prev.map(l => ({ ...l, visible: l.id === id }))); }, 20); const t2 = setTimeout(() => { setLayers(prev => prev.filter(l => l.id === id)); }, 800); return () => { clearTimeout(t1); clearTimeout(t2); }; }, [url]); // eslint-disable-line react-hooks/exhaustive-deps return ( <> {layers.map(layer => (