How It Connects
Independent grants, once each
After sign-in, the one-time player setup leads with Spotify Soloist on supported platforms. This also appears for existing built-in listeners after updating. Its personal key and pairing are separate from the grants below. Choosing the built-in player is remembered; existing librespot grants remain available for playback and library requests.
Spotifast uses separate credentials for Web API access and local playback:
- Your own Spotify app, the personal app, carries the Web API: account, library, catalogue, search, playlists and playback control. Since 0.14.2 it is the only way to sign in. The sign-in screen walks through creating one, and its Client ID is kept in settings (Getting Started). Before opening the browser, Spotifast asks Spotify’s token endpoint whether it knows the Client ID, with a code that cannot be valid: an unknown client is refused as such, a real one for the code, and the sign-in screen says which. Spotify’s own page shows a redirect URI that does not match and never tells the app, so the screen waiting for the browser explains it. Home’s Made for you shelf first reads the account’s actual personalized section over the playback session. Only if that read fails does it try four playlist searches as an approximate fallback.
- A shared grant from before 0.14.2. Earlier versions signed everyone in through one app, which the ncspot project registered. It stopped taking new sign-ins in October 2026 (#1057), and Spotifast no longer carries its Client ID. A grant already saved for it keeps working: it is stored with the ID it was issued to and refreshes with that. Complete playlist-library views and the playlist half of a search stay on it while it lasts, so Spotify-owned results are not filtered out. When Spotify refuses it, a personal app carries on alone; without one, the sign-in screen says why and shows the setup. Both Web API grants must verify as the same Spotify account. Spotify’s desktop client identity cannot take its place: Spotify answers its Web API requests with “too many requests” from the first one.
-
Local playback uses 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 a personal app cannot: other people’s, Spotify’s own among them, and the account’s own when there is no personal app. The personalized Made for you shelf needs this active session and a verified Web API grant, preferably personal. Spotify’s private Home request receives the Web API access token as its
sp_tcontext and the session’s Login5 bearer and client token as headers. No token is logged or persisted for this shelf. Its response is kept in memory for ten minutes per account. Visible cards are also saved in the account-scoped Home cache for the next start and removed on sign-out. A manual refresh bypasses the in-memory cache but respects anyRetry-Aftercooldown. A failed refresh keeps previously shown cards. If the Home read fails or playback is not set up, a Web API search looks for Spotify-owned playlists named Discover Weekly, Release Radar, Daily Mix, and daylist. The shelf keeps its Made for you heading, although search cannot recover the account’s Home ordering or every personalized card. The same read also asks for the account’s Your top mixes section, at the same time, with the same tokens, cache and cooldown; aRetry-Afterfrom either section pauses both. Search cannot find personal mixes, so that shelf has no fallback and keeps any cards it already shows. When playback becomes ready after Home, the personalized read is retried while existing cards stay visible. Shared rate limits can still prevent this fallback from loading. This private response can change without notice. 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. Play counts on album pages and an artist’s popular songs come from the same Pathfinder queries Spotify’s own album and artist pages make, with the session’s token and an already verified Web API grant. Each album or artist is read once per run, and a read that fails leaves the column out.The Listening activity panel also needs this session. While the panel is open, and only then, the session reads Spotify’s private
listening-activity/v1/feedevery 15 seconds through librespot’s spclient, then reads the songs’ details in one batched request. Friends’ names, avatars and playlist names are read once per run and kept in memory. Nothing from the panel is written to disk or logged. If the feed fails, the session’s olderpresence-view/v1/buddyliststands in.
Local playback authorization stays separate from the 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 a Web API grant, so it cannot sign in without one: there is no playback-only mode. A verified personal app can complete sign-in while a shared grant’s verification is still waiting. That verification keeps retrying in the background through rate limits without showing the top bar’s “Waiting for Spotify” spinner, since nothing on screen waits for it. A request waiting for shared access that turns out not to exist goes to the personal app.
Web API sign-in opens the browser only after its local callback port is available. If port 8989 is occupied, the sign-in screen keeps the error visible. Close the app using that port, then choose Try again. Since 0.13.2, playback setup does the same with its port, 8898: if another app holds it, Settings shows the error, the browser stays closed, and a saved playback approval is kept.
Artist pages read popular tracks over the verified account’s local playback
session when it is ready. The list uses that account’s country, or Spotify’s
worldwide (ZZ) ranking when its country is absent, and keeps the first ten
tracks in Spotify’s order. Track details arrive in one metadata batch. If the
session is unavailable or cannot answer, the Web API remains the fallback,
including its rate limits. It goes to a shared grant from before 0.14.2
while one is signed in, and otherwise to the personal app, which Spotify may
refuse it. A different account’s playback
session never supplies this list.
An explicitly empty ranking is kept. If a nonempty ranking resolves to no
tracks, the Web API gets its turn rather than showing a false empty answer.
Related artists also prefer the same-account playback session. The existing
configured HTTP client sends queryArtistRelated with the session’s Login5
bearer and client token, without waiting on the Web API’s authorization,
rate-limit cooldown or request budget. The returned cards retain their order,
names and images. A successful empty list stays empty; a missing or malformed
private response falls back to the Web API, with the same limits as popular
tracks. This uses the
same session-read deadline and does not request another grant or permission.
Background reads have separate four-request budgets for the shared Web API, the personal Web API, and the playback session. Waiting for shared sign-in or rate-limit recovery therefore cannot occupy the personal app’s or playback session’s slots. A failed session read releases its slot before trying the Web API. Foreground requests retain their existing transport concurrency limit.
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.
Explicit track replacements and confirmed seeks also clear the equalizer’s filter history and the limiter’s eight milliseconds of held audio. This keeps a fragment of the previous song out of a replacement’s silent intro. Natural track transitions and pause/resume retain that history.
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.
Each personal app has its own Development Mode quota; a shared grant shares its app’s quota with everyone still signed in through it. Playback state is checked every 15 seconds, every 20 while this computer’s player is active, every 30 while the window cannot be seen and nothing plays, every 4 while the window is visible for ten seconds after a command or a switch to another device, once more just after a song on another device should end, and as soon as this computer’s player stops. See Make It Even Faster.
Since 0.8.0, a search runs as two requests when a personal app is ready and a shared grant is still signed in: 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. With only one grant, one request asks for all six types.
Since 0.8.0, verified Premium accounts using a shared grant see a one-time introduction to their own app. Setup and dismissal are remembered in settings. The prompt uses the existing account profile and adds no request. When Spotify answers a shared-app Web API request with “too many requests” (HTTP 429), those accounts also see a banner offering the same setup, at most once a day and never again after it was closed three times. It reads the answer the request already received 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. When the window regains focus, or is shown again from the tray, after every playlist page has loaded, Spotifast reads the rootlist again over the playback session, at most once every 5 seconds, so an order changed in another Spotify app appears without a restart. A return sooner than that reads once the 5 seconds have passed. This read uses no Web API quota.
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. 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.
A song playing or paused when the session drops stays as it is while the replacement session connects: the player keeps playing the audio it has already fetched, and the new session takes that same player over and picks the song up where it has got to, without loading it again. Pausing or playing during the reconnect works on that song. A song that runs out of fetched audio, or the next song, which needs a new audio key, waits for the new session. The song plays on through failed reconnection attempts that are retried; choosing something else, changing audio settings, signing out, or a reconnection that gives up stops it.
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, where a copy encrypted for the Windows account with the Data
Protection API also stays in the data folder for when Credential Manager
refuses or loses a grant. Since 0.13.4, a portable Windows copy keeps
that data folder next to its executable, and the first time it makes
one from the installed copy’s state it saves those grants again under
entries of its own, never moving or deleting the installed copy’s
(portable copies). Librespot retains its reusable credential in
memory; Spotifast owns persistence. Flatpak can talk to
org.freedesktop.secretsfor this. Version 0.7.1 still uses the older unencrypted files. See migration, sign-out, and storage protection. - 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 neither Spotify nor a local file has lyrics, it sends the track’s artist, title, album, and length to 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 a connection the system reports as metered (Windows), the daily check, automatic downloads, and the large cover the media controls show wait for one that is not; a check by hand still runs. On macOS, the daily check and automatic downloads also wait while the system reports no internet connection.
On Windows, macOS, and Linux, downloading an update fetches release metadata and
checksums.txtandchecksums.txt.sigfrom the project’s GitHub release. It verifies the manifest’s Ed25519 signature against its embedded publisher key before downloading the matching binary archive, Windows installer, or universal macOS DMG. Spotifast then 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. Missing or invalid publisher signatures are rejected; the updater does not fall back to trusting unsigned checksums.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 andresult.txtfor recovery and diagnosis. Helper startup errors are retained inhelper.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.
Each cover uses the smallest artwork URL already present in Spotify metadata with at least a quarter more pixels than the cover takes on the display. Spotify’s 64-pixel thumbnail serves only covers of 32 pixels or less, so at 100% scaling library rows, cards and page headers use the 300-pixel URL. Of the usual 25% scaling steps, page headers use the 640-pixel one from 125% and cards from 175% (to the whole percent, from 114% and 163%). 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
Spotify is being slow explains rate limits for listeners.
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.
While a shared grant is signed in, the playlist list and fallback Made for you searches go through it even when a personal app is set up, because a personal app’s list leaves out Spotify’s own playlists. Without one, they go to the personal app, and the playlist list comes from the playback session’s rootlist whenever local playback is signed in, as described below. While the Web API waits out Spotify’s limits, the sidebar and Home show what they last showed for the account, from the cache described in Settings & Files.
While local playback is signed in and no shared grant can answer now, because there is none, or it is still verifying sign-in or waiting out a rate limit, the playlist list is read from the playback session’s rootlist instead: every saved and followed playlist, Spotify’s own among them, in one request per 500 playlists. The rootlist names each owner by ID only, so their display names are read from their profiles over the same session, four at a time, for at most five seconds; a name that does not arrive in time shows as the ID. A playlist without a cover of its own keeps the mosaic the earlier list showed for it. A list still waiting on the shared app when local playback connects is read again this way. If the session cannot read the list, the request waits for the shared grant, or goes to the personal app when there is none. None of these reads use Web API quota.
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. A banner says so and offers the device picker, since other Spotify Connect devices fetch their own audio and keys; Spotify refused the audio key explains it for listeners. The banner reads the engine’s own event and adds no request.
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.
New releases on Home
Home’s New releases shelf is read when Home refreshes, and again when
local playback becomes ready. With local playback set up, Spotifast sends
Spotify’s own new-releases feed query (queryWhatsNewFeed) to Pathfinder
with the session’s Login5 bearer and client token, asking for the first 50
albums, EPs and singles. Spotifast never sends the feed’s mutation that marks
items as seen, so the bell in Spotify’s apps is left as it was. The query
uses no Web API quota.
When local playback is not set up, or the feed cannot be read, the Web API stands in: the followed artists, 50 to a page, and for each of the first 50 of them its ten newest albums and singles, through the normal personal/shared app routing, merged and sorted by release date. That answer costs a request for each artist, so it is kept in memory for an hour. A rate limit, exhausted quota or expired sign-in stops the remaining artists for that refresh; the shelf keeps what it showed before.
The New releases page lists the songs of the releases on the shelf. Over the playback session they come from two batched metadata reads, one for the releases’ track lists and one for the songs; without it, from the Web API’s album tracks, one request per release.
Songs hidden in a playlist
Spotify’s own playlists, Discover Weekly and the other mixes made for you,
offer Hide in this playlist in a song’s menu, as Spotify’s apps do. The
hidden songs live in the account’s playcontextbans collection set on
Spotify’s client API, which librespot reads and writes for Spotifast. When
local playback connects, Spotifast reads that set once, 500 items to a
page. Hiding or showing a song greys its row out or brings it back at once,
and sends one write for that song and playlist; if Spotify turns it away,
the row goes back and an error is shown. While a playlist has hidden songs,
playing it plays the songs on screen without them, as a filtered playlist
does, so Shuffle and Repeat follow the shown songs. Hiding needs local
playback; without it the menu entry is not offered.
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, all at once within the Web API session’s concurrency limit, 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.
Restricted devices and Sonos
Spotify marks some devices as restricted, Sonos players among them. The Web API refuses every player command for one with “Restriction violated” and leaves an idle one out of the device list. Spotifast sends commands for a restricted device over the playback session’s Connect state service instead, as Spotify’s own apps do: a transfer, then player commands (play, pause, resume, skip, seek, shuffle, repeat, queue) and volume, all sent from this computer’s Connect device to that one. This needs local playback enabled, because the session belongs to it. Playback state is still read from the Web API, which reports a restricted device while it plays.
Shuffle Play enables shuffle before starting the context, as it does for Web API devices. If enabling shuffle fails, playback does not start.
Individual and album queue additions use the same Connect route, one song at a time. A later addition waits for the whole album, repeated songs keep their places, and a failed write stops the remaining songs. Signing out cancels pending commands and forgets the account’s restricted-device state. The playback session must belong to the verified Web API account before it can control another device.
A Sonos is recognised by its receiver endpoint (/spotifyzc on port 1400)
and is offered in the picker from the network even while Spotify does not list
it. Its receiver asks for an OAuth authorization code instead of the librespot
credential. Spotifast does not send a login blob or access token to that HTTP
endpoint. Transfer and player commands go through Spotify using the existing
playback session. Other receivers keep the encrypted login blob described above.
Google Cast devices
Cast speakers, displays and speaker groups announce _googlecast._tcp, not
_spotify-connect._tcp. They do not run Spotify until a sender starts it.
Spotifast lists them under their Cast name (the fn record) without probing
them, because probing one would start Spotify on it.
The picker shows the first resolved Cast announcement immediately while its three-second browse continues, batching subsequent changes at most every 100 ms. It retains at most 128 Cast services and one pending UI update, ignores unchanged advertisements, and bounds provisional rows during receiver churn. Cast rows do not wait for the subsequent Connect receiver probes. Reopening or refreshing the picker during that work shares the scan. Existing rows remain available during refresh; the completed scan removes departed devices and reconciles Connect/Cast duplicates as before. If Connect probe setup fails, current Cast results still complete the scan and remove stale rows. No receiver address is saved to disk or assumed reachable on the next launch.
The active output stays first. Other outputs are ordered by how often they
occur among the last 32 explicit picker selections, most recent first when
counts match, then by name. This combines signed-in devices and receivers
waiting on the network. Only preference keys and their account ID are saved in
session.json; another account does not use that ranking, and sign-out clears
it. Choosing an output records the selection, including an attempt that fails;
background discovery and polling never increase its count. There is no new
setting or network request for ranking.
Signed-in outputs use their Spotify device IDs. A waiting Cast receiver uses a separate hash of its transfer ID, address and port, so another endpoint with the same advertised name does not inherit that ranking. No raw address is stored; an address or port change resets this preference. Waiting and signed-in Cast rows have separate histories. This is an ordering hint, not authenticated device identity; authentication still happens only on selection.
Starting Spotify on a Cast device needs local playback enabled, because the playback session obtains the token. When a Cast device is selected, Spotifast follows the steps of Spotify’s own apps:
- It opens a Cast v2 connection to the device, checks that the device is a genuine Cast device (see below), and launches Spotify’s Cast receiver.
- The receiver’s
getInforeply names the Spotify client it runs as. - The librespot session asks Spotify for a token issued to that client and
to the device’s ID (
/device-auth/v1/refresh, an internal endpoint). - Spotifast sends that token to the receiver with
addUser.
The device ID is the MD5 hash of the Cast name, as in Spotify’s apps. The device then appears in Spotify’s device list, and the Web API controls it like any other device. Right after an earlier sign-in, a receiver can refuse the first attempt, so Spotifast tries once more. A device that does not answer within 20 seconds is reported as not answering.
A Cast device presents a TLS certificate of its own, which system trust stores
do not hold, so the connection accepts any certificate. Since 0.13.1, before
sending anything else, Spotifast asks the device to prove it is genuine, as
Chrome does. It
sends a random challenge on Cast’s deviceauth channel. The device signs the
challenge and the certificate of this connection with its device key, and
sends that key’s certificate chain. Spotifast checks that the chain leads to
one of Google’s two Cast root certificates, which it carries, that the device
certificate is one for signing, and that the signature is valid. Because only
that signature vouches for the connection’s certificate, the connection’s
certificate must also be valid now and for no more than four more days, as
Chrome requires. If any check fails, Spotifast stops. It does not start
Spotify on the device or ask for a token, and it reports that the device could
not prove it is genuine. As in Chrome, older devices that sign with SHA-1 are
accepted, and the revocation list a device may send with its answer is not
checked (Chrome only checks it when told to require one). Spotifast 0.13.0
made no such check: whatever answered at the address of the selected device
received the token.
The token is issued for the Spotify client and device ID that the receiver
names in its getInfo reply, not the reusable playback credential. Spotifast
does not save or log the token.
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. On macOS, while the system reports no route to the internet at all, the reconnection waits for one instead of retrying. Spotifast asks the system about the internet as a whole rather than about a Spotify host, so the check looks up no name and sends nothing over the network.
Since 0.13.2, a playback connection that fails while the network is up, for example with a connection reset after the computer wakes, is tried again with the saved playback approval: after 5 and 15 seconds, 1, 2 and 5 minutes, and then every 10 minutes until it connects. These attempts keep the error on screen instead of flickering through “Connecting”, and they stop when Spotify rejects the approval, playback is set up again, or you sign out. Set up playback here and Try again first reconnect with the saved approval; the browser opens only when there is none or Spotify rejects it.
On macOS, local playback and track loading declare a user-initiated activity so App Nap does not throttle them when another application covers Spotifast. Pausing, stopping, or an output error releases the activity; a song playing on through a dropped session keeps it, and remote playback does not hold it. The activity permits idle system sleep and does not keep the display awake.
Local playback and track loading also keep the computer from
sleeping when idle, released at the same moments (#962): on macOS an
activity with idle system sleep disabled (pmset -g assertions), on Windows
a system power request (powercfg /requests), and on Linux the desktop
portal’s Inhibit request for suspend, or a systemd-logind idle inhibitor
when there is no portal (systemd-inhibit --list). None of them keeps the
display on, and a sleep you ask for still happens.
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.
The engine prepares the next two songs of the queue while one plays, so Next starts the following song without waiting for it to load. For each, it reads the song’s metadata and fetches the first 128 KB of its file from Spotify’s CDN, about three seconds of audio at 320 kbit/s, and keeps them in memory; for the next song it also asks for the audio key. A song already in the audio cache is not fetched again. Nothing is wasted when the song then plays: its load uses what was fetched. Skipping further than two songs ahead loads the song as before, and a song skipped past before it has loaded stops loading. These requests go through the playback session and use no Web API quota.
Audio keys are asked for one at a time, at least 200 ms apart. When Spotify refuses one, the session waits 3 seconds before asking for any key, then 5, then 10 after each further refusal in a row, and a key that arrives ends the wait. The song being loaded asks again after each wait, up to three times, before Spotifast reports the refusal; the next song’s key is not prefetched during a wait.
Local playback has one output on every platform, fastframe-audio: CoreAudio on
macOS, WASAPI on Windows, and on Linux PipeWire when it is running, otherwise a
PulseAudio server, otherwise ALSA directly. It talks only to the local sound
system. On PipeWire and PulseAudio the stream is named Spotifast in system
mixers (application.name, with the spotifast icon), with Spotify
playback as its media name and no media role, so its volume is remembered
for Spotifast rather than shared with other music players.
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.
Upcoming albums and countdown links
spotify:prerelease: and Spotify prerelease share links require the same-account
local playback session. The session’s extended metadata kind 138 resolves the
prerelease URI to its real album URI, cover, artist and release instant. They
are different IDs. Pathfinder getAlbum reads the ordered track preview and
playability using Login5 and the client token through the existing configured
HTTP client. These reads do not use Web API quota. Hidden positions
remain visible, and unavailable rows never enter playback or shuffle lists.
Collection membership uses /collection/v2/contains. Pressing Pre-save or
Pre-saved sends one explicit /collection/v2/write for the resolved album URI and
then checks membership. Writes use the configured proxy with transport retries
and redirects disabled. Opening the page never changes the library. Both use
the existing session’s authorization, with no new OAuth permissions or stored
credentials. Failed confirmation leaves state unknown and offers a fresh read;
it never automatically repeats a mutation. Requests have a 20-second deadline.
Sign-out cancels work, stale session/page generations are ignored, and a new
link or history navigation cannot be overwritten by an older response.
Removing one copy of a repeated song
Removing a song from a playlist uses the Web API’s DELETE
/playlists/{id}/items, which removes every copy of the song. When the
playlist holds the removed song more than once, Spotifast instead reads the
playlist’s rows and revision over the session (/playlist/v2/playlist/{id})
and sends one /playlist/v2/playlist/{id}/changes request through librespot
that removes the picked rows by index, based on that revision, with the
session’s own authorization. Spotify refuses the change if the playlist
changed since. The request is sent once, never retried, and has a 20-second
deadline. A row that is no longer
in the playlist removes nothing; the playlist reloads and shows the error.
Optional Soloist player
Soloist setup describes the optional Spotify-owned player. Spotifast downloads it directly from Spotify, stores its personal key in the system credential store, and starts one child with a loopback-only WebSocket. The device must appear in the signed-in account’s Spotify device list before local control is enabled. Basic commands and state use the local API; collection starts keep their offset through the existing playback request. Library and metadata connections remain available through the existing Web API and librespot session. Signing out stops the child and deletes its saved key and pairing data. Soloist’s own streaming traffic is not routed by Spotifast’s proxy settings.