Skip to content

fix: detectHttpByteRanges should still try a Range GET after an inconclusive HEAD - #1245

Open
gavrichards wants to merge 1 commit into
software-mansion:mainfrom
gavrichards:fix/detect-http-byte-ranges-head-fallback
Open

fix: detectHttpByteRanges should still try a Range GET after an inconclusive HEAD#1245
gavrichards wants to merge 1 commit into
software-mansion:mainfrom
gavrichards:fix/detect-http-byte-ranges-head-fallback

Conversation

@gavrichards

Copy link
Copy Markdown
Contributor

What

detectHttpByteRanges()'s own docstring says it does "HEAD, then Range probe" — but the code only tried the Range GET fallback when the HEAD request itself threw. Any HEAD response that merely succeeded, even without an Accept-Ranges: bytes header, was treated as decisive proof of "no range support," short-circuiting before the documented fallback probe ever ran.

Why this matters

This misdetects real servers. Confirmed against a live public Icecast stream (SomaFM):

$ curl -I https://ice1.somafm.com/groovesalad-128-mp3
HTTP/1.1 200 OK
...no Accept-Ranges header...

$ curl -H "Range: bytes=0-0" https://ice1.somafm.com/groovesalad-128-mp3
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-0/1073741823

The server answers a Range GET with a genuine 206, but its HEAD response never mentions ranges at all — a common pattern for Icecast/Shoutcast configs. Under the current logic, detectHttpByteRanges gives up right after the HEAD check and never finds this out, so loadRemoteHttpSource falls back to downloadRemoteHttpSource(), which calls response.arrayBuffer() — and for a genuinely indefinite live stream, that promise never resolves.

The fix

Only short-circuit to true when Accept-Ranges: bytes is actually confirmed on the HEAD response. Any other outcome (HEAD ok-but-inconclusive, non-ok, or thrown) now falls through to the Range GET probe, matching the function's own documented behavior. Minimal diff, no behavior change for the cases that were already working correctly.

Testing

  • Two new Jest cases: one confirming the fallback now runs and correctly detects range support via the Range GET when HEAD is inconclusive, one confirming the genuinely-no-ranges case (neither HEAD nor Range GET confirm support) still correctly falls back to a full download. Full suite: 81/81 passing.
  • typecheck / lint:js: clean.
  • Verified against the real repro, before and after this change: loading https://ice1.somafm.com/groovesalad-128-mp3 through loadRemoteHttpSource() (FFmpeg enabled) never resolved before this fix; it resolves in <1s after.

What this doesn't fix

The FFmpeg-disabled path skips byte-range detection entirely by design (if (!isFfmpegEnabled() || forceDownload) return downloadRemoteHttpSource(...)), so it still hangs on the same URL — that's unrelated to this bug and not something this PR claims to address. For servers that genuinely don't support ranges at all (not just omit the header), there's still no way for a caller to say "this is a live stream, don't try to buffer it" up front. I'd like to follow up with that as a separate PR (an explicit hint on AudioURISource) once this one's settled, rather than combine the two.

…clusive HEAD

detectHttpByteRanges()'s docstring says it does 'HEAD, then Range probe', but
the code only tried the Range GET when the HEAD request itself threw. Any
HEAD response that merely succeeded without an Accept-Ranges: bytes header
was treated as decisive proof of no range support, even though the intended
fallback probe was never attempted.

This misdetects real servers: confirmed against a live public Icecast stream
(SomaFM) that returns 200 OK on HEAD with no Accept-Ranges header, but
answers a Range GET with a genuine 206 Partial Content. Sending that case to
detectHttpByteRanges's fallback (rather than returning false immediately)
lets loadRemoteHttpSource correctly hand the URL to native code instead of
attempting to buffer an indefinite live stream into memory via
response.arrayBuffer(), which never resolves.

Fix: only short-circuit to true when Accept-Ranges: bytes is confirmed; any
other HEAD outcome (ok-but-inconclusive, non-ok, or thrown) now falls
through to the Range GET probe, matching the function's own documented
behavior.

Verified against the real repro: before this change, loading
https://ice1.somafm.com/groovesalad-128-mp3 through loadRemoteHttpSource()
(FFmpeg enabled) never resolved; after, it resolves in <1s. The FFmpeg-
disabled path (which skips byte-range detection by design) still hangs on
the same URL — that's a separate, deeper gap tracked for a follow-up PR
(an explicit stream/type hint on AudioURISource), not something this fix
is meant to address.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

WPT non-regression comparison

PASS — no regressions · 0 improved section(s) · overall 2632 → 2632 (0)

Unchanged sections (28)
Spec section Base pass Head pass Delta
Processing model 0 0 0
Other 52 52 0
AnalyserNode 138 138 0
AudioBuffer 140 140 0
AudioBufferSourceNode 208 208 0
AudioContext 58 58 0
AudioNode 261 261 0
AudioParam 599 599 0
BiquadFilterNode 275 275 0
ChannelMergerNode 30 30 0
ChannelSplitterNode 7 7 0
ConstantSourceNode 59 59 0
ConvolverNode 170 170 0
DelayNode 104 104 0
DestinationNode 0 0 0
DynamicsCompressorNode 4 4 0
GainNode 15 15 0
IIRFilterNode 87 87 0
MediaElementAudioSourceNode 0 0 0
MediaStreamAudioDestinationNode 1 1 0
MediaStreamAudioSourceNode 0 0 0
OfflineAudioContext 35 35 0
OscillatorNode 104 104 0
PannerNode 75 75 0
PeriodicWave 33 33 0
ScriptProcessorNode 0 0 0
StereoPannerNode 102 102 0
WaveShaperNode 75 75 0

Baseline: d81131a254460714ea4d083e14f6b45f2845b527 · Candidate: 7f05988274b7276638af389ece1d2a270dc64ff3

Workflow run · this comment is updated on every push.

@closetcaiman closetcaiman added the fix Code changes specifically addressing and resolving a bug label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Code changes specifically addressing and resolving a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants