audio: fall back to the next CDN URL when a fetch returns a non-206 status#1722
Conversation
AudioFileStreaming::open() resolves several CDN URLs from storage-resolve and loops over them to find one that streams, but the loop only treats a transport error (the Err arm) as a reason to try the next URL. An HTTP error status such as 500 is a successful response at the transport layer, so the Ok(_) arm matches and the loop breaks on that first URL. The StatusCode::PARTIAL_CONTENT check then runs AFTER the loop and returns an error, so the remaining (working) CDN URLs are never attempted and playback fails (surfacing as "Unable to load encrypted file: FailedPrecondition"). Reproducible against Spotify today: storage-resolve returns multiple CDN URLs where the first host (e.g. audio-fa-del-874.spotifycdn.com) returns 500 while the others (audio-cf.spotifycdn.com, audio-fa.scdn.co, audio4-ak.spotifycdn.com) return 206. Every track fails even though working URLs were provided in the same response. Move the 206 acceptance into the loop: only break when a URL responds with PARTIAL_CONTENT; otherwise log the unexpected status and fall through to the next URL. The post-loop status check is kept as a safety net. Tested against a live Premium account: before, 0 tracks loaded (every track returned 500 on the first URL); after, tracks load by failing over from the 500 edge to a 206 one.
There was a problem hiding this comment.
Pull request overview
Extends the existing CDN URL fallback logic in AudioFileStreaming::open so that librespot also retries the next CDN URL when the HTTP response is not 206 Partial Content, not just when connection-level errors occur. This aligns the client behavior with Spotify’s expectation that multiple CDN URLs are tried until a working one is found.
Changes:
- Treat non-
206 Partial ContentHTTP responses as a failed attempt and continue to the next CDN URL. - Log non-206 responses as retryable failures during initial streaming open.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
This is very timely, as a Spotify-side issue apparently arose this morning which this PR should help mitigate. #1723 (comment) Please merge! |
|
Can you address the copilot review suggestion, which I think is sensible. And add a changelog entry. |
|
removed the duplicate check and updated CHANGELOG.md |
|
FWIW: I can report one more happy user with this patch! |
|
A bit late to the party, but this can be mocked up to some degree by blocking the top-most served CDN ( |
|
This patch fixes the same issue for me as well. Repro was ncspot/librespot on NixOS with a Premium account: cached tracks played, but uncached tracks failed with Spotify CDN responses like 500/530: I applied this fallback patch locally to ncspot's vendored librespot-audio 0.8.0, and uncached tracks started playing immediately. |
…/librespot#1722) Signed-off-by: Michael Pacheco <git@michaelpacheco.org>
Problem
Spotify hands librespot several CDN URLs for each track and expects the client to try the next one if a URL doesn't work. Right now, librespot only moves on to the next URL when a download fails at the connection level (timeout, DNS, TLS, etc.).
But a CDN edge can accept the connection and still answer with an HTTP error — e.g. a 500 Internal Server Error from an edge node that's being drained. To librespot that counts as a "successful" response, so it stops on that first broken URL and never tries the others. The track fails to load, and playback never starts.
This currently breaks playback completely for some accounts: Spotify is handing out a bad edge as the first URL, so every track dies on the first try even though working URLs were right there in the list.
This fix builds on #1524, which added CDN-URL fallback but only for connection-level errors. This change extends that same fallback to cover HTTP error responses too.
Evidence
For one track,
storage-resolvereturned 4 CDN URLs. Fetching each directly with a range request (curl):audio-fa-del-874.spotifycdn.com- 500 ← the only one librespot triedaudio-cf.spotifycdn.com- 206audio-fa.scdn.co- 206audio4-ak.spotifycdn.com- 206librespot's log on the unpatched build:
It tried the broken URL, treated the 500 as a final answer, and gave up — never attempting the three working URLs.
Fix
A successful fetch is now defined as one that actually returns 206 Partial Content (what a range request should return). Anything else — a 500, or any other status — is logged and treated like the other failures, so the loop continues to the next CDN URL instead of stopping.
The change is in the existing URL-retry loop in
audio/src/fetch/mod.rs(AudioFileStreaming::open): the "is this 206?" check, which previously ran after the loop had already committed to one URL, now runs inside the loop so a non-206 response falls through to the next candidate.Testing
Built from this branch and played tracks against the same account that was failing:
Also verified end-to-end through the
spottyhelper / Lyrion Music Server, where this bug was originally observed: full tracks decode and play.