Skip to content

Commit 471febf

Browse files
committed
feat: enhance SpotifyCard with playback controls and improve state management
- Added play/pause icons to the SpotifyCard component. - Improved playback state handling to reset progress when no song is playing. - Updated local storage key for currently playing data.
1 parent 544ab42 commit 471febf

File tree

2 files changed

+47
-29
lines changed

2 files changed

+47
-29
lines changed

src/components/common/spotify-card.tsx

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import clsx from 'clsx';
22
import { useState, useEffect } from 'react';
33
import { SiSpotify } from 'react-icons/si';
4+
import { HiPause, HiPlay } from 'react-icons/hi2';
45
import { useMounted } from '@lib/hooks/use-mounted';
56
import { formatMilisecondsToPlayback } from '@lib/format';
67
import { 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}

src/lib/hooks/use-currently-playing-sse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type UseCurrentlyPlayingSSE = {
1616
export function useCurrentlyPlayingSSE(): UseCurrentlyPlayingSSE {
1717
const [data, setData] =
1818
useLocalStorage<BackendSuccessApiResponse<CurrentlyPlaying> | null>(
19-
'spotify',
19+
'spotify-data',
2020
null
2121
);
2222

0 commit comments

Comments
 (0)