Spotify Connect controller. Written in Rust.
Terminal controller for Spotify, and a Spotify Connect device. Search, browse playlists and saved tracks, queue items, switch devices, drive playback (play / pause / next / prev / seek / volume / shuffle / repeat). tune registers itself as a Spotify Connect endpoint via librespot — so audio plays right out of the same binary on whichever machine you launched it on — but it can also drive any other Connect device on your account (phone, desktop client, web player, smart speaker). Built on crust. Part of the Fe₂O₃ Rust terminal suite.
Requires a Spotify Premium account. librespot's audio stream requests are gated behind Premium; controller-only mode (search, browse, transport on other devices) still works for free accounts.
Setup (one time, ~2 min)
tune talks to the Spotify Web API on your behalf, so it needs its own developer-app credentials. There's no shared client_id — every user registers their own.
- Open https://developer.spotify.com/dashboard, log in with your regular Spotify account.
- Create app. Name:
tune(or whatever). Description: free text. - Add Redirect URI:
http://127.0.0.1:8888/callback - Tick Web API, save.
- In the new app's Settings, copy the Client ID.
Run tune for the first time — it prints these same instructions and prompts for the client ID. Once pasted, tune writes ~/.tune/config.yml, opens your browser for the authorization grant, captures the redirect, and caches the token at ~/.tune/token.json. Subsequent launches skip straight to the TUI.
Install
git clone https://github.com/isene/tune cd tune cargo build --release cp target/release/tune ~/.local/bin/
Or symlink for live rebuilds:
ln -s "$(pwd)/target/release/tune" ~/bin/tune
Keybindings
| Key | Action |
|---|---|
| Views | |
/ |
Search |
P |
Your playlists |
L |
Liked / saved tracks |
Q |
Up-next queue |
d |
Spotify Connect devices |
? |
Help |
| Playback | |
SPACE |
Play / pause |
n / b |
Next / previous track |
+ / - |
Volume ±5% |
] / [ |
Seek +5s / −5s |
s |
Toggle shuffle |
r |
Cycle repeat (off / context / track) |
| Lists | |
j / k |
Down / up |
g / G |
Top / bottom |
PgDn/PgUp |
Page down / up |
ENTER |
Play this item / open playlist / switch to device |
a |
Add this track to the queue |
h |
Back to playlist list (from PlaylistTracks) |
| Misc | |
R |
Refresh now-playing |
q |
Quit |
What you can do
- Search for tracks (
/→ type query). ENTER plays the cursor;aadds it to the queue. - Browse your playlists (
P), open one with ENTER, scroll, ENTER again to play from that track within the playlist context.hgoes back to the playlist list. - Switch device (
d) — pick any Spotify Connect device (your phone, desktop client, a speaker) and ENTER transfers playback there. - Liked songs (
L) — your saved tracks, scroll and ENTER to play. - Up next (
Q) — shows what Spotify will play after the current track. Context-driven autoplay also shows here oncecurrent_user_queueresolves it. - Transport — SPACE pause/resume, n/b skip, +/− volume, [/] seek, s shuffle, r repeat. Status reflects current playback state on a 2s poll.
What you can't do
- Edit playlists. Currently read-only; add/remove/reorder lives behind a future scope grant.
- Free-tier accounts. Spotify gates audio streaming AND most playback-modify endpoints behind Premium. Search and library browsing work, but transport calls return 403.
Config
~/.tune/config.yml:
client_id: "<your spotify developer client id>" poll_s: 2 # now-playing refresh cadence, seconds default_device: "" # preferred device id; empty = last-used local_player: true # register tune itself as a Spotify Connect device device_name: "tune" # name shown in Spotify Connect picker
Token cache: ~/.tune/token.json (refresh token + access token; auto-refreshed when stale). Delete the file to force re-authorization (e.g. after adding a new scope).
Battery-drain profile
tune is built to be quiet on a laptop:
- Idle (nothing playing): librespot keeps a long-lived TCP keep-alive to Spotify's access-point server (one packet every ~30 s), the now-playing pane polls the Web API every
poll_sseconds (default 2 s, one tiny request). Audio backend's sink suspends. CPU near zero, no audio device wakeups. - Playing: ogg/vorbis decode + pulseaudio write. Single-digit CPU% on any modern laptop.
- Paused: same as idle.
mDNS-based discovery (which would broadcast periodically) is off by default — tune authenticates via OAuth, so it doesn't need to advertise itself on the LAN. The librespot with-libmdns / with-avahi features are disabled.
Part of the Rust Terminal Suite (Fe₂O₃)
See the Fe₂O₃ suite overview and the landing page for the full list.
Dependencies
Build: Rust toolchain.
Runtime: a working browser for the one-time OAuth flow (xdg-open / open / equivalent). Once authorized, tune runs offline-of-the-browser — only the Spotify Web API needs to be reachable.
License
Unlicense — public domain.
Credits
Built on rspotify for the Web API layer. Pair-programmed with Claude Code.