Skip to content

Commit 4d269f0

Browse files
committed
feat: streamline song navigation by removing redundant handlers and enhancing keyboard controls for ctrl+left ctrl+right skips
1 parent 2c07d97 commit 4d269f0

File tree

2 files changed

+47
-42
lines changed

2 files changed

+47
-42
lines changed

docker-compose.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
server:
3-
image: skeptrune/jukebox-server:v0.0.17
3+
image: skeptrune/jukebox-server:v0.0.18
44
ports:
55
- "${SERVER_PORT}:${SERVER_INTERNAL_PORT}"
66
env_file:
@@ -40,7 +40,7 @@ services:
4040
window: 60s
4141

4242
worker1:
43-
image: skeptrune/jukebox-worker:v0.0.17
43+
image: skeptrune/jukebox-worker:v0.0.18
4444
ports:
4545
- "${WORKER1_PORT}:${WORKER1_INTERNAL_PORT}"
4646
env_file:
@@ -74,7 +74,7 @@ services:
7474
retries: 3
7575

7676
worker2:
77-
image: skeptrune/jukebox-worker:v0.0.17
77+
image: skeptrune/jukebox-worker:v0.0.18
7878
ports:
7979
- "${WORKER2_PORT}:${WORKER2_INTERNAL_PORT}"
8080
env_file:
@@ -108,7 +108,7 @@ services:
108108
retries: 3
109109

110110
worker3:
111-
image: skeptrune/jukebox-worker:v0.0.17
111+
image: skeptrune/jukebox-worker:v0.0.18
112112
ports:
113113
- "${WORKER3_PORT}:${WORKER3_INTERNAL_PORT}"
114114
env_file:
@@ -142,7 +142,7 @@ services:
142142
retries: 3
143143

144144
frontend:
145-
image: skeptrune/jukebox-spa:v0.0.17
145+
image: skeptrune/jukebox-spa:v0.0.18
146146
env_file:
147147
- .env
148148
ports:

frontend/src/components/YouTubePlayer.tsx

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export const YouTubePlayer = () => {
3636
}
3737
}, [currentSongIndex, songs]);
3838

39-
// Poll for YouTube audio signed URL
4039
useEffect(() => {
4140
if (!currentSong || !currentSong.youtube_id) {
4241
setMediaUrl(null);
@@ -51,7 +50,6 @@ export const YouTubePlayer = () => {
5150
const poll = async () => {
5251
try {
5352
const result = await getYouTubeAudioSignedUrl(currentSong.youtube_id!);
54-
// If cancelled or song changed, abort
5553
if (cancelled || lastPolledIdRef.current !== currentSong.youtube_id)
5654
return;
5755
if (result && result.url) {
@@ -60,14 +58,12 @@ export const YouTubePlayer = () => {
6058
} else {
6159
setIsLoading(true);
6260
setMediaUrl(null);
63-
// Re-poll after yet
6461
pollTimeoutRef.current = setTimeout(poll, 2000);
6562
}
6663
} catch (e: unknown) {
6764
console.error("Failed to get YouTube audio signed URL:", e);
6865
setIsLoading(true);
6966
setMediaUrl(null);
70-
// Re-poll after current = setTimeout(poll, 2000);
7167
}
7268
};
7369
poll();
@@ -94,12 +90,10 @@ export const YouTubePlayer = () => {
9490
setIsPlaying(false);
9591
setIsLoading(true);
9692
} else if (!mediaUrl) {
97-
// No media URL yet, waiting for polling
9893
setIsLoading(true);
9994
}
10095
}, [mediaUrl, currentSong]);
10196

102-
// Audio event listeners effect
10397
useEffect(() => {
10498
const audio = audioRef.current;
10599
if (!audio) return;
@@ -119,7 +113,6 @@ export const YouTubePlayer = () => {
119113
setIsPlaying(true);
120114
setIsLoading(false);
121115

122-
// Update status to "playing" when song starts
123116
if (currentSong?.id) {
124117
try {
125118
await updateBoxSong(currentSong.id, {
@@ -139,7 +132,6 @@ export const YouTubePlayer = () => {
139132
const handleEnded = async () => {
140133
setIsPlaying(false);
141134

142-
// Mark current song as "played" when it ends
143135
if (currentSong?.id) {
144136
try {
145137
await updateBoxSong(currentSong.id, {
@@ -151,7 +143,6 @@ export const YouTubePlayer = () => {
151143
}
152144
}
153145

154-
// Auto-advance to next song if available
155146
if (currentSongIndex < songs.length - 1) {
156147
goToNext();
157148
setCurrentTime(0);
@@ -178,7 +169,6 @@ export const YouTubePlayer = () => {
178169
console.error("Audio playback error");
179170
};
180171

181-
// Add event listeners
182172
audio.addEventListener("timeupdate", handleTimeUpdate);
183173
audio.addEventListener("loadedmetadata", handleLoadedMetadata);
184174
audio.addEventListener("play", handlePlay);
@@ -188,7 +178,6 @@ export const YouTubePlayer = () => {
188178
audio.addEventListener("canplay", handleCanPlay);
189179
audio.addEventListener("error", handleError);
190180

191-
// Cleanup function
192181
return () => {
193182
audio.removeEventListener("timeupdate", handleTimeUpdate);
194183
audio.removeEventListener("loadedmetadata", handleLoadedMetadata);
@@ -215,40 +204,28 @@ export const YouTubePlayer = () => {
215204
setIsPlaying(true);
216205
} catch (error) {
217206
console.error("Playback failed:", error);
218-
// The error will be handled by the audio error event listener
219207
}
220208
}
221209
}, [isPlaying]);
222210

223-
const handlePrevious = async () => {
224-
goToPrevious();
225-
};
226-
227-
const handleNext = async () => {
228-
goToNext();
229-
};
230-
231211
const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {
232212
const audio = audioRef.current;
233213
if (!audio || duration === 0 || isLoading) return;
234214

235-
// Prevent seeking if audio is not ready
236-
if (audio.readyState < 2) return; // HAVE_CURRENT_DATA or higher
215+
if (audio.readyState < 2) return;
237216

238217
const rect = e.currentTarget.getBoundingClientRect();
239218
const clickX = e.clientX - rect.left;
240-
const percentage = Math.max(0, Math.min(1, clickX / rect.width)); // Clamp between 0 and 1
219+
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
241220
const newTime = percentage * duration;
242221

243-
// Ensure the new time is within valid bounds
244222
const clampedTime = Math.max(0, Math.min(duration, newTime));
245223

246-
// Clear any existing seek timeout
247224
if (seekTimeoutRef.current) {
248225
clearTimeout(seekTimeoutRef.current);
249226
}
250227

251-
setCurrentTime(clampedTime); // Update UI immediately
228+
setCurrentTime(clampedTime);
252229

253230
try {
254231
audio.currentTime = clampedTime;
@@ -283,27 +260,55 @@ export const YouTubePlayer = () => {
283260
return `${mins}:${secs.toString().padStart(2, "0")}`;
284261
};
285262

286-
// Space bar play/pause control
287263
useEffect(() => {
288264
const handleKeyDown = (e: KeyboardEvent) => {
289-
// Only trigger if space bar is pressed and not inside an input/textarea
290265
if (
291-
e.code === "Space" &&
292-
!(e.target instanceof HTMLInputElement) &&
293-
!(e.target instanceof HTMLTextAreaElement) &&
294-
!e.repeat &&
295-
currentSong &&
296-
!isLoading
266+
e.target instanceof HTMLInputElement ||
267+
e.target instanceof HTMLTextAreaElement
297268
) {
269+
return;
270+
}
271+
272+
if (e.code === "Space" && !e.repeat && currentSong && !isLoading) {
298273
e.preventDefault();
299274
handlePlayPause();
275+
return;
276+
}
277+
278+
if (
279+
(e.metaKey || e.ctrlKey) &&
280+
e.code === "ArrowLeft" &&
281+
!e.repeat &&
282+
currentSong
283+
) {
284+
e.preventDefault();
285+
goToPrevious();
286+
return;
287+
}
288+
289+
if (
290+
(e.metaKey || e.ctrlKey) &&
291+
e.code === "ArrowRight" &&
292+
!e.repeat &&
293+
currentSong
294+
) {
295+
e.preventDefault();
296+
goToNext();
297+
return;
300298
}
301299
};
302300
window.addEventListener("keydown", handleKeyDown);
303301
return () => {
304302
window.removeEventListener("keydown", handleKeyDown);
305303
};
306-
}, [currentSong, isLoading, isPlaying, handlePlayPause]);
304+
}, [
305+
currentSong,
306+
isLoading,
307+
isPlaying,
308+
handlePlayPause,
309+
goToPrevious,
310+
goToNext,
311+
]);
307312

308313
return (
309314
<motion.div
@@ -404,7 +409,7 @@ export const YouTubePlayer = () => {
404409
<Button
405410
variant="neutral"
406411
size="sm"
407-
onClick={handlePrevious}
412+
onClick={goToPrevious}
408413
disabled={currentSongIndex === 0}
409414
>
410415
<SkipBack className="h-4 w-4" />
@@ -425,7 +430,7 @@ export const YouTubePlayer = () => {
425430
<Button
426431
variant="neutral"
427432
size="sm"
428-
onClick={handleNext}
433+
onClick={goToNext}
429434
disabled={currentSongIndex === songs.length - 1}
430435
>
431436
<SkipForward className="h-4 w-4" />

0 commit comments

Comments
 (0)