11import clsx from 'clsx' ;
22import { useState , useEffect } from 'react' ;
33import { SiSpotify } from 'react-icons/si' ;
4+ import { HiPause , HiPlay } from 'react-icons/hi2' ;
45import { useMounted } from '@lib/hooks/use-mounted' ;
56import { formatMilisecondsToPlayback } from '@lib/format' ;
67import { useCurrentlyPlayingSSE } from '@lib/hooks/use-currently-playing-sse' ;
@@ -21,52 +22,62 @@ export function SpotifyCard(): React.ReactNode {
2122 item ?? { } ;
2223
2324 useEffect ( ( ) => {
24- if ( ! isPlaying || ! item ) return ;
25+ // If there's no song, reset everything to zero and stop.
26+ if ( ! item ) {
27+ setCurrentPlaybackTime ( 0 ) ;
28+ setProgressPercentage ( 0 ) ;
29+ return ;
30+ }
2531
2632 const { progressMs, durationMs } = item ;
2733
28- // Record the timestamp when this item data was received.
29- const lastDataUpdateTime = Date . now ( ) ;
34+ let progressIntervalId : NodeJS . Timeout ;
3035
31- const progressIntervalId = setInterval ( ( ) => {
32- // Calculate how much time has passed since the last data update.
33- const timeSinceLastUpdate = Date . now ( ) - lastDataUpdateTime ;
36+ // Correctly handles all cases after the initial render.
37+ if ( isPlaying ) {
38+ // STATE 1: Song is playing. Start the live-updating interval.
39+ const effectStartTime = Date . now ( ) ;
3440
35- // This is the total, un-looped progress of the item.
36- const unloopedProgressMs = progressMs + timeSinceLastUpdate ;
41+ const updateProgress = ( ) : void => {
42+ const timeElapsed = Date . now ( ) - effectStartTime ;
43+ const currentProgressMs = ( progressMs + timeElapsed ) % durationMs ;
3744
38- // Use the modulo operator to get the actual current progress, looping if necessary.
39- const currentProgressMs = unloopedProgressMs % durationMs ;
45+ setCurrentPlaybackTime ( currentProgressMs ) ;
46+ setProgressPercentage ( ( currentProgressMs / durationMs ) * 100 ) ;
47+ } ;
4048
41- setCurrentPlaybackTime ( currentProgressMs ) ;
42- setProgressPercentage ( ( currentProgressMs / durationMs ) * 100 ) ;
43- } , 1000 ) ;
49+ updateProgress ( ) ;
4450
45- // Immediately set the state based on the initial data received.
46- setCurrentPlaybackTime ( progressMs ) ;
47- setProgressPercentage ( ( progressMs / durationMs ) * 100 ) ;
51+ progressIntervalId = setInterval ( updateProgress , 1000 ) ;
52+ } else {
53+ // STATE 2: Song is paused. Set the progress once from the API data.
54+ setCurrentPlaybackTime ( progressMs ) ;
55+ setProgressPercentage ( ( progressMs / durationMs ) * 100 ) ;
56+ }
4857
4958 return ( ) : void => clearInterval ( progressIntervalId ) ;
50- } , [ isPlaying , item ] ) ;
59+ } , [ isPlaying , item ] ) ; // Re-run when the song or its playing status changes.
5160
52- const totalDuration = item ?. durationMs ?? 0 ;
53-
54- if ( ! mounted ) return null ;
61+ if ( ! mounted ) {
62+ return null ;
63+ }
5564
5665 const spotifyIcon = < SiSpotify className = 'shrink-0 text-lg text-[#1ed760]' /> ;
5766
67+ const totalDuration = item ?. durationMs ?? 0 ;
68+
5869 return (
5970 < div
6071 className = { clsx (
6172 'max-h-20 transition-[max-height] duration-300' ,
62- isPlaying && 'max-h-40'
73+ item && 'max-h-40'
6374 ) }
6475 >
6576 < UnstyledLink
6677 className = 'main-border clickable peer flex w-80 items-center gap-4 rounded-md p-4 '
6778 href = { trackUrl ?? '/' }
6879 >
69- { isPlaying ? (
80+ { item ? (
7081 < div className = 'grid w-full gap-4' >
7182 < div className = 'flex gap-4' >
7283 { albumImageUrl && (
@@ -92,12 +103,19 @@ export function SpotifyCard(): React.ReactNode {
92103 </ p >
93104 { spotifyIcon }
94105 </ div >
95- < p
96- className = 'truncate text-xs text-gray-600 dark:text-gray-300'
97- title = { artistName }
98- >
99- by < span > { artistName } </ span >
100- </ p >
106+ < div className = 'mt-1 flex justify-between gap-2 truncate' >
107+ < p
108+ className = 'truncate text-xs text-gray-600 dark:text-gray-300'
109+ title = { artistName }
110+ >
111+ by < span > { artistName } </ span >
112+ </ p >
113+ { isPlaying ? (
114+ < HiPause className = 'shrink-0 text-lg' />
115+ ) : (
116+ < HiPlay className = 'shrink-0 text-lg' />
117+ ) }
118+ </ div >
101119 < p
102120 className = 'w-10/12 truncate text-xs text-gray-600 dark:text-gray-300'
103121 title = { albumName }
0 commit comments