Skip to content

v0.3.16 — OpenSubsonic scrobble fix

Choose a tag to compare

@aroundmyroom aroundmyroom released this 01 Aug 09:05
· 32 commits to main since this release

Velvet v0.3.16 — OpenSubsonic scrobble fix

What changed

reportPlayback scrobbling now works correctly

Scrobbles sent by OpenSubsonic-compatible clients (such as Naviamp) were
silently dropped. Here is why, and what was fixed.

Background — two scrobble flows

The original Subsonic API has a scrobble endpoint: the client sends it
once when it decides the user has "finished" a track. Every client picks
its own threshold (often 50% played, or a hard-coded timer).

OpenSubsonic introduced a different, server-owned flow via the
reportPlayback extension (added to the spec March 2026). Instead of a
single "scrobble now" call, the client sends periodic state updates:

state=starting  → user pressed play
state=playing   → every ~15–30 s while playing
state=paused    → user paused
state=stopped   → user moved to the next track / stopped playback

The key design intent, as described in the OpenSubsonic spec:

"Servers should not assume a media is played after the calculated end
of content, but needs to wait for a stopped state."

The server decides when a stopped event should count as a scrobble,
keeping threshold logic in one place rather than every client re-implementing it.

What was wrong

Velvet's reportPlayback handler recognised started, playing,
paused, and completed — but completed is not a valid state in the
spec
. The real terminal state is stopped. So every time Naviamp (or
any other compliant client) sent state=stopped when skipping or
finishing a track, the server did nothing and the scrobble was lost.

What is fixed

  • state=stopped now triggers the scrobble path.
  • A listen-threshold check is applied before scrobbling on stopped:
    the user must have heard ≥ 50 % of the track or ≥ 4 minutes,
    whichever is the shorter threshold (mirrors the Last.fm / ListenBrainz
    minimum). Position is read from the positionMs parameter; if that is
    zero, elapsed time is estimated from when starting/playing was last
    received.
  • Tracks where positionMs indicates less than 30 seconds of listening
    are never scrobbled (handles quick skips).
  • state=starting is accepted for now-playing tracking (the spec-correct
    name; the old started alias still works for older clients).
  • state=completed is still accepted and scrobbles unconditionally, so
    any client already using that value keeps working.
  • ignoreScrobble=true suppresses side effects on stopped the same way
    it does on every other state.

Admin panel setting

The per-user toggles "Scrobble to Last.fm via Subsonic" and
"Scrobble to ListenBrainz via Subsonic" control whether stopped
events are forwarded. Users who have not enabled these remain unaffected.