spot: A Spotify Client Built on Consult, Embark, and Marginalia

spot: A Spotify Client Built on Consult, Embark, and Marginalia

1. About   emacs spotify completion

spot-banner.jpeg

Figure 1: JPEG produced with DALL-E 4o

There are a few Spotify clients for Emacs. Most of them predate the modern completion ecosystem, so they build their own UI from scratch: custom buffers, custom keymaps, custom rendering. spot takes a different approach. It's built entirely on consult, embark, and marginalia, which means searching, acting on results, and reading metadata all use the same interfaces you already know.

2. Why Another Spotify Client   spotify motivation

smudge (formerly spotify.el) is the most established Emacs Spotify client. It works, but it renders results in a custom tabulated-list buffer. You search, wait for a buffer to pop up, navigate it with its own keybindings, and act on items through its own action system. This is a parallel universe of interaction that doesn't benefit from any of the completion infrastructure you've invested in.

spot doesn't have its own UI idioms to learn. The UI is consult + embark + marginalia.

If you use vertico or selectrum for completion, embark for contextual actions, and marginalia for annotations, you already have a sophisticated interaction model. spot plugs into that model directly. Search results appear in your minibuffer completion framework. Metadata shows up as marginalia annotations. Actions are embark actions. There's nothing new to learn, just Spotify data flowing through tools you already use.

3. Architecture   emacs design

spot is modular by design. Each concern lives in its own file:

  • spot-auth.el: OAuth2 authorization code flow with token refresh
  • spot-search.el: Mutex-guarded, cached search with argument parsing
  • spot-consult.el: Seven async consult sources (albums, artists, tracks, playlists, shows, episodes, audiobooks)
  • spot-marginalia.el: Annotation functions per content type
  • spot-embark.el: Action keymaps per content type
  • spot-mode-line.el: Currently-playing display with smart update timing

The search layer deserves a note. spot uses a mutex to serialize concurrent search requests and caches results by query string. This matters because consult's async sources fire searches as you type, and Spotify's API has rate limits. Without the mutex, rapid keystrokes could produce interleaved results or hit rate limits. The cache prevents redundant API calls when you backspace and retype.

3.1. OAuth2 Flow

Spotify requires OAuth2 for most API operations. spot handles the authorization code grant flow: it opens the Spotify authorization URL in your browser, you log in and approve, then paste the authorization code back into Emacs. The access token is stored in memory for the session, and spot refreshes it automatically when it expires. You set spot-client-id and spot-client-secret from a Spotify Developer application.

4. Search and Navigation   spotify consult

M-x spot opens a consult--multi search across all seven content types. As you type, spot searches the Spotify API asynchronously and populates completion candidates. Each content type has a narrowing key:

Key Content Type
a Albums
A Artists
p Playlists
t Tracks
s Shows
e Episodes
b Audiobooks

Narrowing works the way consult narrowing always works: press the narrowing key to filter to a single content type, or leave it open to see everything.

Queries also support Spotify's field filter syntax via -- separator. For example, radiohead -- --type=album restricts the API query to albums only, which is more efficient than fetching everything and narrowing client-side.

5. Demo: Search and Playback   spotify demonstration

Here's a search session showing multi-source results, marginalia annotations, and playback control.

6. Marginalia Annotations   marginalia 

Each content type has its own marginalia annotator that pulls relevant metadata from the Spotify API response. Here's what you see in the completion margin for each type:

Type Annotations
Albums Artist, release date, track count
Artists Popularity score, follower count
Tracks Track number, artist, duration, album, release date
Playlists Track count
Shows Publisher, media type, episode count, description
Episodes Release date, duration, description
Audiobooks Publisher, narrator, author, description

This is pure marginalia integration. The annotators register themselves when spot-mode is enabled and unregister when it's disabled. If you don't use marginalia, spot still works, you just don't see the extra metadata.

7. Embark Actions   embark actions

spot defines embark keymaps for every content type. When you press your embark-act key on a completion candidate, you get actions appropriate to that type:

Key Action Available On
P Play All types
s Show raw JSON data All types
t List tracks Albums, artists, playlists
+ Add to playlist Tracks only

Listing an album's tracks, for example, opens a new consult search narrowed to that album's tracks. From there you can play individual tracks, inspect them, or add them to a playlist, all through the same embark action system.

8. Mode Line   emacs modeline

When spot-mode is enabled, the mode line shows the currently playing track, artist, and album. The display updates on a 30-second timer, with smart scheduling that calculates the time remaining in the current track to minimize unnecessary API calls. The currently-playing text is displayed in Spotify green (#1db954) by default.

9. Player Controls   spotify playback

spot provides standard player commands, all operating through Spotify Connect:

(spot-player-play)      ; Resume playback
(spot-player-pause)     ; Pause playback
(spot-player-next)      ; Skip to next track
(spot-player-previous)  ; Skip to previous track

There's also spot-add-current-track-to-playlist, which prompts you to select from your playlists (via consult, naturally) and adds whatever is currently playing.

10. Getting Started   emacs installation

spot is on GitHub. It requires Emacs 29.1+ and depends on consult, embark, marginalia, ht, and dash.

(use-package spot
  :ensure (:host github :repo "chiply/spot")
  :config
  (setq spot-client-id "your-client-id"
        spot-client-secret "your-client-secret")
  (spot-mode 1))

You'll need to create a Spotify Developer application at https://developer.spotify.com/dashboard and set the redirect URI to http://localhost:8080/callback. On first use, run M-x spot-authorize to complete the OAuth2 flow. After that, M-x spot and start typing.