diff --git a/src/components/MusicPlayer.tsx b/src/components/MusicPlayer.tsx index 69a9be3..5e576ff 100644 --- a/src/components/MusicPlayer.tsx +++ b/src/components/MusicPlayer.tsx @@ -7,7 +7,6 @@ import type { MediaItem } from '@/lib/types' const TAIL_SKIP = 10 const CROSSFADE_DURATION = 3 const VOLUME = 0.4 -const FADE_IN_MS = 2000 export default function MusicPlayer({ tracks }: { tracks: MediaItem[] }) { const track = tracks[0] ?? null @@ -17,11 +16,9 @@ export default function MusicPlayer({ tracks }: { tracks: MediaItem[] }) { const audioB = useRef(null) const activeRef = useRef<'A' | 'B'>('A') const fadingRef = useRef(false) - const startedRef = useRef(false) - const audibleRef = useRef(false) + const playingRef = useRef(false) const [userMuted, setUserMuted] = useState(false) - const [playing, setPlaying] = useState(false) const getActive = useCallback( () => (activeRef.current === 'A' ? audioA.current : audioB.current), @@ -66,7 +63,7 @@ export default function MusicPlayer({ tracks }: { tracks: MediaItem[] }) { // Monitor for crossfade useEffect(() => { - if (!playing || !src) return + if (!playingRef.current || !src) return let id: number const tick = () => { const a = getActive() @@ -79,7 +76,7 @@ export default function MusicPlayer({ tracks }: { tracks: MediaItem[] }) { } id = requestAnimationFrame(tick) return () => cancelAnimationFrame(id) - }, [playing, src, getActive, crossfade]) + }) // Fallback loop const handleEnded = useCallback(() => { @@ -96,56 +93,41 @@ export default function MusicPlayer({ tracks }: { tracks: MediaItem[] }) { if (b && !fadingRef.current && !b.paused) b.volume = vol }, [userMuted, getActive, getInactive]) - // ── Fade volume from 0 → VOLUME ────────────────────────────── - const fadeIn = useCallback(() => { - if (audibleRef.current) return - audibleRef.current = true - const active = activeRef.current === 'A' ? audioA.current : audioB.current - if (!active) return - const t0 = performance.now() - const step = () => { - const t = Math.min((performance.now() - t0) / FADE_IN_MS, 1) - if (!fadingRef.current) active.volume = VOLUME * t - if (t < 1) requestAnimationFrame(step) - } - requestAnimationFrame(step) + // ── Ensure playback is running ──────────────────────────────── + // Called on any user interaction. Starts audio if not started yet. + const ensurePlaying = useCallback(() => { + if (playingRef.current) return + const a = audioA.current + if (!a) return + a.volume = VOLUME + a.play().then(() => { + playingRef.current = true + }).catch(() => {}) }, []) - // ── Start playback (called directly in gesture handler for mobile) ── - const startPlayback = useCallback(() => { - if (startedRef.current) return - const a = audioA.current - if (!a) return - startedRef.current = true - a.volume = 0 - a.play().then(() => { - setPlaying(true) - fadeIn() - }).catch(() => { startedRef.current = false }) - }, [fadeIn]) - - // ── Autoplay ───────────────────────────────────────────────── + // Try autoplay on mount (silent, then make audible on interaction) useEffect(() => { - if (!src || startedRef.current) return + if (!src) return const a = audioA.current if (!a) return - // Try silent autoplay (desktop allows this) - a.volume = 0 + // Try to autoplay + a.volume = VOLUME a.play().then(() => { - startedRef.current = true - setPlaying(true) - // Playing silently — fade in on first interaction - const onInteract = () => fadeIn() - const events = ['scroll', 'click', 'touchstart', 'keydown'] as const - events.forEach((e) => window.addEventListener(e, onInteract, { once: true, passive: true })) + playingRef.current = true }).catch(() => { - // Blocked (mobile) — start on first gesture - const onGesture = () => startPlayback() - const events = ['click', 'touchstart', 'touchend', 'keydown'] as const - events.forEach((e) => document.addEventListener(e, onGesture, { once: true, passive: true })) + // Blocked — will start on first interaction via ensurePlaying }) - }, [src, fadeIn, startPlayback]) + + // Safety net: on any interaction, make sure audio is playing + const handler = () => ensurePlaying() + const events = ['click', 'touchstart', 'scroll', 'keydown'] as const + events.forEach((e) => window.addEventListener(e, handler, { passive: true })) + + return () => { + events.forEach((e) => window.removeEventListener(e, handler)) + } + }, [src, ensurePlaying]) if (!track || !src) return null @@ -155,7 +137,10 @@ export default function MusicPlayer({ tracks }: { tracks: MediaItem[] }) {