diff --git a/client/src/components/MemoriesView.jsx b/client/src/components/MemoriesView.jsx index 9971f1a..b5f4017 100644 --- a/client/src/components/MemoriesView.jsx +++ b/client/src/components/MemoriesView.jsx @@ -195,17 +195,31 @@ export default function MemoriesView() { const [ssIndex, setSsIndex] = useState(0); const [opacity, setOpacity] = useState(1); const [muted, setMuted] = useState(false); + const [tracks, setTracks] = useState([]); + const [trackIdx, setTrackIdx] = useState(0); + const [scanInfo, setScanInfo] = useState(null); const audioCtxRef = useRef(null); const musicRef = useRef(null); const timerRef = useRef(null); + const audioRef = useRef(null); useEffect(() => { fetchMemories() .then(d => setGroups(groupByYear(d.photos))) .catch(e => setError(e.message)); + fetch('/api/music/scan-status') + .then(r => r.json()).then(setScanInfo).catch(() => {}); }, []); + // Re-fetch track list whenever mood changes + useEffect(() => { + fetch(`/api/music?mood=${musicStyle}`) + .then(r => r.json()) + .then(d => { setTracks(d.tracks ?? []); setTrackIdx(0); }) + .catch(() => setTracks([])); + }, [musicStyle]); + useEffect(() => () => { stopMusic(); clearInterval(timerRef.current); }, []); const allPhotos = groups ? groups.flatMap(g => g.photos) : []; @@ -230,10 +244,13 @@ export default function MemoriesView() { } function toggleMute() { - if (!musicRef.current || !audioCtxRef.current) return; const next = !muted; setMuted(next); - musicRef.current.master.gain.linearRampToValueAtTime(next ? 0 : 0.42, audioCtxRef.current.currentTime + 0.5); + if (tracks.length > 0) { + if (audioRef.current) audioRef.current.volume = next ? 0 : 1; + } else if (musicRef.current && audioCtxRef.current) { + musicRef.current.master.gain.linearRampToValueAtTime(next ? 0 : 0.42, audioCtxRef.current.currentTime + 0.5); + } } // ── Slideshow ── @@ -248,7 +265,8 @@ export default function MemoriesView() { function startSlideshow(idx) { setSsIndex(idx); setOpacity(1); setSlideshow(true); - startMusic(musicStyle); + setTrackIdx(0); + if (tracks.length === 0) startMusic(musicStyle); // synth fallback } function stopSlideshow() { @@ -256,6 +274,7 @@ export default function MemoriesView() { setSlideshow(false); stopMusic(); setMuted(false); + if (audioRef.current) { audioRef.current.pause(); audioRef.current.src = ''; } } useEffect(() => { @@ -304,25 +323,46 @@ export default function MemoriesView() { {!noPhotos && (
{/* Music style picker */} -
- {MUSIC_STYLES.map(s => ( - - ))} +
+
+ {MUSIC_STYLES.map(s => ( + + ))} +
+ {scanInfo && ( +
+ + {scanInfo.scanning ? '⟳ Scanning music…' : scanInfo.enabled ? `${(scanInfo.total ?? tracks.length || scanInfo.count).toLocaleString()} tracks` : ''} + + {scanInfo.enabled && !scanInfo.scanning && ( + + )} +
+ )}
{/* Play button */} @@ -414,6 +454,19 @@ export default function MemoriesView() {
+ {/* Hidden audio element for real music */} + {tracks.length > 0 && ( +