"Listening time this week" summed the full duration of every track whose
stream ticket was issued, so pressing play and skipping counted the whole
song. Playback is now measured on the client and reported to the API.
Backend:
- ListeningHistory gains PlayedSeconds, LastProgressAt and IsCounted
(migration TrackPlayedSeconds marks existing rows as counted).
- /tracks/{id}/stream returns a listenId; new
PUT /tracks/listens/{listenId}/progress stores the accumulated seconds,
capped by track duration and elapsed wall-clock time, monotonic and
owner-only.
- A listen counts once it reaches min(30s, 50% of the track). Weekly
stats, streak, recent history/albums, mixes and listensCount only use
counted listens; seconds sum PlayedSeconds.
- ListeningHistoryCleanupJob (daily) deletes uncounted listens older
than 8 days.
web-player:
- ListenTracker accumulates played time (seeks are ignored) and flushes
on pause, track change, ended, every 30s, and on pagehide.
- Proxy route /api/listens/[id]/progress; repeat starts a new listen per
loop.
The counted-listen tests lived in one catch-all UncountedListensTests
file. Each now sits next to the handler it covers, and the cleanup
handler gets its own test file.
Discover used a hardcoded list of genre tiles that searched track titles,
and the upload form kept its own copy of the genre list and compatibility
rules. Both now come from the backend.
Backend:
- GET /genres returns genres with at least one track and their counts.
- GET /genres/available returns every assignable genre with the genres
it cannot be combined with, so the rules live only in
GenreCompatibility.
- GET /tracks accepts ?genre= (case-insensitive, 400 on unknown values).
web-player:
- Discover tiles are built from /genres; each opens /explore?genre=X, a
song list filtered by that tag. Unknown genres fall back to a label
derived from their name.
- The upload form loads /genres/available, disables incompatible genres
as you pick, and the server validates before reserving upload URLs.
- lib/data/trackGenres.ts is removed; its helpers merge into
lib/data/genres.ts.
- Regenerate the typed API client.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
"Listening time this week" summed the full duration of every track whose stream ticket was issued, so pressing play and skipping counted the whole song. Playback is now measured on the client and reported to the API. Backend: - ListeningHistory gains PlayedSeconds, LastProgressAt and IsCounted (migration TrackPlayedSeconds marks existing rows as counted). - /tracks/{id}/stream returns a listenId; new PUT /tracks/listens/{listenId}/progress stores the accumulated seconds, capped by track duration and elapsed wall-clock time, monotonic and owner-only. - A listen counts once it reaches min(30s, 50% of the track). Weekly stats, streak, recent history/albums, mixes and listensCount only use counted listens; seconds sum PlayedSeconds. - ListeningHistoryCleanupJob (daily) deletes uncounted listens older than 8 days. web-player: - ListenTracker accumulates played time (seeks are ignored) and flushes on pause, track change, ended, every 30s, and on pagehide. - Proxy route /api/listens/[id]/progress; repeat starts a new listen per loop.