feat: update commit SHA, enhance audio player and full screen player with favorite functionality, and update various components to support starred tracks

This commit is contained in:
2025-07-02 00:37:01 +00:00
committed by GitHub
parent d6ac2479cb
commit 707960b088
11 changed files with 150 additions and 28 deletions

View File

@@ -5,13 +5,14 @@ import { useRouter } from 'next/navigation';
import { useAudioPlayer, Track } from '@/app/components/AudioPlayerContext';
import { FullScreenPlayer } from '@/app/components/FullScreenPlayer';
import { FaPlay, FaPause, FaVolumeHigh, FaForward, FaBackward, FaCompress, FaVolumeXmark, FaExpand, FaShuffle } from "react-icons/fa6";
import { Heart } from 'lucide-react';
import { Progress } from '@/components/ui/progress';
import { useToast } from '@/hooks/use-toast';
import { useLastFmScrobbler } from '@/hooks/use-lastfm-scrobbler';
import { useStandaloneLastFm } from '@/hooks/use-standalone-lastfm';
export const AudioPlayer: React.FC = () => {
const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue, queue, toggleShuffle, shuffle } = useAudioPlayer();
const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue, queue, toggleShuffle, shuffle, toggleCurrentTrackStar } = useAudioPlayer();
const router = useRouter();
const audioRef = useRef<HTMLAudioElement>(null);
const preloadAudioRef = useRef<HTMLAudioElement>(null);
@@ -377,6 +378,19 @@ export const AudioPlayer: React.FC = () => {
</div>
<p className="text-xs text-muted-foreground truncate">{currentTrack.artist}</p>
</div>
{/* Heart icon for favoriting */}
<button
className="p-1.5 hover:bg-gray-700/50 rounded-full transition-colors mr-2"
onClick={(e) => {
e.stopPropagation();
toggleCurrentTrackStar();
}}
title={currentTrack.starred ? 'Remove from favorites' : 'Add to favorites'}
>
<Heart
className={`w-4 h-4 ${currentTrack.starred ? 'text-primary fill-primary' : 'text-gray-400'}`}
/>
</button>
<div className="flex items-center justify-center space-x-2">
<button className="p-1.5 hover:bg-gray-700/50 rounded-full transition-colors" onClick={playPreviousTrack}>
<FaBackward className="w-3 h-3" />
@@ -413,7 +427,6 @@ export const AudioPlayer: React.FC = () => {
<p className="font-semibold truncate text-sm">{currentTrack.name}</p>
<p className="text-xs text-muted-foreground truncate">{currentTrack.artist}</p>
</div>
{/* faviorte icon or smthing here */}
</div>
{/* Control buttons */}
<button
@@ -430,6 +443,18 @@ export const AudioPlayer: React.FC = () => {
<button className="p-1.5 hover:bg-gray-700/50 rounded-full transition-colors" onClick={playNextTrack}>
<FaForward className="w-3 h-3" />
</button>
<button
className="p-1.5 hover:bg-gray-700/50 rounded-full transition-colors flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
toggleCurrentTrackStar();
}}
title={currentTrack.starred ? 'Remove from favorites' : 'Add to favorites'}
>
<Heart
className={`w-4 h-4 ${currentTrack.starred ? 'text-primary fill-primary' : ''}`}
/>
</button>
</div>
<div className="flex items-center space-x-1 ml-2">
<button

View File

@@ -16,6 +16,7 @@ export interface Track {
albumId: string;
artistId: string;
autoPlay?: boolean; // Flag to control auto-play
starred?: boolean; // Flag for starred/favorited tracks
}
interface AudioPlayerContextProps {
@@ -39,6 +40,8 @@ interface AudioPlayerContextProps {
playArtist: (artistId: string) => Promise<void>;
playedTracks: Track[];
clearHistory: () => void;
toggleCurrentTrackStar: () => Promise<void>;
updateTrackStarred: (trackId: string, starred: boolean) => void;
}
const AudioPlayerContext = createContext<AudioPlayerContextProps | undefined>(undefined);
@@ -104,7 +107,8 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
}, [api]);
@@ -577,7 +581,75 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
shuffleAllAlbums,
playArtist,
playedTracks,
clearHistory
clearHistory,
toggleCurrentTrackStar: async () => {
if (!currentTrack || !api) {
toast({
variant: "destructive",
title: "Error",
description: "No track currently playing or API not configured",
});
return;
}
const newStarredStatus = !currentTrack.starred;
try {
if (newStarredStatus) {
await api.star(currentTrack.id, 'song');
} else {
await api.unstar(currentTrack.id, 'song');
}
// Update the current track state
setCurrentTrack((prev) => prev ? { ...prev, starred: newStarredStatus } : null);
} catch (error) {
console.error('Failed to update track starred status:', error);
toast({
variant: "destructive",
title: "Error",
description: "Failed to update track favorite status",
});
}
},
updateTrackStarred: async (trackId: string, starred: boolean) => {
if (!api) {
toast({
variant: "destructive",
title: "Configuration Required",
description: "Please configure Navidrome connection in settings",
});
return;
}
try {
if (starred) {
await api.star(trackId, 'song');
} else {
await api.unstar(trackId, 'song');
}
// Update the current track state if it matches the updated track
if (currentTrack?.id === trackId) {
setCurrentTrack((prev) => prev ? { ...prev, starred } : null);
}
// Also update queue if the track is in there
setQueue((prev) => prev.map(track =>
track.id === trackId ? { ...track, starred } : track
));
} catch (error) {
console.error('Failed to update track starred status:', error);
toast({
variant: "destructive",
title: "Error",
description: "Failed to update track favorite status",
});
}
}
}), [
currentTrack,
queue,
@@ -598,7 +670,9 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
shuffleAllAlbums,
playArtist,
playedTracks,
clearHistory
clearHistory,
api,
toast
]);
return (

View File

@@ -19,6 +19,7 @@ import {
FaQuoteLeft,
FaListUl
} from "react-icons/fa6";
import { Heart } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
@@ -34,7 +35,7 @@ interface FullScreenPlayerProps {
}
export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onClose, onOpenQueue }) => {
const { currentTrack, playPreviousTrack, playNextTrack, shuffle, toggleShuffle } = useAudioPlayer();
const { currentTrack, playPreviousTrack, playNextTrack, shuffle, toggleShuffle, toggleCurrentTrackStar } = useAudioPlayer();
const [progress, setProgress] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [volume, setVolume] = useState(1);
@@ -384,17 +385,17 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
<FaForward className="w-4 h-4 sm:w-5 sm:h-5" />
</button>
{lyrics.length > 0 && (
<button
onClick={() => setShowLyrics(!showLyrics)}
className={`p-2 hover:bg-gray-700/50 rounded-full transition-colors ${
showLyrics ? 'text-primary bg-primary/20' : 'text-gray-500'
}`}
title={showLyrics ? 'Hide Lyrics' : 'Show Lyrics'}
>
<FaQuoteLeft className="w-4 h-4 sm:w-5 sm:h-5" />
</button>
)}
<button
onClick={toggleCurrentTrackStar}
className="p-2 hover:bg-gray-700/50 rounded-full transition-colors"
title={currentTrack?.starred ? 'Remove from favorites' : 'Add to favorites'}
>
<Heart
className={`w-4 h-4 sm:w-5 sm:h-5 ${currentTrack?.starred ? 'text-primary fill-primary' : 'text-gray-400'}`}
/>
</button>
</div>
@@ -410,6 +411,17 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
)}
</button>
{lyrics.length > 0 && (
<button
onClick={() => setShowLyrics(!showLyrics)}
className={`p-2 hover:bg-gray-700/50 rounded-full transition-colors ${
showLyrics ? 'text-primary bg-primary/20' : 'text-gray-500'
}`}
title={showLyrics ? 'Hide Lyrics' : 'Show Lyrics'}
>
<FaQuoteLeft className="w-4 h-4 sm:w-5 sm:h-5" />
</button>
)}
{showVolumeSlider && (
<div

View File

@@ -38,7 +38,8 @@ export function PopularSongs({ songs, artistName }: PopularSongsProps) {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
};

View File

@@ -79,6 +79,7 @@ export function AlbumArtwork({
url: api.getStreamUrl(song.id),
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt) : undefined,
starred: !!song.starred
}));
playTrack(tracks[0]);

View File

@@ -60,6 +60,7 @@ const FavoritesPage = () => {
url: api?.getStreamUrl(song.id) || '',
duration: song.duration,
coverArt: song.coverArt ? api?.getCoverArtUrl(song.coverArt) : undefined,
starred: !!song.starred
});
};
@@ -79,6 +80,7 @@ const FavoritesPage = () => {
url: api.getStreamUrl(song.id),
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt) : undefined,
starred: !!song.starred
}));
playTrack(tracks[0]);

View File

@@ -116,7 +116,8 @@ export default function SongsPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
playTrack(track);
@@ -136,7 +137,8 @@ export default function SongsPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
addToQueue(track);

View File

@@ -59,7 +59,8 @@ export default function PlaylistPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
playTrack(track);
};
@@ -78,7 +79,8 @@ export default function PlaylistPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
addToQueue(track);
};
@@ -98,7 +100,8 @@ export default function PlaylistPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
}));
// Play the first track and add the rest to queue

View File

@@ -66,7 +66,8 @@ export default function SearchPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
playTrack(track);
@@ -86,7 +87,8 @@ export default function SearchPage() {
duration: song.duration,
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
albumId: song.albumId,
artistId: song.artistId
artistId: song.artistId,
starred: !!song.starred
};
addToQueue(track);

View File

@@ -443,7 +443,7 @@ const SettingsPage = () => {
</CardContent>
</Card>
<Card>
{/* <Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FaCog className="w-5 h-5" />
@@ -472,7 +472,7 @@ const SettingsPage = () => {
</p>
</div>
</CardContent>
</Card>
</Card> */}
<Card>
<CardHeader>