three bottlenecks in streaming and loading

Measured, not guessed.

ffprobe is gone from the stream path. It ran on the first request for each
file — 50ms warm, 315ms cold on this machine, paid before the first byte, once
per file per sidecar restart — to fill X-Audio-Duration, a header nothing in
the web read and which duplicates the durationSec the indexer already wrote
into meta.json. Removed rather than cached harder: the fastest work is the work
not done, and a second copy of a number is a second thing that can disagree.
BREAKING for any client that read that header; read /meta instead, which every
client already fetches to build its queue.

/stream gains an ETag and Cache-Control. It was the only route with no
validator at all — cover, meta, lyrics and poster have had one all along — so
every seek and every replay re-read the file over the network. mtime+size
identifies the bytes, since a re-encode replaces the file. A Range request
never answers 304: that would answer a question about part of a file with a
statement about all of it, and players handle it badly.

And `?v=` makes covers cacheable rather than merely revalidatable. `no-cache`
means "ask before reusing", so a grid of forty albums cost forty conditional
requests on every load, all answering 304. The manifest already carries each
album's version, so passing it costs the client nothing — and the server then
answers `immutable`, making a repeat visit send nothing at all.

That last one is deliberately conditional on `v` being present, which keeps it
backward compatible: a client that has not been updated gets today's behaviour
and cannot end up holding a stale cover forever.
This commit is contained in:
officer
2026-08-19 15:15:35 +00:00
parent 456336e27f
commit 2a70bc18d7
6 changed files with 95 additions and 51 deletions
+16 -2
View File
@@ -122,8 +122,22 @@ export const TYPE_ORDER = [
'Other',
];
export const coverUrl = (rel: string, token: string | null) =>
`/api/music/cover?path=${encodeURIComponent(rel)}${token ? `&token=${encodeURIComponent(token)}` : ''}`;
/**
* `version` is what makes a cover cacheable rather than merely revalidatable.
*
* Without it the browser holds the image but asks about it on every page load — a grid of forty albums
* is forty conditional requests before any art appears, and all forty answer 304. With it, the URL names
* one specific version of that artwork, the server marks it `immutable`, and a repeat visit sends
* nothing at all. When the album changes its version changes, so the URL changes, and the stale entry is
* simply never asked for again.
*
* Optional because the server only applies the aggressive caching when it is present — a caller without
* a version keeps the old behaviour rather than risking a cover frozen forever.
*/
export const coverUrl = (rel: string, token: string | null, version?: string) =>
`/api/music/cover?path=${encodeURIComponent(rel)}` +
`${token ? `&token=${encodeURIComponent(token)}` : ''}` +
`${version ? `&v=${encodeURIComponent(version)}` : ''}`;
/** Path (home-relative) → rel (relative to the Music root). */
export const toRel = (cwd: string | null) => (cwd ? cwd.slice(MUSIC_ROOT.length + 1) : '');