feat: enhance audio player and favorites functionality with improved type safety, update image handling in components
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
import { useAudioPlayer, Track } from '@/app/components/AudioPlayerContext';
|
||||||
import { FullScreenPlayer } from '@/app/components/FullScreenPlayer';
|
import { FullScreenPlayer } from '@/app/components/FullScreenPlayer';
|
||||||
import { FaPlay, FaPause, FaVolumeHigh, FaForward, FaBackward, FaCompress, FaVolumeXmark, FaExpand, FaShuffle } from "react-icons/fa6";
|
import { FaPlay, FaPause, FaVolumeHigh, FaForward, FaBackward, FaCompress, FaVolumeXmark, FaExpand, FaShuffle } from "react-icons/fa6";
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
@@ -44,30 +44,30 @@ export const AudioPlayer: React.FC = () => {
|
|||||||
} = useStandaloneLastFm();
|
} = useStandaloneLastFm();
|
||||||
|
|
||||||
// Combined Last.fm handlers
|
// Combined Last.fm handlers
|
||||||
const onTrackStart = (track: any) => {
|
const onTrackStart = useCallback((track: Track) => {
|
||||||
navidromeOnTrackStart(track);
|
navidromeOnTrackStart(track);
|
||||||
standaloneOnTrackStart(track);
|
standaloneOnTrackStart(track);
|
||||||
};
|
}, [navidromeOnTrackStart, standaloneOnTrackStart]);
|
||||||
|
|
||||||
const onTrackPlay = (track: any) => {
|
const onTrackPlay = useCallback((track: Track) => {
|
||||||
navidromeOnTrackPlay(track);
|
navidromeOnTrackPlay(track);
|
||||||
standaloneOnTrackPlay(track);
|
standaloneOnTrackPlay(track);
|
||||||
};
|
}, [navidromeOnTrackPlay, standaloneOnTrackPlay]);
|
||||||
|
|
||||||
const onTrackPause = (currentTime: number) => {
|
const onTrackPause = useCallback((currentTime: number) => {
|
||||||
navidromeOnTrackPause(currentTime);
|
navidromeOnTrackPause(currentTime);
|
||||||
standaloneOnTrackPause(currentTime);
|
standaloneOnTrackPause(currentTime);
|
||||||
};
|
}, [navidromeOnTrackPause, standaloneOnTrackPause]);
|
||||||
|
|
||||||
const onTrackProgress = (track: any, currentTime: number, duration: number) => {
|
const onTrackProgress = useCallback((track: Track, currentTime: number, duration: number) => {
|
||||||
navidromeOnTrackProgress(track, currentTime, duration);
|
navidromeOnTrackProgress(track, currentTime, duration);
|
||||||
standaloneOnTrackProgress(track, currentTime, duration);
|
standaloneOnTrackProgress(track, currentTime, duration);
|
||||||
};
|
}, [navidromeOnTrackProgress, standaloneOnTrackProgress]);
|
||||||
|
|
||||||
const onTrackEnd = (track: any, currentTime: number, duration: number) => {
|
const onTrackEnd = useCallback((track: Track, currentTime: number, duration: number) => {
|
||||||
navidromeOnTrackEnd(track, currentTime, duration);
|
navidromeOnTrackEnd(track, currentTime, duration);
|
||||||
standaloneOnTrackEnd(track, currentTime, duration);
|
standaloneOnTrackEnd(track, currentTime, duration);
|
||||||
};
|
}, [navidromeOnTrackEnd, standaloneOnTrackEnd]);
|
||||||
|
|
||||||
const handleOpenQueue = () => {
|
const handleOpenQueue = () => {
|
||||||
setIsFullScreen(false);
|
setIsFullScreen(false);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
import Image from 'next/image';
|
||||||
import { Song } from '@/lib/navidrome';
|
import { Song } from '@/lib/navidrome';
|
||||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -92,9 +93,11 @@ export function PopularSongs({ songs, artistName }: PopularSongsProps) {
|
|||||||
{/* Album Art */}
|
{/* Album Art */}
|
||||||
<div className="relative w-12 h-12 bg-muted rounded-md overflow-hidden flex-shrink-0">
|
<div className="relative w-12 h-12 bg-muted rounded-md overflow-hidden flex-shrink-0">
|
||||||
{song.coverArt && api && (
|
{song.coverArt && api && (
|
||||||
<img
|
<Image
|
||||||
src={api.getCoverArtUrl(song.coverArt, 96)}
|
src={api.getCoverArtUrl(song.coverArt, 96)}
|
||||||
alt={song.album}
|
alt={song.album}
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
import { lastFmAPI } from '@/lib/lastfm-api';
|
import { lastFmAPI } from '@/lib/lastfm-api';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
|
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
|
||||||
@@ -71,9 +72,11 @@ export function SimilarArtists({ artistName }: SimilarArtistsProps) {
|
|||||||
>
|
>
|
||||||
<div className="w-32 space-y-2 group cursor-pointer">
|
<div className="w-32 space-y-2 group cursor-pointer">
|
||||||
<div className="relative w-32 h-32 bg-muted rounded-full overflow-hidden">
|
<div className="relative w-32 h-32 bg-muted rounded-full overflow-hidden">
|
||||||
<img
|
<Image
|
||||||
src={getArtistImage(artist)}
|
src={getArtistImage(artist)}
|
||||||
alt={artist.name}
|
alt={artist.name}
|
||||||
|
width={128}
|
||||||
|
height={128}
|
||||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
|
|
||||||
import { useNavidrome } from "./NavidromeContext"
|
import { useNavidrome } from "./NavidromeContext"
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useAudioPlayer } from "@/app/components/AudioPlayerContext";
|
import { useAudioPlayer, Track } from "@/app/components/AudioPlayerContext";
|
||||||
import { getNavidromeAPI } from "@/lib/navidrome";
|
import { getNavidromeAPI } from "@/lib/navidrome";
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -82,7 +82,7 @@ export function AlbumArtwork({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
playTrack(tracks[0]);
|
playTrack(tracks[0]);
|
||||||
tracks.slice(1).forEach((track: any) => addToQueue(track));
|
tracks.slice(1).forEach((track: Track) => addToQueue(track));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to play album:', error);
|
console.error('Failed to play album:', error);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
@@ -68,11 +68,7 @@ export function LoginForm({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Check if Navidrome is already working on component mount
|
// Check if Navidrome is already working on component mount
|
||||||
useEffect(() => {
|
const checkNavidromeConnection = useCallback(async () => {
|
||||||
checkNavidromeConnection();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkNavidromeConnection = async () => {
|
|
||||||
try {
|
try {
|
||||||
// First check if there's a working API instance
|
// First check if there's a working API instance
|
||||||
const { getNavidromeAPI } = await import('@/lib/navidrome');
|
const { getNavidromeAPI } = await import('@/lib/navidrome');
|
||||||
@@ -122,7 +118,11 @@ export function LoginForm({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Navidrome connection check failed, will show config step');
|
console.log('Navidrome connection check failed, will show config step');
|
||||||
}
|
}
|
||||||
};
|
}, [config, setStep, setFormData, setCanSkipNavidrome, testConnection]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkNavidromeConnection();
|
||||||
|
}, [checkNavidromeConnection]);
|
||||||
|
|
||||||
const handleInputChange = (field: string, value: string) => {
|
const handleInputChange = (field: string, value: string) => {
|
||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { AlbumArtwork } from "@/app/components/album-artwork";
|
|||||||
import { ArtistIcon } from "@/app/components/artist-icon";
|
import { ArtistIcon } from "@/app/components/artist-icon";
|
||||||
import { Album, Artist, Song } from "@/lib/navidrome";
|
import { Album, Artist, Song } from "@/lib/navidrome";
|
||||||
import { Heart, Music, Disc, Mic, Play } from "lucide-react";
|
import { Heart, Music, Disc, Mic, Play } from "lucide-react";
|
||||||
import { useAudioPlayer } from "@/app/components/AudioPlayerContext";
|
import { useAudioPlayer, Track } from "@/app/components/AudioPlayerContext";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const FavoritesPage = () => {
|
const FavoritesPage = () => {
|
||||||
@@ -82,7 +82,7 @@ const FavoritesPage = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
playTrack(tracks[0]);
|
playTrack(tracks[0]);
|
||||||
tracks.slice(1).forEach((track: any) => addToQueue(track));
|
tracks.slice(1).forEach((track: Track) => addToQueue(track));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to play album:', error);
|
console.error('Failed to play album:', error);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class LastFmAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async makeRequest(method: string, params: Record<string, string>): Promise<any> {
|
private async makeRequest(method: string, params: Record<string, string>): Promise<Record<string, unknown>> {
|
||||||
const credentials = this.getCredentials();
|
const credentials = this.getCredentials();
|
||||||
if (!credentials?.apiKey) {
|
if (!credentials?.apiKey) {
|
||||||
throw new Error('No Last.fm API key available');
|
throw new Error('No Last.fm API key available');
|
||||||
@@ -108,7 +108,7 @@ export class LastFmAPI {
|
|||||||
autocorrect: '1'
|
autocorrect: '1'
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.artist || null;
|
return (data.artist as LastFmArtistInfo) || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch artist info from Last.fm:', error);
|
console.error('Failed to fetch artist info from Last.fm:', error);
|
||||||
return null;
|
return null;
|
||||||
@@ -123,7 +123,7 @@ export class LastFmAPI {
|
|||||||
autocorrect: '1'
|
autocorrect: '1'
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.toptracks || null;
|
return (data.toptracks as LastFmTopTracks) || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch artist top tracks from Last.fm:', error);
|
console.error('Failed to fetch artist top tracks from Last.fm:', error);
|
||||||
return null;
|
return null;
|
||||||
@@ -138,7 +138,7 @@ export class LastFmAPI {
|
|||||||
autocorrect: '1'
|
autocorrect: '1'
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.similarartists || null;
|
return (data.similarartists as LastFmArtistInfo['similar']) || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch similar artists from Last.fm:', error);
|
console.error('Failed to fetch similar artists from Last.fm:', error);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user