From 4d44632fbfce6c098f931f82bdedfbb56f8007fa Mon Sep 17 00:00:00 2001 From: benya Date: Fri, 3 Apr 2026 02:15:57 +0300 Subject: [PATCH] feat: add recently played and scrobble flow --- SUBSONIC_SERVER_BLUEPRINT.md | 8 +- apps/web/src/components/player-bar.tsx | 51 ++++++- apps/web/src/lib/api.ts | 23 ++- apps/web/src/pages/home-page.tsx | 56 +++++++- internal/db/migrations/0003_play_history.sql | 12 ++ internal/httpapi/router.go | 81 +++++++++++ internal/library/service.go | 143 +++++++++++++------ 7 files changed, 320 insertions(+), 54 deletions(-) create mode 100644 internal/db/migrations/0003_play_history.sql diff --git a/SUBSONIC_SERVER_BLUEPRINT.md b/SUBSONIC_SERVER_BLUEPRINT.md index 70ca00f..33eb8a7 100644 --- a/SUBSONIC_SERVER_BLUEPRINT.md +++ b/SUBSONIC_SERVER_BLUEPRINT.md @@ -574,9 +574,9 @@ Responsibilities: - [x] Add delete playlist endpoint - [ ] Add reorder tracks endpoint - [x] Add add/remove track endpoints -- [ ] Add listening history table -- [ ] Record play/scrobble events -- [ ] Add recently played endpoint +- [x] Add listening history table +- [x] Record play/scrobble events +- [x] Add recently played endpoint ## Favorites @@ -608,7 +608,7 @@ Responsibilities: - [x] Implement `star` - [x] Implement `unstar` - [x] Implement playlist endpoints -- [ ] Implement `scrobble` +- [x] Implement `scrobble` - [ ] Test against at least one existing Subsonic client ## Frontend Bootstrap diff --git a/apps/web/src/components/player-bar.tsx b/apps/web/src/components/player-bar.tsx index fe95ce8..efc441a 100644 --- a/apps/web/src/components/player-bar.tsx +++ b/apps/web/src/components/player-bar.tsx @@ -10,11 +10,13 @@ import { Shuffle, Volume2, } from 'lucide-react' -import { streamUrl } from '@/lib/api' +import { scrobbleTrack, streamUrl } from '@/lib/api' import { usePlayerStore } from '@/stores/player-store' export function PlayerBar() { const audioRef = useRef(null) + const lastStartedTrackRef = useRef(null) + const lastSubmittedTrackRef = useRef(null) const currentTrack = usePlayerStore((state) => state.currentTrack) const isPlaying = usePlayerStore((state) => state.isPlaying) const volume = usePlayerStore((state) => state.volume) @@ -41,6 +43,7 @@ export function PlayerBar() { return } audioRef.current.src = streamUrl(currentTrack.id) + lastSubmittedTrackRef.current = null if (isPlaying) { void audioRef.current.play().catch(() => {}) } @@ -66,13 +69,55 @@ export function PlayerBar() { clearSeekRequest() }, [seekRequest, clearSeekRequest]) + useEffect(() => { + if (!currentTrack || !isPlaying || lastStartedTrackRef.current === currentTrack.id) { + return + } + lastStartedTrackRef.current = currentTrack.id + void scrobbleTrack({ + trackId: currentTrack.id, + submission: false, + time: Date.now(), + clientName: 'temporserv-web', + }).catch(() => {}) + }, [currentTrack, isPlaying]) + return (