Files
spotifast/docs/_reference/how-it-connects.md
queueeee df9c1e42d0 Keep the volume you set when the session reconnects (#589)
The backend built every replacement engine from the config captured at
launch, so a session that dropped on its own came back at the level the
app started with, not the one being heard. Music turned down to 5% could
return at 80%. The worker now keeps the level of the engine it takes
down and starts the next one there, on every path that replaces the
engine: a session drop, a proxy change, sign-out and the Premium check.
A level set here goes into the mixer and the engine state together, so
the next engine starts at it even before the first play.

A volume set before this computer became the active Connect device was
also lost: librespot's Spirc ignored SetVolume while inactive and
reported its starting level on activation. The fix is in the librespot
fork (crmne/librespot 23fc42c, proposed upstream as
librespot-org/librespot#1770). Moving the pin also brings in the fork's
Windows on ARM platform signature fix.
2026-09-29 12:30:01 +02:00

386 lines
22 KiB
Markdown

---
title: How It Connects
description: Independent Spotify grants, what is stored, and how API traffic is routed.
nav_order: 1
---
## Independent grants, once each
Spotifast uses separate credentials for Web API access, a personal app, and
local playback:
1. **The shared Web API app** keeps full catalogue and playlist coverage.
2. **Your optional personal Web API app** handles supported playback, library,
catalog, catalogue search, playlist creation, and owned or collaborative
playlist requests without using the shared app's quota. Complete
playlist-library views and the playlist half of a search stay on the shared
app so Spotify-owned results are not filtered out. Both Web API grants must
verify as the same Spotify account.
3. **Local playback** uses
[librespot](https://github.com/librespot-org/librespot). It needs one more
browser approval and keeps an independent reusable credential. Spotify Premium
is required. While it is signed in, its session also reads the playlists
the shared app would otherwise be asked for: other people's, and the
account's own when there is no personal app. Radio pages come only from
this session: the Web API has no stations. Opening one resolves the
station and reads its songs' details in one batched request.
Local playback authorization stays separate from both Web API grants. Its
browser approval requests only the streaming permission and always shows the
consent dialog. The playback session uses the account ID verified by either
Web API grant. A verified personal app can complete sign-in while the shared
app's verification is still waiting.
Since 0.8.0, local playback retains the artist IDs
already supplied by librespot. Artist links in the player bar work before the
Web API's track metadata arrives, without an extra request.
Since 0.8.0, local seeks discard audio queued from the previous
position once librespot confirms the seek. This also applies when another
Spotify client controls playback on this computer. Natural track transitions
retain their queue for gapless playback. The seek still waits for librespot to
find and fetch the requested audio, and sound already handed to the device
cannot be recalled. Seeking adds no Web API request or full-track download.
Since 0.8.0, requests that need a grant still being
verified wait for it instead of showing "not signed in". Sign-out cancels
pending requests, and their late results cannot undo a new sign-in. If Spotify
rejects a saved refresh grant, Spotifast removes that grant and asks for a new
browser approval. Upgrading to protected storage does not itself require
signing in again.
By default, Spotifast uses the public app shared with spotify-player, ncspot,
and Omarchy Spotify. Spotify divides its quota among all users. A personal app
adds a separate Development Mode quota. See
[Use a Personal Spotify App](/make-it-even-faster/).
Since 0.8.0, a search runs as two requests when a
personal app is ready: songs, artists, albums, podcasts, and episodes on the
personal app, and playlists on the shared app. This moves catalogue search off the quota Spotify divides among every user
of the shared app. Each half is shown the moment it
arrives, so a shared app waiting out a rate limit no longer holds up the songs,
and playlists appear underneath when that wait ends. A half belonging to an
earlier search is discarded rather than shown beside a newer one. A newer or cleared search cancels the previous requests, including waits
for shared access. If either half fails, the successful results remain usable
with an error for the failed part. A new query starts a fresh result set; old
songs never appear under its name. The loading indicator remains until both
parts finish. A personal app answers
with ten results for each type where the shared app answers with twenty, because
Development Mode rejects the larger page. Without a personal app, one request
still asks for all six types and nothing about a search changes.
Since 0.8.0, verified Premium accounts using shared access see a
one-time introduction to that option. Setup and dismissal are remembered in
settings. The prompt uses the existing account profile and adds no request.
Since 0.8.0, explicitly sorting a Library section loads its remaining
pages through the existing Web API grant, one at a time, while loaded entries
stay visible. A failed page stops automatic loading. Spotify custom playlist
order uses the existing account-scoped rootlist from local playback; sorting
and dragging never write that order back to Spotify.
Since 0.9.0, a playlist-folder and invitation-permission read requested
before local playback connects waits for that connection instead of being lost.
Sign-out discards the waiting read and any result from the previous session.
Since 0.9.0, a dropped active playback session retains its resolved
contexts and queue in memory. The replacement session restores them after
Connect registration instead of loading only the interrupted song. Unresolved
context pages continue through the new session. The snapshot contains no login
credentials, is never written to disk, and cannot be restored by another
account. Reconnecting can still briefly interrupt audio, and the replacement
engine starts at the volume being heard. This recovery applies to unexpected
disconnects; changing audio settings still restarts the engine with its
current-track pickup.
## What the client stores
- Since 0.8.0, shared and personal Web API grants
and the reusable playback credential use the platform credential store:
Secret Service on Linux, Keychain on macOS, and Credential Manager on
Windows. Librespot retains its reusable credential in memory; Spotifast
owns persistence. Flatpak can talk to `org.freedesktop.secrets` for this.
Version 0.7.1 still uses the older unencrypted files.
See [migration, sign-out, and storage protection](/settings-and-files/).
- Downloaded audio and artwork, in the cache directory, within the budget
you set.
- The first time MilkDrop opens with an empty preset folder, the two projectM
preset packs are downloaded from GitHub (about 26 MB) and stored in the
config directory.
- On Windows and macOS, desktop media controls load the cover themselves and
are given a file, so the full-size artwork is downloaded into that cache
when a song starts, even when no view on screen is showing it. Linux MPRIS
carries the Spotify artwork URL for the desktop to resolve and asks for
nothing extra.
- Lyrics, in the cache directory, for a month.
- Liked Songs metadata, scoped to the verified account, in the cache directory.
This behavior is available since 0.8.0.
Cached pages less than 15 minutes old need no repeat request. Older cached
prefixes refresh through the existing Web API grant, one page at a time,
while the saved rows remain visible. Manual refresh starts immediately.
Like and Unlike are kept over lagging reads until Spotify confirms them.
- Spotifast has no telemetry, analytics, or hosted service. When the lyrics
panel is open and Spotify has no lyrics, it sends the track's artist, title,
album, and length to [lrclib.net](https://lrclib.net). It also checks
api.github.com once a day for updates. You can turn off automatic checks in
Settings, or request one there at any time. On macOS, **Check for Updates**
is also in the application menu.
On Windows, macOS, and Linux, downloading an update fetches release metadata and
`checksums.txt` from the project's GitHub release, then the matching binary
archive, Windows installer, or universal macOS DMG. Spotifast checks the published SHA-256 digest
and the portable executable's reported version before offering a restart.
Automatic downloads are optional; installation always waits for your click.
Checks and downloads do not open the update popup. The green update pill opens
it on request; closing the popup does not cancel a download.
No Spotify credential is sent. These are GitHub-hosted checksums, not a
separate publisher signature.
Updates stage their files in a private `.spotifast-update-*` directory beside
the application so replacement stays on the same filesystem. The directory
retains the previous executable or Mac app bundle and `result.txt` for recovery and diagnosis.
Helper startup errors are retained in `helper.log`. On macOS the helper runs
from a copy of the whole signed bundle in that directory, so replacing or
restoring the installed app never moves the helper's own code.
Settings, caches and credential stores are not replaced. Package-manager
installs keep their package-manager update path. Mac updates verify the bundle
identifier, version and code signature before replacing the whole app bundle.
A Developer ID installation also requires the same signing team and a passing
macOS security assessment. Apps running from a disk image or an App Translocation
directory must be moved to a writable installation directory first.
## Collection loading and artwork
Since 0.9.0, opening a playlist, album, artist, or podcast keeps any
title, summary, and cover Spotifast already knows while the complete page is
requested. Controls that need unfinished details stay disabled, and a loading
indicator marks the unfinished content. If the request fails, the known header
stays above **Retry**. A direct link with no known details shows only the normal
loading or error state.
Visible library grid cards use the closest 300-pixel artwork URL already
present in Spotify metadata. Larger collection cards and page headers use the
closest 640-pixel URL. Compact rows, page tint extraction, and softened loading
previews use 64-pixel thumbnails. These URLs remain unchanged and follow the
configured proxy and normal artwork cache. Choosing an artwork size and reusing
known details add no Web API request.
A ready sharp cover stays visible until its replacement is ready. A 300-pixel
grid cover already loaded for the same item can preview a larger page cover
without another download. When only a small known thumbnail is available, a
softened preview may appear while the larger image loads. If the completed
response has no cover, the page shows its placeholder instead of an old image.
Since 0.8.0, album and playlist scrollbars can request a distant track
page through the existing session or Web API read path, without fetching all
preceding tracks. These reads run one at a time per list and retain the existing
rate-limit handling. Unloaded
rows are placeholders until their page arrives; scrolling never starts playback.
## When Spotify pushes back
Each Web API session has separate concurrency and rate limits. A `Retry-After`
response pauses only that session. Spotifast routes each request once and
does not retry it through the other app. A playlist read the librespot session
refuses outright, because the playlist is gone or private, is shown as such. A
dropped connection, a read that takes longer than 30 seconds, or a page whose
song details Spotify did not supply in full, hands the read to the Web API
instead of caching rows without songs. A song Spotify no longer has, or
withholds for legal reasons, is an empty row, as the Web API shows it.
Spotify can also explicitly refuse the key needed to decrypt a track. When
that happens, Spotifast stops local playback and leaves the rest of the queue
alone instead of treating every following track as unavailable. This refusal
comes from Spotify; trying again later may work.
Before adding songs to an existing playlist, Spotifast checks the rows it
already holds. A known duplicate produces an immediate confirmation naming the
song. Only a playlist that has not been fully loaded needs a background scan to
rule out duplicates. Once confirmed, the new rows appear locally at once.
Since 0.8.0,
a drop into an open editable playlist sends its chosen insertion position
through the same Web API grant. Duplicate checks and confirmation retain that
position; partial loaded pages keep the correct continuation offset. A
successful write advances the cached playlist to Spotify's returned snapshot
instead of downloading the playlist again. If Spotify cannot answer the scan,
Spotifast preserves the requested edit and lets the write report its result.
Since 0.8.0, manually reloading an edited playlist waits for all
pending writes and confirmation of the returned Spotify revision before
requesting replacement rows. Current rows, filtering, sorting, and selection
stay visible while it loads. Automatic paging also waits for those edits.
If metadata still reports an older revision after three immediate rechecks,
or the request fails, the page keeps the edits and offers a retry. Refreshing
again retries confirmation without losing the local changes. This uses the
existing playlist requests and adds no periodic polling.
## Podcasts on Home
Since 0.10.0, Home's **Your podcasts** shelf reads the first page of
saved shows, the same request the library's Podcasts shelf makes, if it has
not been read yet. It then asks for the five newest episodes of each of the
eight most recently saved shows, one show at a time, through the normal
personal/shared app routing. Spotify includes each episode's resume point in
those answers. Play uses that position so an in-progress episode continues
rather than starting over. Shows known to be audiobooks are not asked for. These requests
run when Home refreshes: on opening Home, at most once every ten minutes, and
on a manual refresh. A rate limit, exhausted quota or expired sign-in stops the
remaining shows for that refresh; the shelf keeps what it showed before.
## Album queueing
Since 0.9.0, adding an album, single, or EP to the queue resolves
its songs before sending playback commands. A complete album already loaded
in the app needs no catalogue request. Otherwise, the existing album-track
endpoint is read in pages of 50, with the normal personal/shared app routing
and rate limits. The album URI itself is never sent to Spotify's song queue.
Local playback queues the resolved songs through librespot. On another device,
Web API queue writes run one at a time in album order. A later song waits for
the album's writes, and partial queue reads cannot remove the shown album
while those writes are pending. A rate-limited queue write waits for the full
`Retry-After` delay and retries automatically, keeping its place ahead of later
additions. This applies to individual songs too. Sign-out cancels the wait.
A permanent rejection stops the remaining album writes, reports the error, and
removes only the unaccepted additions. Songs already accepted stay queued.
Network failures and server errors are not retried automatically because an
append might already have succeeded.
## Playlist cover uploads
Since 0.9.0, **Edit details → Change cover** opens the native file
picker. Spotifast reads only the selected JPEG or PNG, preserves its aspect ratio, flattens transparent
pixels onto white, and encodes a JPEG preview. Files must be smaller than 20 MB
and no larger than 8192 pixels per side, within a 128 MB decoding budget.
Encoding reduces the image to fit
Spotify's 256 KB Base64 request limit. **Upload cover** sends that preview to
Spotify; **Save** separately saves the name, description, and visibility.
Uploads use the same shared or personal app routing as playlist edits. Requests
are not retried through another app. The uploaded image stays visible while
Spotify propagates its artwork. A changed URL can still contain an earlier
upload, so Spotifast checks the largest returned image through its normal
artwork cache, off the UI thread. Only matching image bytes or decoded pixels
replace the temporary preview. It makes at most three immediate metadata
rechecks; if Spotify is still catching up or the check fails, the preview stays
and a later page refresh can check again. Once confirmed, later cover changes
from other clients can appear. The selected source file is not copied to the
cache or settings.
Image uploads require renewed Web API consent. If Spotifast asks you to sign
in again after updating, approve the image upload permission. Reconnect your
personal app in Settings too, if you use one. Local playback authorization is
unchanged. Spotify can refuse changes to playlists you do not own.
On Linux the file picker uses a desktop portal, with Zenity as a fallback.
Install your desktop's file chooser portal or Zenity if no picker opens.
## Receivers on the local network
Spotify's device list only shows signed-in receivers. A new librespot or
spotifyd receiver is therefore invisible to the Web API.
Receivers announce themselves over mDNS as `_spotify-connect._tcp` and answer
a small HTTP interface. Opening or refreshing the picker first reads
`getInfo` to find each receiver's name and device ID. These probes run off
the UI thread, four at a time, with a two-second limit per receiver and six
seconds overall after discovery. Only responding receivers with a name and
ID are offered. Matching IDs are combined; separate devices can have the
same name. These reads send no account credential.
When a receiver is selected, Spotifast encrypts the stored librespot credential
with a receiver-specific key and a key from a Diffie-Hellman exchange. The
encrypted value only works for that receiver and exchange. Spotifast does not
save another copy of the credential.
The receiver then signs in and appears in Spotify's device list. Spotifast
uses the Web API for subsequent control requests.
## The engine
Playback runs on a separate runtime. Librespot maintains the Spotify Connect
session, exposes this computer as a device, receives transfers, and reports
playback state. If the session drops, it reconnects with the stored credential.
Since 0.9.0, selecting this computer in the device picker asks
librespot to transfer playback from the active Connect device. Spotify supplies
the current song, position, playing or paused state, context, and queue together.
This uses the existing playback session's Connect transfer request and needs
no additional authorization. An old or unavailable Web API playback snapshot
does not cause the previous local song to restart or replace the remote queue.
When the PulseAudio backend is selected on Linux, its PulseAudio or PipeWire
stream is named **Spotifast**, with **Spotify playback** as its description, so
system mixers and audio processors can identify and route it. Explicit
`PULSE_PROP_application.name` and `PULSE_PROP_stream.description` environment
values take precedence.
The same session checks releases that the Web API calls `single`, so confirmed
EPs can carry their precise label. Spotifast deduplicates these checks while
the app session is active. If the engine reconnects, an interrupted check may
be tried again; if metadata is unavailable, its label stays `Single`.
The engine discovers access points through `apresolve.spotify.com` and
connects over TCP in the resolver's preference order: port 4070 first,
falling back to 443 and 80. Only outbound connections are needed; no
inbound ports have to be open.
Each socket setup and handshake shares a five-second deadline. The pinned
librespot version can try up to six access points and retry each connection
once. Spotifast allows 75 seconds for the whole engine connection, giving
stalled endpoints more time to fall back. Resolving access points,
authentication, and other setup also use that outer deadline, so reaching
every fallback endpoint is not guaranteed.
Since 0.8.0, the access-point and Dealer TCP connectors resolve
names off the playback runtime thread and try all returned addresses. If
the preferred IPv4 or IPv6 route stalls, the other family starts after
300 ms. DNS, TCP setup and any HTTP proxy tunnel share a five-second limit.
The access-point handshake still shares its existing five-second budget;
the Dealer's WebSocket TLS verification is unchanged.
When a proxy is configured, only its name is resolved locally. The target
name is sent through CONNECT, and a proxy failure never falls back to a
direct connection. Proxy URLs and credentials are not logged by this
connector. The change adds no destination or background polling.
## Proxy
Since 0.9.0, Settings → Proxy has four modes:
- **Off**: a direct connection. Environment proxy variables are ignored.
- **System**: `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`, and on macOS and
Windows the OS proxy. This is the default.
- **HTTP**: a configured HTTP proxy: host, port, and optional login.
- **SOCKS5**: a configured SOCKS5 proxy: host, port, and optional login.
Spotify hostnames are resolved by that proxy.
The mode selects the protocol. Host and port are separate fields.
These settings apply to Spotifast's requests. The external browser used
for Spotify approval keeps its own network and proxy settings.
The Web API, artwork, lyrics, update checks and downloads, and MilkDrop preset
downloads follow that mode. Update downloads retain their release-host redirect
restrictions and checksum verification. Local receivers on the LAN are never sent through a proxy.
Local playback can only use an unauthenticated, plaintext HTTP proxy: that is
what librespot's CONNECT client supports. Proxy login still covers catalogue
and control, but the engine then connects to Spotify directly. SOCKS5 behaves
the same way for local playback. System uses the same proxy reqwest would
(environment variables, and the OS proxy on macOS and Windows) only when it
is an unauthenticated `http://` endpoint; authenticated, `https://`, and SOCKS
system proxies are ignored by the engine. Applying a proxy choice restarts
local playback when it changes the endpoint used by the current connection,
including when the system proxy has changed since that connection started.
Off and System apply immediately. HTTP and SOCKS5 apply when you press
**Apply settings**. The same choice is on the sign-in screen, so it can be
set before the first grant. The expanded sign-in form scrolls in short windows.
Manual edits remain drafts until the backend
accepts Apply or Sign in. Saving another preference does not save those drafts.
A configuration that cannot be built produces an error without silently
switching to a direct connection. Applying a valid configuration repairs it.
On startup, network work waits for the protected proxy password to be restored.
That lookup runs on the credential worker and does not block the interface or
shutdown. The password belongs to its host, port, and username; editing any of
these fields clears it. See [password storage and migration](/settings-and-files/).