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:
@@ -105,7 +105,10 @@ export const MusicBrowser = () => {
|
||||
};
|
||||
|
||||
const crumbs = navFolder ? navFolder.slice(MUSIC_ROOT.length + 1).split('/') : [];
|
||||
const coverFor = (childRel: string) => (manifest[childRel]?.cover ? coverUrl(childRel, token) : null);
|
||||
// The manifest already carries each album's version, so passing it costs nothing and turns every
|
||||
// repeat visit's cover request into no request at all.
|
||||
const coverFor = (childRel: string) =>
|
||||
manifest[childRel]?.cover ? coverUrl(childRel, token, manifest[childRel]?.v) : null;
|
||||
const shownLibraries = libraries.filter((l) => fuzzyMatch(query, l));
|
||||
const shownFolders = folders.filter((f) => fuzzyMatch(query, f));
|
||||
|
||||
|
||||
+2
-2
@@ -216,7 +216,7 @@ export const MusicDetail = () => {
|
||||
>
|
||||
<div className="aspect-square w-full overflow-hidden rounded-md bg-muted">
|
||||
<img
|
||||
src={coverUrl(r, token)}
|
||||
src={coverUrl(r, token, manifest[r]?.v)}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
className="h-full w-full object-cover"
|
||||
@@ -282,7 +282,7 @@ export const MusicDetail = () => {
|
||||
<div className="flex items-end gap-5">
|
||||
<div className="h-40 w-40 shrink-0 overflow-hidden rounded-lg bg-muted shadow-lg">
|
||||
<img
|
||||
src={coverUrl(rel, token)}
|
||||
src={coverUrl(rel, token, manifest[rel]?.v)}
|
||||
alt=""
|
||||
className="h-full w-full object-cover"
|
||||
onError={(e) => {
|
||||
|
||||
+16
-2
@@ -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) : '');
|
||||
|
||||
Reference in New Issue
Block a user