Bug Description
The Deezer preview proxy added for #173 forwards Deezer's Accept-Ranges: bytes header to the client but never reads req.headers.range. Every request is answered 200 with the full body, even when the client asked for a byte range.
WebKit — Safari on macOS and all iOS browsers — requires 206 Partial Content in response to its Range request for <audio>. Receiving 200 instead, it abandons playback silently: no console error, no failed request, no visible state change. Chrome and Firefox tolerate the same response and play normally.
Net effect: artist previews are completely non-functional on iPhone/iPad and work fine on desktop Chrome, which makes it read as a device problem rather than a server one.
This looks like a regression from the #173 fix — the same-origin proxy pipes the body correctly but does not implement the range semantics it advertises.
Affected code: backend/src/routes/artists.ts, the /preview/:artistName/:trackTitle/stream handler. In the compiled dist/routes/artists.js shipped in the image, the only two lines in the whole file that mention ranges are these:
if (upstream.headers["accept-ranges"]) {
res.setHeader("Accept-Ranges", upstream.headers["accept-ranges"]);
}
There is no req.headers.range read, no 206, and no Content-Range anywhere in the file.
Measurements. Deezer's CDN behaves correctly and supports ranges — only this proxy hop does not. Run from inside the container against a fresh preview URL:
| request |
Deezer CDN |
kima v1.9.0 proxy |
| no Range |
200, audio/mpeg, 479,827 bytes |
200, full body |
Range: bytes=0-1 |
206, content-range: bytes 0-1/479827 |
200, full body |
Range: bytes=0-1023 |
206, content-range: bytes 0-1023/479827 |
200, full body |
bytes=0-1 is the probe WebKit opens media with, so it is the first request that fails.
Suggested fix. Forward the client's Range upstream and relay the partial response — Deezer already does the hard part:
const clientRange = req.headers.range;
const upstream = await axios.get(previewUrl, {
responseType: "stream",
timeout: 10000,
headers: clientRange ? { Range: clientRange } : undefined,
});
res.setHeader("Content-Type", upstream.headers["content-type"] || "audio/mpeg");
if (upstream.headers["content-length"]) {
res.setHeader("Content-Length", upstream.headers["content-length"]);
}
if (upstream.headers["accept-ranges"]) {
res.setHeader("Accept-Ranges", upstream.headers["accept-ranges"]);
}
// Only claim 206 when upstream actually served a partial body — if it ignored
// the Range, fall through as 200 rather than lying in the other direction.
if (upstream.status === 206 && upstream.headers["content-range"]) {
res.setHeader("Content-Range", upstream.headers["content-range"]);
res.status(206);
}
Verified with this change in place, against the live CDN:
no Range -> 200 | content-range: (none) | 479827 bytes
Range: bytes=0-1 -> 206 | content-range: bytes 0-1/479827 | 2 bytes
Range: bytes=0-1023 -> 206 | content-range: bytes 0-1023/479827 | 1024 bytes
axios treats 206 as success under its default validateStatus, so no other change is needed. I am running this patch locally and previews now play on iOS/Safari.
Happy to open a PR if useful.
Steps to Reproduce
- Open Kima on an iPhone or iPad (any browser — all are WebKit), or Safari on macOS.
- Go to an artist's page and tap a Similar Artist to open an artist not in the library.
- In the Popular list, tap a track to play its Deezer preview.
Expected Behavior
The 30-second Deezer preview plays.
Actual Behavior
Nothing plays. No error is surfaced anywhere — no toast, no console error, no failed network request. The same track previews correctly in Chrome on desktop.
Kima Version
v1.9.0
Deployment Method
Docker (standalone)
Environment Details
- Image:
chevron7locked/kima:v1.9.0
- Client: iOS 18 / Safari (reproduced); also affects Safari on macOS
- Working for comparison: Chrome on desktop
- Behind a reverse proxy, TLS terminated upstream; no auth layer in front of Kima
Additional Notes
Worth auditing any other route that proxies media the same way — the underlying mistake is "forward an upstream capability header without implementing the capability", and it is invisible on Chromium.
Same silent-failure shape as #173, just a different browser engine: strict clients reject, permissive clients mask the problem.
Bug Description
The Deezer preview proxy added for #173 forwards Deezer's
Accept-Ranges: bytesheader to the client but never readsreq.headers.range. Every request is answered200with the full body, even when the client asked for a byte range.WebKit — Safari on macOS and all iOS browsers — requires
206 Partial Contentin response to its Range request for<audio>. Receiving200instead, it abandons playback silently: no console error, no failed request, no visible state change. Chrome and Firefox tolerate the same response and play normally.Net effect: artist previews are completely non-functional on iPhone/iPad and work fine on desktop Chrome, which makes it read as a device problem rather than a server one.
This looks like a regression from the #173 fix — the same-origin proxy pipes the body correctly but does not implement the range semantics it advertises.
Affected code:
backend/src/routes/artists.ts, the/preview/:artistName/:trackTitle/streamhandler. In the compileddist/routes/artists.jsshipped in the image, the only two lines in the whole file that mention ranges are these:There is no
req.headers.rangeread, no206, and noContent-Rangeanywhere in the file.Measurements. Deezer's CDN behaves correctly and supports ranges — only this proxy hop does not. Run from inside the container against a fresh preview URL:
200,audio/mpeg, 479,827 bytes200, full bodyRange: bytes=0-1206,content-range: bytes 0-1/479827200, full bodyRange: bytes=0-1023206,content-range: bytes 0-1023/479827200, full bodybytes=0-1is the probe WebKit opens media with, so it is the first request that fails.Suggested fix. Forward the client's Range upstream and relay the partial response — Deezer already does the hard part:
Verified with this change in place, against the live CDN:
axiostreats206as success under its defaultvalidateStatus, so no other change is needed. I am running this patch locally and previews now play on iOS/Safari.Happy to open a PR if useful.
Steps to Reproduce
Expected Behavior
The 30-second Deezer preview plays.
Actual Behavior
Nothing plays. No error is surfaced anywhere — no toast, no console error, no failed network request. The same track previews correctly in Chrome on desktop.
Kima Version
v1.9.0
Deployment Method
Docker (standalone)
Environment Details
chevron7locked/kima:v1.9.0Additional Notes
Worth auditing any other route that proxies media the same way — the underlying mistake is "forward an upstream capability header without implementing the capability", and it is invisible on Chromium.
Same silent-failure shape as #173, just a different browser engine: strict clients reject, permissive clients mask the problem.