Skip to content
Konstantinos Kyriakopoulos edited this page May 27, 2026 · 16 revisions

Foo Scrobbler for Mac

Introduction

Foo Scrobbler for Mac (foo_scrobbler_mac) is a native Last.fm scrobbling component for foobar2000 on macOS.

It submits playback metadata from foobar2000 to your Last.fm account, including artist, track title, album, and duration. Submitted data becomes part of your Last.fm listening history, where it is used to generate long-term statistics, charts, and listening trends accessible through the Last.fm web interface.

The component is implemented as a proper foobar2000 component, not a wrapper or external helper process. Its design follows foobar2000’s event model, lifetime rules, and threading constraints, with explicit attention to macOS and Apple Silicon behaviour.

Foo Scrobbler provides reliable submission of:

  • Now Playing updates for the currently playing track
  • Scrobbles that meet Last.fm eligibility rules, without background services, polling loops, or uncontrolled thread creation

Design priorities are deliberately conservative, with a strong emphasis on lightweight execution and predictable resource usage:

  • Deterministic behaviour over implicit automation
    All actions are triggered by explicit playback or user events. There is no background polling, speculative work, or hidden automation.

  • Explicit state transitions over heuristics
    Internal state changes are driven by well-defined playback boundaries, authentication events, and user actions.

  • Failure recovery over silent retry loops
    Errors are handled explicitly, with bounded retry logic and backoff to prevent runaway network activity or thread churn.

  • Minimal interaction with the playback pipeline
    Playback is observed without interfering with decoding, DSP, or output stages. Metadata handling and network submission are fully decoupled from the audio path.

All network activity is bounded, state-driven, and recoverable. Internal state is persistent, ensuring that pending scrobbles survive crashes, restarts, or forced termination without duplication or loss.

When disabled or suspended, the component performs no Now Playing updates or scrobble submissions, while preserving internal state and cached data.

This project targets users who prefer a predictable, inspectable scrobbling implementation that behaves as a stable subsystem of foobar2000 rather than an autonomous background agent.


Defaults and Limits

Preferences Defaults

Preferences are exposed under:

Preferences → Tools → Foo Scrobbler

Tabs appear in this order:

  1. Console
  2. Scrobbling
  3. Tags
  4. Exclusions

Default values:

  • Console log level: Basic
  • Artist (Title Formatting): [%Artist%]
  • Album Artist (Title Formatting): [%Album Artist%]
  • Title (Title Formatting): [%Title%]
  • Album (Title Formatting): [%Album%]
  • Treat "Various Artists" as empty (Album Artist only): Off
  • Disable Now Playing updates: Off
  • Only scrobble from media library: Off
  • Exclude artists (text or regex; ';' separated): Empty
  • Exclude titles (text or regex; ';' separated): Empty
  • Exclude albums (text or regex; ';' separated): Empty
  • Exclude by Title Formatting (reject if output is non-empty): Empty
  • TF template defaults: Genre is Podcast; Media kind is Audiobook; Path contains .mpc; /other/; Comment contains Interview
  • Dynamic sources: NP & Scrobbling

Cache and Drain Constants

  • Cache draining is enabled by default
  • Drain cooldown: 360 seconds (disabled when cache size is less than 50)
  • Scrobble batch size: up to 50 per Last.fm track.scrobble request
  • Linear retry backoff with a maximum delay of 1 hour
  • Default daily submission budget: 2600 scrobbles

Eligibility Constants

Scrobble eligibility is governed by fixed, non-configurable rule constants:

  • Minimum track duration: 30 seconds
    Tracks shorter than this are ignored entirely.

  • Eligibility threshold: 50% of track duration
    A track must be played for at least half of its total duration.

  • Maximum eligibility cap: 240 seconds (4 minutes)
    For long tracks, eligibility is capped to avoid excessive wait times.

  • Long track classification: 480 seconds (8 minutes)
    Tracks longer than this are treated uniformly using the capped threshold.

These values are applied consistently to all tracks and are not affected by user preferences or runtime conditions.


Main Functionality

Foo Scrobbler provides Last.fm scrobbling for foobar2000 on macOS using a strictly event-driven and state-aware design. All behaviour is derived from playback events, explicit user actions, and well-defined runtime states. There is no background polling and no autonomous activity.

Menu Commands

Foo Scrobbler integrates into the foobar2000 menu under:

Playback → Last.fm

Menu entries are state-aware and appear only when applicable.

  • Authenticate
    Initiates or completes the Last.fm authentication process. The command is hidden once authentication is valid.

  • Suspend Scrobbling
    Immediately disables all Now Playing updates and scrobble submission without clearing cached data.

  • Resume Scrobbling
    Appears only when scrobbling is suspended. Restores normal operation and allows cached scrobbles to be submitted according to internal rules.

Menu commands are non-blocking and never perform synchronous network operations.

Playback Integration

The component listens to playback state changes such as track start, track change, pause, resume, and stop. These events are used only to update internal playback state and eligibility tracking.

Playback callbacks never perform network operations. All communication with Last.fm is deferred and handled asynchronously to ensure that audio playback remains unaffected.

Scrobble Eligibility and Submission

A track is considered for scrobbling only if all of the following conditions are met:

  • Mandatory metadata (artist and title) is present and stable
  • Track duration is known and at least 30 seconds
  • Playback progresses past the computed eligibility threshold
  • Scrobbling is enabled and the component is in an active runtime state
  • The track is not disabled by the optional FOO_SCROBBLER tag

For local tracks, FOO_SCROBBLER can be used as a per-track override. Values such as false, 0, no, or off suppress both scrobbling and Now Playing updates for that track; missing or unrecognized values are treated as allowed. While disabled this way, listening time does not count toward scrobble eligibility. The eligibility threshold is computed as:

threshold = min(trackDuration × 0.5, 240 seconds)

Playback time is measured using real elapsed playback time. If playback ends before the threshold is reached, the track is discarded and never queued.

If the threshold is reached while submission is temporarily blocked, eligibility is remembered and resolved at the next natural playback boundary.

Scrobble submission is persistent and stateful. If a scrobble cannot be submitted immediately due to temporary conditions, it is cached locally and submitted later when conditions allow. Successfully submitted scrobbles are never duplicated.

When multiple cached scrobbles are ready, Foo Scrobbler submits them using Last.fm API 2.0 batch scrobbling where possible, while keeping the same retry, cooldown, and failure handling rules.

For dynamic sources like radio stations, the eligibility threshold is 30 seconds of effective listening time. These scrobbles are never persistently queued, but are temporarily cached locally to be submitted during the first available slot (stream song change, stop, or start). Dynamic sources are a special-case eligibility path and do not use the standard threshold rule.

Now Playing Behaviour

Now Playing updates are handled independently from scrobble submission.

They are best-effort and non-persistent, and are sent only when:

  • Authentication is valid
  • Scrobbling is not suspended
  • Now Playing updates are enabled in preferences
  • Playback metadata is stable

Now Playing updates are:

  • Never cached
  • Never retried
  • Immediately suppressed on authentication failure or suspension

Disabling Now Playing updates does not affect scrobble eligibility or submission.

Media Library Filtering

When Submit only tracks from Media Library is enabled, scrobbling is restricted strictly to tracks that belong to the foobar2000 Media Library.

Tracks outside the Media Library:

  • Do not generate Now Playing updates
  • Are not tracked for scrobble eligibility
  • Are never cached or submitted

This filtering is applied early and prevents further processing for excluded tracks.

Tag Formatting and Metadata Handling

Foo Scrobbler treats track tags strictly as input data.

It does not rewrite file tags, normalize library metadata, or persist any metadata changes back to the media library. Metadata is evaluated only for scrobbling decisions and submission payload construction.

The following fields are required:

  • Artist
  • Title

If either is missing or invalid after evaluation and validation, the track is ignored and not submitted.

Core metadata is defined through foobar2000 Title Formatting expressions in the Foo Scrobbler preferences pane:

Preferences → Tools → Foo Scrobbler → Tags

Available fields:

  • Artist (Title Formatting)
  • Album Artist (Title Formatting)
  • Title (Title Formatting)
  • Album (Title Formatting)

Default expressions:

[%Artist%]
[%Album Artist%]
[%Title%]
[%Album%]

This allows users to:

  • read standard tags directly
  • reference alternative tags
  • build fallbacks
  • use conditional logic
  • trim or transform values using foobar2000 Title Formatting syntax

Examples:

[%Conductor%]
[$if2(%Album Artist%,%Artist%)]
[$if3(%Original Artist%,%Artist%,%Album Artist%)]
[$replace(%Title%,_, )]
[$replace($trim(%Title%),LIVE,Live)]
$if($trim($replace(%Title%,' (Edit)',)),$trim($replace(%Title%,' (Edit)',)),%Title%)

These examples show common Title Formatting uses: optional fields, fallback to alternative tags, multi-step fallback chains, simple text cleanup, basic value normalization before validation, and selective suffix removal. After evaluation, values are trimmed and validated. Empty values, placeholder text such as unknown, and whitespace-only results are rejected.

An optional rule allows treating "Various Artists" as empty for Album Artist handling. This is particularly useful for compilation releases and also affects Artist fallback behaviour when the Artist expression resolves through Album Artist.

For local tracks, metadata can be refreshed while playback is active when needed. Before final scrobble submission, metadata is re-evaluated from the current track state so the queued submission reflects the latest valid Title Formatting result available at submit time.

Foo Scrobbler submits validated metadata exactly as evaluated by foobar2000 Title Formatting.

Foo Scrobbler supports optional submission of MusicBrainz Track ID with scrobbles. If the track metadata contains the tag musicbrainz_trackid (case variants such as MUSICBRAINZ_TRACKID are also accepted), its value is included in the Last.fm submission payload. It is not mandatory, does not influence scrobble eligibility, and is silently omitted when absent or empty. As with the other evaluated submission fields, the value is taken from the current track metadata at submit time and is never written back to file tags or the media library.

Text, Regex, and Title Formatting Exclusion Filters

Foo Scrobbler can exclude tracks from Now Playing and scrobbling using filters. In preferences:

Preferences → Tools → Foo Scrobbler → Exclusions

  • Exclude artists (text or regex; ';' separated)
  • Exclude titles (text or regex; ';' separated)
  • Exclude albums (text or regex; ';' separated)
  • Exclude by Title Formatting (reject if output is non-empty)

If any artist, title, album, or Title Formatting exclusion matches, the track is skipped. Max patterns total per text field (Artist, Title, or Album): 32. That’s the combined count of plain substrings + regex entries in that one field. Anything beyond 32 ;-separated entries is ignored. Also each entry is capped at 256 characters.

The syntax below applies to the artist, title, and album text filters. The Title Formatting exclusion uses a normal foobar2000 Title Formatting expression and rejects the track when the evaluated output is not empty. The TF Templates checkboxes append generated expressions to that same field; clearing one removes only its generated expression. Template text accepts semicolon-separated values; genre and media kind are exact case-insensitive matches, while path and comment are case-insensitive contains matches.

Syntax

  • Entries are semicolon-separated.
  • Plain text entries match as substrings.
  • Entries that contain regex metacharacters are treated as regular expressions. Matching uses a regex “search” (it can match anywhere unless you use ^...$).
  • Regex entries use std::regex with the ECMAScript grammar and are matched with std::regex_search.
  • Case-insensitive filter application.

Artist filter examples

Exclude artists containing either word:

karaoke; tribute

Exclude artists that contain both words (any order):

(Smith.*Wes)|(Wes.*Smith)

Exclude artists that include common collaboration markers:

\b(feat\.?|ft\.?|featuring|with)\b

Exclude exact artist names (list-style exact match):

^(Miles Davis|John Coltrane|Bill Evans|Herbie Hancock)$

Exclude artists that look like “Various Artists” variants:

^(Various Artists|V\.A\.|VA|Various)$

Title filter examples

Exclude titles containing any of these:

live; demo

Exclude titles containing “remaster” / “remastered” / “mix” / “edit”:

\b(remaster(ed)?|mix|edit|version)\b

Exclude titles that end with bracketed tags like “(Live)” or “[Radio Edit]”:

(\([^)]*\)|\[[^\]]*\])\s*$

Exclude titles that contain a year tag in parentheses, like “(2011 Remastered)”:

\(\s*(19|20)\d{2}[^)]*\)

Exclude titles that start with “Intro” or “Interlude”:

^(intro|interlude)\b

Title Formatting exclusion examples

The Title Formatting exclusion rejects the track when the expression returns any non-empty output.

Exclude albums whose name ends with a parenthesized suffix, such as Album Name (Remastered):

$if($and($strcmp($right(%Album%,1),')'),$strrchr(%Album%,'(')),1,)

Exclude tracks with “demo” or “rehearsal” in the title:

$if($or($strstr($lower(%Title%),demo),$strstr($lower(%Title%),rehearsal)),1,)

Exclude podcasts and audiobooks by genre:

$if($or($stricmp(%Genre%,podcast),$stricmp(%Genre%,audiobook)),1,)

Need help writing a regex pattern?

If you don’t know regex, ask any chatbot to generate a pattern.

Copy/paste one prompt similar to these:

For Artist:

Write only the raw ECMAScript regex pattern (no leading/trailing /, no flags).
Match an artist string if it contains the whole word feat, ft, featuring, or with.
Paste only the pattern text (no /.../ delimiters or flags). Output ONLY the pattern.

For Title:

Write only the raw ECMAScript regex pattern (no surrounding /.../ and no flags).
The pattern must match track titles that end with a tag enclosed in either parentheses (...) or square brackets [...], e.g., (Live) or [Radio Edit].
Output only the pattern text, preserve backslashes exactly, and wrap the output in a code block.

Invalid regex entries are ignored and reported in the console log. Regex generators can be helpful, but generated patterns should be reviewed before use.


Lightweight, Efficient and Non-Intrusive Behaviour

Foo Scrobbler is designed to remain effectively invisible during normal operation. It does not alter playback behavior, timing, or metadata flow beyond what is strictly required for scrobbling.

There are no background services, helper applications, or persistent UI elements. The component exists entirely within foobar2000’s process and follows its lifecycle exactly.

Playback Safety

All playback-related callbacks are treated as read-only observation points.

  • No network requests are performed from playback callbacks
  • No blocking operations are executed on playback-related threads
  • No additional latency is introduced into the audio pipeline

All time-consuming or failure-prone operations are deferred and handled asynchronously.

Threading Discipline

The component uses a bounded and predictable threading model.

  • No detached or fire-and-forget threads
  • No unbounded thread creation
  • No busy-waiting or polling loops

Background work is scheduled only in response to explicit events. When idle, the component consumes no CPU time.

Network Restraint

Network activity is strictly demand-driven.

  • No periodic polling
  • No keep-alive traffic
  • No speculative retries

Requests are issued only when a submission is eligible and permitted. On failure, retries are deferred and rate-limited according to internal backoff rules.

Shutdown and Failure Behaviour

On application shutdown or component unload:

  • Pending work is safely terminated
  • No blocking shutdown hooks are executed
  • No background threads outlive the component

Unexpected termination does not corrupt cached data or cause duplicate submissions on restart.


Abstract - Modular Design

Foo Scrobbler is built from a small number of clearly separated parts, each responsible for one job only. This keeps behavior predictable and makes problems easier to isolate and recover from when something goes wrong.

Foo Scrobbler Architecture

Rather than acting as a single monolithic system, the component behaves as a set of cooperating pieces with clear boundaries between playback tracking, user interaction, network communication, and data storage.

Clear Separation of Responsibilities

Different aspects of scrobbling are handled independently:

  • Playback is observed and tracked without affecting audio output
  • User commands and preferences control behavior explicitly
  • Communication with Last.fm is isolated from playback logic
  • Pending scrobbles are stored safely until submission succeeds
  • Background work and retries are handled in a controlled way

Because each part has a single responsibility, changes or failures in one area do not cascade into others.

One-Way, Predictable Data Flow

Foo Scrobbler Data Flow

Information moves through the component in a single, well-defined direction:

  1. Playback events update internal tracking state
  2. Tracks that become eligible are recorded as submission entries
  3. These entries are submitted to Last.fm
  4. Local storage is updated when pending scrobbles are cached and again after successful submission to remove or reconcile stored entries

Network or storage state never feeds back into playback behavior.

Failures Stay Contained

Errors are handled where they occur and do not leak into unrelated parts of the system:

  • Network issues do not interrupt playback tracking
  • Authentication problems do not damage cached data
  • Storage issues do not block playback or menu interaction

Each part can pause, fail, or recover independently without forcing a full reset or requiring user intervention.

Predictable and Inspectable Behavior

All internal state changes are explicit and event-driven.

State transitions occur only in response to:

  • Playback boundaries
  • User commands
  • Explicit network responses

There are no hidden behavioural modes, and no timer-driven playback logic. Time-based scheduling is used only for bounded retry and cache-drain control.


Network Error Handling

Network interaction is treated as an unreliable external dependency.
Failures are expected, classified, and handled explicitly without affecting playback, UI responsiveness, or internal state consistency.

Foo Scrobbler Network Errors

Network errors never alter playback tracking, timing, or eligibility logic.

Authentication Failures

Authentication errors are treated as a hard stop.

When credentials are invalid, expired, or revoked:

  • All scrobble submission halts immediately
  • Cached scrobbles are preserved without modification
  • Now Playing updates are suppressed entirely
  • No retry attempts are made using invalid credentials

The component does not attempt to refresh or repair credentials automatically.
Recovery occurs only after the user successfully completes re-authentication via the menu.

Transport and Network Failures

Transient network failures such as connection errors, timeouts, or unreachable hosts are handled conservatively:

  • The current submission attempt is aborted
  • The affected scrobble remains cached
  • No further submissions are attempted in the same cycle

Playback tracking continues uninterrupted while network activity is paused.

If Last.fm returns error 29 (rate limit exceeded), Foo Scrobbler enters a queue cooldown state. During this period, cached scrobbles are retained but submission is temporarily throttled to reduce repeated failures and avoid aggressive retry behaviour. Once the cooldown window expires, submission may resume on the next eligible drain opportunity.

This behaviour affects only outbound submission. It does not interrupt playback observation, eligibility tracking, or local cache preservation.

Unclassified API responses (bounded retry, then discard)

  • Some Last.fm responses contain valid JSON and an API error code that is not mapped to a specific handling category (anything other than the known handled cases like invalid session and selected temporary conditions); these are treated as “unclassified” results (internally treated as OTHER_ERROR).
  • For cached scrobbles, retries follow the normal backoff policy, but are bounded by a small fixed ceiling:
    • The queue entry is discarded after 5 consecutive unclassified responses for that same scrobble.
    • This prevents endless retry loops when Last.fm repeatedly returns the same unclassified response, or when API changes / undocumented conditions are encountered.
  • If a later attempt returns a temporary or transport-level failure, the “unclassified” streak is reset (so transient network issues do not push an entry toward discard).
  • Applies to persistently queued scrobbles only; Now Playing remains best-effort and non-persistent.
  • Logs indicate discard after repeated unclassified responses, including the counter.

Backoff and Retry Discipline

Retries are bounded, state-driven, and rate-aware.

  • Retry attempts use linear backoff with an upper time cap
  • Only one submission attempt is active at a time
  • Failures immediately pause further drain activity
  • Retries are additionally bounded when Last.fm repeatedly returns unclassified API responses for a specific queued scrobble.

There are no aggressive retry loops, retry storms, or parallel submission attempts.

Recovery Conditions

Submission resumes only in response to explicit recovery events:

  • Successful re-authentication
  • Explicit user action (resuming scrobbling)
  • Restoration of network availability during a scheduled drain opportunity

There are no background probes, heartbeat timers, or speculative retries.

Failure Isolation Guarantees

Network and authentication failures are isolated from the rest of the component:

  • Playback behavior is unchanged
  • UI state remains stable and responsive
  • Cached data is not corrupted or reordered

Failures do not cascade into unrelated subsystems and do not require application restart or manual cleanup.