diff --git a/apps/web/src/components/full-player.tsx b/apps/web/src/components/full-player.tsx index c7c1715..3cb1734 100644 --- a/apps/web/src/components/full-player.tsx +++ b/apps/web/src/components/full-player.tsx @@ -1,7 +1,8 @@ import { useQuery } from '@tanstack/react-query' -import { ChevronDown, Heart, ListMusic, Pause, Play, Repeat2, Rewind, Shuffle, SkipForward, Volume2 } from 'lucide-react' +import { ChevronDown, ListMusic, Pause, Play, Repeat2, Rewind, Shuffle, SkipForward, Trash2, Volume2 } from 'lucide-react' import { useMemo, useState } from 'react' -import { coverArtUrl } from '@/lib/api' +import { FavoriteToggle } from '@/components/favorite-toggle' +import { coverArtUrl, fetchFavorites } from '@/lib/api' import { usePlayerStore } from '@/stores/player-store' type LyricsLine = { @@ -16,12 +17,24 @@ export function FullPlayer() { const currentTime = usePlayerStore((state) => state.currentTime) const duration = usePlayerStore((state) => state.duration) const volume = usePlayerStore((state) => state.volume) + const shuffle = usePlayerStore((state) => state.shuffle) + const repeatMode = usePlayerStore((state) => state.repeatMode) const togglePlayback = usePlayerStore((state) => state.togglePlayback) const playNext = usePlayerStore((state) => state.playNext) const playPrevious = usePlayerStore((state) => state.playPrevious) + const playAtIndex = usePlayerStore((state) => state.playAtIndex) + const removeFromQueue = usePlayerStore((state) => state.removeFromQueue) + const toggleShuffle = usePlayerStore((state) => state.toggleShuffle) + const cycleRepeatMode = usePlayerStore((state) => state.cycleRepeatMode) const setVolume = usePlayerStore((state) => state.setVolume) + const seekTo = usePlayerStore((state) => state.seekTo) const setFullPlayerOpen = usePlayerStore((state) => state.setFullPlayerOpen) const [tab, setTab] = useState<'queue' | 'now' | 'lyrics'>('now') + const favoritesQuery = useQuery({ + queryKey: ['favorites'], + queryFn: fetchFavorites, + enabled: !!currentTrack, + }) const lyricsQuery = useQuery({ queryKey: ['lrclib', currentTrack?.id], @@ -40,6 +53,7 @@ export function FullPlayer() { }) const parsedLyrics = useMemo(() => parseLyrics(lyricsQuery.data?.syncedLyrics ?? lyricsQuery.data?.plainLyrics ?? ''), [lyricsQuery.data]) + const favoriteTrackIds = useMemo(() => new Set((favoritesQuery.data?.tracks ?? []).map((item) => item.id)), [favoritesQuery.data]) const activeLine = useMemo(() => { if (parsedLyrics.length === 0) { return -1 @@ -113,22 +127,35 @@ export function FullPlayer() { {tab === 'queue' ? (
{queue.map((track, index) => ( -
playAtIndex(index)} + type="button" >
{index + 1}
{track.coverArtId ? {track.title} : null}
-
+
{track.title}
{track.artistName}
-
+ + ))}
) : null} @@ -137,9 +164,15 @@ export function FullPlayer() {
{formatClock(currentTime)} -
-
-
+ seekTo(Number(event.target.value))} + step={0.1} + type="range" + value={Math.min(currentTime, duration || 0)} + /> {formatClock(duration)}
@@ -154,17 +187,17 @@ export function FullPlayer() {
- } /> + } onClick={toggleShuffle} /> } onClick={playPrevious} /> } onClick={playNext} /> - } /> + } label={repeatMode === 'one' ? '1' : undefined} onClick={cycleRepeatMode} />
- } /> +
void }) { return ( - ) } diff --git a/apps/web/src/components/player-bar.tsx b/apps/web/src/components/player-bar.tsx index b203a6a..fe95ce8 100644 --- a/apps/web/src/components/player-bar.tsx +++ b/apps/web/src/components/player-bar.tsx @@ -2,7 +2,6 @@ import { useEffect, useRef } from 'react' import { Expand, Forward, - Heart, ListMusic, Pause, Play, @@ -21,12 +20,20 @@ export function PlayerBar() { const volume = usePlayerStore((state) => state.volume) const currentTime = usePlayerStore((state) => state.currentTime) const duration = usePlayerStore((state) => state.duration) + const shuffle = usePlayerStore((state) => state.shuffle) + const repeatMode = usePlayerStore((state) => state.repeatMode) + const seekRequest = usePlayerStore((state) => state.seekRequest) const togglePlayback = usePlayerStore((state) => state.togglePlayback) const playNext = usePlayerStore((state) => state.playNext) const playPrevious = usePlayerStore((state) => state.playPrevious) + const toggleShuffle = usePlayerStore((state) => state.toggleShuffle) + const cycleRepeatMode = usePlayerStore((state) => state.cycleRepeatMode) const setVolume = usePlayerStore((state) => state.setVolume) const setCurrentTime = usePlayerStore((state) => state.setCurrentTime) const setDuration = usePlayerStore((state) => state.setDuration) + const seekTo = usePlayerStore((state) => state.seekTo) + const clearSeekRequest = usePlayerStore((state) => state.clearSeekRequest) + const handleTrackEnded = usePlayerStore((state) => state.handleTrackEnded) const setFullPlayerOpen = usePlayerStore((state) => state.setFullPlayerOpen) useEffect(() => { @@ -51,10 +58,19 @@ export function PlayerBar() { } }, [isPlaying, volume]) + useEffect(() => { + if (!audioRef.current || seekRequest == null) { + return + } + audioRef.current.currentTime = seekRequest + clearSeekRequest() + }, [seekRequest, clearSeekRequest]) + return (