Skip to content

Add opt-in ListenBrainz scrobbling - #63

Closed
lostf1sh wants to merge 1 commit into
mainfrom
feature/listenbrainz-scrobbling
Closed

Add opt-in ListenBrainz scrobbling#63
lostf1sh wants to merge 1 commit into
mainfrom
feature/listenbrainz-scrobbling

Conversation

@lostf1sh

Copy link
Copy Markdown
Collaborator

What

Re-introduces public scrobbling as a strictly opt-in ListenBrainz integration, reversing the documented removal (PRIVACY.md, docs/FDROID.md, CHANGELOG.md are updated in the same change).

  • Opt-in, default off. Nothing is submitted until a user pastes their ListenBrainz token on the Accounts screen. The token is validated on connect and stored in EncryptedSharedPreferences (same pattern as Navidrome/Jellyfin credentials), and both prefs files are excluded from Auto Backup and device transfer. Disconnecting deletes any queued listens.
  • Listen threshold per the ListenBrainz spec: 4 minutes or half the track, whichever is lower, evaluated on pause-aware accumulated listening time in ListeningStatsTracker.finalizeCurrentSession() — independent of the existing stats floor and of the Subsonic track-end scrobble.
  • Offline queue: new Room table listenbrainz_pending_listens (MIGRATION_2_3, idempotent per the migration convention; schema v3 exported). Rows snapshot track metadata at enqueue and are drained oldest-first by a HiltWorker with a network constraint and exponential backoff. A batch-level 400 falls back to individual submission so one bad listen can't wedge the queue; 401 pauses flushing until reconnect (listens keep queueing); 429 honors Retry-After.
  • Per-source toggles for local files, Subsonic, and Jellyfin playback (all on once connected), so users whose server already scrobbles server-side can avoid duplicates.
  • Playing-now is submitted on track start/resume (debounced) with duration_ms, relying on ListenBrainz's server-side expiry instead of a heartbeat.
  • Groundwork for MusicBrainz tag lookup: songs.mb_recording_id / mb_release_id / mb_artist_id columns (not yet populated); scrobbles will carry recording MBIDs once tag reading lands.

Why

Lets users keep their listening history on their own terms while preserving the app's privacy posture: consent is explicit (token paste), revocation is complete (disconnect clears the queue), and the F-Droid listing requirement is reframed to strictly-opt-in rather than absent.

UI changes

The Accounts screen gains a ListenBrainz card (token dialog with masked input, connection state, queued-listen count, reconnect flow for invalidated tokens, per-source toggles) and a new "Available services" section so services can be connected while others are already linked — previously connect buttons only appeared when no account was connected. Screenshots to follow.

Verification

  • :app:compileDebugKotlin — passes
  • :app:testDebugUnitTest — passes (new ScrobbleManagerTest covers the threshold edges; ListeningStatsTrackerTest updated for the new dependency)
  • :app:lintDebug — passes

An adversarial review pass was done on the change; its findings (disconnect/enqueue race, missing backup exclusions, missing reconnect path, 401/429 handling, unmasked token field) are all fixed in this branch. Known gap: no unit tests yet for the disconnect-ordering and 401-pause behaviors — they need fakes for EncryptedSharedPreferences/WorkManager.

@lostf1sh lostf1sh closed this Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant