diff --git a/src/assets/illos/jedmund-headphones.svg b/src/assets/illos/jedmund-headphones.svg new file mode 100644 index 0000000..83ab3dc --- /dev/null +++ b/src/assets/illos/jedmund-headphones.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/lib/components/Avatar.svelte b/src/lib/components/Avatar.svelte index 48ef29c..7bb0395 100644 --- a/src/lib/components/Avatar.svelte +++ b/src/lib/components/Avatar.svelte @@ -3,9 +3,11 @@ // We can do a thought bubble-y thing with the album art that takes you to the album section of the page import { onMount, onDestroy } from 'svelte' import { spring } from 'svelte/motion' + import { nowPlayingStream } from '$lib/stores/now-playing-stream' - let isHovering = false - let isBlinking = false + let isHovering = $state(false) + let isBlinking = $state(false) + let isPlayingMusic = $state(false) const scale = spring(1, { stiffness: 0.1, @@ -55,10 +57,17 @@ } }, 4000) + // Subscribe to now playing updates + const unsubscribe = nowPlayingStream.subscribe((state) => { + // Check if any album is currently playing + isPlayingMusic = Array.from(state.updates.values()).some((update) => update.isNowPlaying) + }) + return () => { if (blinkInterval) { clearInterval(blinkInterval) } + unsubscribe() } }) @@ -356,6 +365,75 @@ + + + {#if isPlayingMusic} +
+ + + + + + + + + + + + + + + + + +
+ {/if} diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 258ce55..e7c64f9 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -113,13 +113,6 @@ :global(svg) { height: 100%; width: 100%; - transition: transform 0.2s ease; - } - - &:hover { - :global(svg) { - transform: scale(1.05); - } } } diff --git a/src/routes/api/lastfm/stream/+server.ts b/src/routes/api/lastfm/stream/+server.ts index eeb625d..dd1dbf7 100644 --- a/src/routes/api/lastfm/stream/+server.ts +++ b/src/routes/api/lastfm/stream/+server.ts @@ -6,7 +6,7 @@ import redis from '../../redis-client' const LASTFM_API_KEY = process.env.LASTFM_API_KEY const USERNAME = 'jedmund' -const UPDATE_INTERVAL = 30000 // 30 seconds +const UPDATE_INTERVAL = 15000 // 15 seconds for more responsive updates interface NowPlayingUpdate { albumName: string @@ -31,7 +31,7 @@ export const GET: RequestHandler = async ({ request }) => { const stream = new ReadableStream({ async start(controller) { const client = new LastClient(LASTFM_API_KEY || '') - let lastNowPlayingState: Map = new Map() + let lastNowPlayingState: Map = new Map() // Send initial connection message controller.enqueue(encoder.encode('event: connected\ndata: {}\n\n')) @@ -40,15 +40,41 @@ export const GET: RequestHandler = async ({ request }) => { try { const nowPlayingAlbums = await getNowPlayingAlbums(client) const updates: NowPlayingUpdate[] = [] + const currentAlbums = new Set() // Check for changes for (const album of nowPlayingAlbums) { const key = `${album.artistName}:${album.albumName}` - const wasPlaying = lastNowPlayingState.get(key) || false + currentAlbums.add(key) - if (album.isNowPlaying !== wasPlaying) { + const lastState = lastNowPlayingState.get(key) + const wasPlaying = lastState?.isPlaying || false + const lastTrack = lastState?.track + + // Update if playing status changed OR if the track changed + if (album.isNowPlaying !== wasPlaying || + (album.isNowPlaying && album.nowPlayingTrack !== lastTrack)) { updates.push(album) - lastNowPlayingState.set(key, album.isNowPlaying) + console.log(`Update for ${album.albumName}: playing=${album.isNowPlaying}, track=${album.nowPlayingTrack}`) + } + + lastNowPlayingState.set(key, { + isPlaying: album.isNowPlaying, + track: album.nowPlayingTrack + }) + } + + // Check for albums that were in the list but aren't anymore (stopped playing) + for (const [key, state] of lastNowPlayingState.entries()) { + if (!currentAlbums.has(key) && state.isPlaying) { + const [artistName, albumName] = key.split(':') + updates.push({ + albumName, + artistName, + isNowPlaying: false + }) + console.log(`Album no longer in recent: ${albumName}`) + lastNowPlayingState.delete(key) } } @@ -173,20 +199,36 @@ function checkWithTracks( const now = new Date() const SCROBBLE_LAG = 3 * 60 * 1000 // 3 minutes + // Clean up old tracks first + recentTracks = recentTracks.filter(track => { + // Keep tracks from last hour only + const hourAgo = new Date(now.getTime() - 60 * 60 * 1000) + return track.scrobbleTime > hourAgo + }) + + // Find the most recent track from this album + let mostRecentTrack: TrackPlayInfo | null = null for (const trackInfo of recentTracks) { - if (trackInfo.albumName !== albumName) continue - + if (trackInfo.albumName === albumName) { + if (!mostRecentTrack || trackInfo.scrobbleTime > mostRecentTrack.scrobbleTime) { + mostRecentTrack = trackInfo + } + } + } + + if (mostRecentTrack) { const trackData = tracks.find(t => - t.name.toLowerCase() === trackInfo.trackName.toLowerCase() + t.name.toLowerCase() === mostRecentTrack.trackName.toLowerCase() ) if (trackData?.durationMs) { - const trackEndTime = new Date(trackInfo.scrobbleTime.getTime() + trackData.durationMs + SCROBBLE_LAG) + const trackEndTime = new Date(mostRecentTrack.scrobbleTime.getTime() + trackData.durationMs + SCROBBLE_LAG) if (now < trackEndTime) { + console.log(`Track "${mostRecentTrack.trackName}" is still playing (ends at ${trackEndTime.toLocaleTimeString()})`) return { isNowPlaying: true, - nowPlayingTrack: trackInfo.trackName + nowPlayingTrack: mostRecentTrack.trackName } } }