revert the /stream etag, cache-control and 304

playback did not come back when x-audio-duration did, so the duration header
was not the only thing i broke. the range and whole-file branches are
byte-identical to the version that worked, which leaves these three as the
only remaining difference on /stream.

a cache that keeps a validator after evicting the body sends if-none-match,
gets a 304 and has no audio. plausible, unproven, and not worth another round
trip through someone's phone to find out — the etag was an optimisation and
playback is not.

/stream is now byte-identical to the last version known to play, verified by
running both implementations side by side over no-range, closed, open and
invalid ranges and diffing status, every header and body length.

if-none-match is still accepted and ignored. re-introducing this needs a
capture of what the player actually sends.
This commit is contained in:
2026-08-19 15:43:11 +00:00
parent b430a702b4
commit 1170e411ac
2 changed files with 30 additions and 25 deletions
+10 -5
View File
@@ -30,8 +30,8 @@ Byte-range streaming so the player can **seek without downloading the whole file
| Case | Status | Headers |
| --------------------- | ------ | --------------------------------------------------------------------------------------------- |
| No `Range` | `200` | `Content-Type`, `Content-Length`, `Accept-Ranges: bytes`, `ETag`, `Cache-Control` |
| With `Range: bytes=…` | `206` | `Content-Range`, `Content-Length`, `Accept-Ranges: bytes`, `Content-Type`, `ETag` |
| No `Range` | `200` | `Content-Type`, `Content-Length`, `Accept-Ranges: bytes`, `X-Audio-Duration` |
| With `Range: bytes=…` | `206` | `Content-Range`, `Content-Length`, `Accept-Ranges: bytes`, `Content-Type`, `X-Audio-Duration` |
- **`X-Audio-Duration` still ships, and no longer costs an `ffprobe`** (2026-08-19). It used to spawn
one per file on the first request — measured at 50ms warm, 315ms cold, paid before the first byte.
@@ -46,9 +46,14 @@ Byte-range streaming so the player can **seek without downloading the whole file
`meta.json` is the fast path, not the only one: `/stream` serves any home-relative path and does not
require indexing, so an unindexed album falls back to ffprobe (cached per file) rather than dropping
the header. Absent only if ffprobe is missing or the file is unreadable — as before.
- **`ETag`** is `mtime-size` in base36. A whole-file request with a matching `If-None-Match` answers
`304`; a Range request never does, because a 304 would answer a question about part of a file with a
statement about all of it.
- **`/stream` has no `ETag` and no `Cache-Control`.** One was added on 2026-08-19 and removed the same
day: the mobile player stopped playing, and after `X-Audio-Duration` was ruled out these headers were
the only remaining difference from the version that worked. A cache that keeps a validator after
evicting the body would send `If-None-Match`, receive `304`, and have no audio.
`If-None-Match` on `/stream` is therefore ignored — never `304`, always bytes. `/cover`, `/meta`,
`/lyrics` and `/poster` DO have validators and are unaffected.
- Errors: `400` invalid/missing path · `404` not found · `416` bad range.
```