diff --git a/.env.local b/.env.local index 7bf772e..72c8d37 100644 --- a/.env.local +++ b/.env.local @@ -1 +1 @@ -NEXT_PUBLIC_COMMIT_SHA=b668c1b +NEXT_PUBLIC_COMMIT_SHA=a00bf3e diff --git a/app/manifest.ts b/app/manifest.ts index 2d7fcd3..58b2e1d 100644 --- a/app/manifest.ts +++ b/app/manifest.ts @@ -70,5 +70,46 @@ export default function manifest(): MetadataRoute.Manifest { form_factor: 'wide' } ], + shortcuts: [ + { + name: 'Resume Song', + short_name: 'Resume', + description: 'Resume the last played song', + url: '/?action=resume', + icons: [ + { + src: '/icon-192.png', + sizes: '192x192', + type: 'image/png' + } + ] + }, + { + name: 'Play Recent Albums', + short_name: 'Recent', + description: 'Play from recently added albums', + url: '/?action=recent', + icons: [ + { + src: '/icon-192.png', + sizes: '192x192', + type: 'image/png' + } + ] + }, + { + name: 'Shuffle Favorites', + short_name: 'Shuffle', + description: 'Shuffle songs from favorite artists', + url: '/?action=shuffle-favorites', + icons: [ + { + src: '/icon-192.png', + sizes: '192x192', + type: 'image/png' + } + ] + } + ] } } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 4181622..56eae7e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,14 +8,19 @@ import { useNavidrome } from './components/NavidromeContext'; import { useEffect, useState } from 'react'; import { Album } from '@/lib/navidrome'; import { useNavidromeConfig } from './components/NavidromeConfigContext'; +import { useSearchParams } from 'next/navigation'; +import { useAudioPlayer } from './components/AudioPlayerContext'; type TimeOfDay = 'morning' | 'afternoon' | 'evening'; export default function MusicPage() { const { albums, isLoading, api, isConnected } = useNavidrome(); + const { playAlbum, playTrack, shuffle, toggleShuffle, addToQueue } = useAudioPlayer(); + const searchParams = useSearchParams(); const [recentAlbums, setRecentAlbums] = useState([]); const [newestAlbums, setNewestAlbums] = useState([]); const [favoriteAlbums, setFavoriteAlbums] = useState([]); const [favoritesLoading, setFavoritesLoading] = useState(true); + const [shortcutProcessed, setShortcutProcessed] = useState(false); useEffect(() => { if (albums.length > 0) { @@ -45,6 +50,114 @@ export default function MusicPage() { loadFavoriteAlbums(); }, [api, isConnected]); + // Handle PWA shortcuts + useEffect(() => { + const action = searchParams.get('action'); + if (!action || shortcutProcessed || !api || !isConnected) return; + + const handleShortcuts = async () => { + try { + switch (action) { + case 'resume': + // Try to resume from localStorage or play a recent track + const lastTrack = localStorage.getItem('lastPlayedTrack'); + if (lastTrack) { + const trackData = JSON.parse(lastTrack); + await playTrack(trackData); + } else if (recentAlbums.length > 0) { + // Fallback: play first track from most recent album + await playAlbum(recentAlbums[0].id); + } + break; + + case 'recent': + if (recentAlbums.length > 0) { + // Get the 10 most recent albums and shuffle them + const tenRecentAlbums = recentAlbums.slice(0, 10); + const shuffledAlbums = [...tenRecentAlbums].sort(() => Math.random() - 0.5); + + // Enable shuffle if not already on + if (!shuffle) { + toggleShuffle(); + } + + // Play first album and add remaining albums to queue + await playAlbum(shuffledAlbums[0].id); + + // Add remaining albums to queue + for (let i = 1; i < shuffledAlbums.length; i++) { + try { + const albumSongs = await api.getAlbumSongs(shuffledAlbums[i].id); + albumSongs.forEach(song => { + addToQueue({ + id: song.id, + name: song.title, + url: api.getStreamUrl(song.id), + artist: song.artist || 'Unknown Artist', + artistId: song.artistId || '', + album: song.album || 'Unknown Album', + albumId: song.parent, + duration: song.duration || 0, + coverArt: song.coverArt, + starred: !!song.starred + }); + }); + } catch (error) { + console.error('Failed to load album tracks:', error); + } + } + } + break; + + case 'shuffle-favorites': + if (favoriteAlbums.length > 0) { + // Shuffle all favorite albums + const shuffledFavorites = [...favoriteAlbums].sort(() => Math.random() - 0.5); + + // Enable shuffle if not already on + if (!shuffle) { + toggleShuffle(); + } + + // Play first album and add remaining albums to queue + await playAlbum(shuffledFavorites[0].id); + + // Add remaining albums to queue + for (let i = 1; i < shuffledFavorites.length; i++) { + try { + const albumSongs = await api.getAlbumSongs(shuffledFavorites[i].id); + albumSongs.forEach(song => { + addToQueue({ + id: song.id, + name: song.title, + url: api.getStreamUrl(song.id), + artist: song.artist || 'Unknown Artist', + artistId: song.artistId || '', + album: song.album || 'Unknown Album', + albumId: song.parent, + duration: song.duration || 0, + coverArt: song.coverArt, + starred: !!song.starred + }); + }); + } catch (error) { + console.error('Failed to load album tracks:', error); + } + } + } + break; + } + setShortcutProcessed(true); + } catch (error) { + console.error('Failed to handle PWA shortcut:', error); + } + }; + + // Delay to ensure data is loaded + const timeout = setTimeout(handleShortcuts, 1000); + return () => clearTimeout(timeout); + }, [searchParams, api, isConnected, recentAlbums, favoriteAlbums, shortcutProcessed, playAlbum, playTrack, shuffle, toggleShuffle, addToQueue]); + // Get greeting and time of day const hour = new Date().getHours(); const greeting = hour < 12 ? 'Good morning' : 'Good afternoon'; @@ -107,7 +220,7 @@ export default function MusicPage() {
{isLoading ? ( // Loading skeletons - Array.from({ length: 6 }).map((_, i) => ( + Array.from({ length: 10 }).map((_, i) => (
)) ) : ( @@ -144,7 +257,7 @@ export default function MusicPage() {
{favoritesLoading ? ( // Loading skeletons - Array.from({ length: 6 }).map((_, i) => ( + Array.from({ length: 10 }).map((_, i) => (
)) ) : (