perf(web-player): serve YouTube artwork through a caching proxy

The browser used to pull every cover straight from Google, so a search grid
plus infinite scroll fired dozens of cross-origin image requests and started
coming back as 429. Covers now go through /api/youtube/thumbnail, which
resizes the upstream URL, caches the bytes in memory, collapses duplicate
in-flight fetches, caps the concurrency against Google and backs off when it
answers 429. Responses are immutable for a month and revalidate with an ETag,
and serving them same-origin also lets the accent extractor read the pixels.
This commit is contained in:
2026-07-20 17:36:41 +02:00
parent dd3150e3d4
commit 2691c40da8
6 changed files with 262 additions and 3 deletions
+6 -2
View File
@@ -1,6 +1,7 @@
import { browser } from '$app/environment';
import { HUES, accentForHue, gradientForHue, hueFor } from '$lib/theme/color';
import { extractAccent, type Accent } from '$lib/theme/palette';
import { thumbnailSrc } from '$lib/thumbnails';
export type TrackSourceKind = 'local' | 'youtube';
@@ -190,7 +191,8 @@ class PlayerState {
navigator.mediaSession.metadata = null;
return;
}
const artwork = track.coverUrl ?? `/api/tracks/${track.id}/cover?size=small`;
const artwork =
thumbnailSrc(track.coverUrl, 'medium') ?? `/api/tracks/${track.id}/cover?size=small`;
navigator.mediaSession.metadata = new MediaMetadata({
title: track.title,
artist: track.artist,
@@ -242,7 +244,9 @@ class PlayerState {
this.gradientColor = cached.gradient;
return;
}
const result = await extractAccent(coverUrl ?? `/api/tracks/${id}/cover?size=small`);
const result = await extractAccent(
thumbnailSrc(coverUrl, 'small') ?? `/api/tracks/${id}/cover?size=small`
);
if (this.currentId !== id) return;
if (result) {
this.#accentCache.set(key, result);