audio: fall back to the next CDN URL when a fetch returns a non-206 status#1722
Open
dmeiselman wants to merge 1 commit into
Open
audio: fall back to the next CDN URL when a fetch returns a non-206 status#1722dmeiselman wants to merge 1 commit into
dmeiselman wants to merge 1 commit into
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.
Contributor
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.
Comment on lines
469
to
+477
| match streamer_result { | ||
| Ok(r) => { | ||
| Ok(r) if r.status() == StatusCode::PARTIAL_CONTENT => { | ||
| response_streamer_url = Some((r, streamer, url)); | ||
| break; | ||
| } | ||
| Ok(r) => warn!( | ||
| "Fetching {url} returned {} (expected 206 Partial Content), trying next", | ||
| r.status() | ||
| ), |
|
This is very timely, as a Spotify-side issue apparently arose this morning which this PR should help mitigate. #1723 (comment) Please merge! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.