restore the three streaming improvements

reverts 0e08aef, b430a70 and 1170e41 — stream-audio.ts and MUSIC_API.md are
now byte-identical to 2a70bc1.

all three were unwound over two days chasing a mobile playback failure that
turned out to have nothing to do with this plugin. the reasoning that undid
them was elimination, not diagnosis: the duration header did not fix it, so
the etag "must" be the cause. the option never considered was that none of it
was mine, which is the one that was true.

nothing here was measured wrong. removing the per-file ffprobe from the path
before the first byte is the speedup, and it is confirmed in real use.

what the mobile client should know, unchanged from MUSIC_API.md: x-audio-duration
is gone, read durationSec from /meta instead — it arrives with the queue,
before the first stream request.
This commit is contained in:
2026-08-19 17:11:26 +00:00
parent 1170e411ac
commit efc9581889
2 changed files with 37 additions and 134 deletions
+10 -23
View File
@@ -30,30 +30,17 @@ 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`, `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.
It is now read from the album's `meta.json` and cached per album: 1.5ms cold, 0.1ms warm, and the
same number, because the indexer got it from ffprobe too.
It was briefly removed outright, on the reasoning that nothing in the web read it and `/meta` already
carried `durationSec`. The mobile player stopped playing, so it is load-bearing for at least one
client and stays in the contract. New clients should still prefer `durationSec` from `/meta` — it
arrives with the queue, before the first stream request.
`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.
- **`/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.
| 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` |
- **`X-Audio-Duration` is GONE** (2026-08-19). It cost an `ffprobe` per file on the first request —
measured at 50ms warm, 315ms cold, paid before the first byte — to report a number the indexer had
already written into `meta.json`. **Read `durationSec` from `/meta` instead**, which every client
already fetches to build its queue.
- **`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.
VBR MP3s. No need to scan the file.
- Errors: `400` invalid/missing path · `404` not found · `416` bad range.
```