perf(plex): request 220x220 transcoded artwork for thumbnail contexts
Plex artwork URLs were going directly to `/library/metadata/<id>/thumb/<ts>` which serves the **full resolution** image — typically 1000x1000 or larger for album covers. The browser then had to decode the full JPEG and downscale to the displayed 220x220, blowing through both decode budget (full-res JPEG decode per visible card) and memory budget (~4MB raw raster per card × N visible = hundreds of MB of image cache). For a Plex-dominant library (the user's case), this was a hidden cost factor on every thumbnail grid surface. Fix: `buildPlexArtworkUrl` now takes an optional `size` parameter. When provided, the path is wrapped in `/photo/:/transcode` so the Plex server downscales server-side and the browser receives ~30KB JPEGs at the right size instead of 1MB+ originals. When `size` is omitted, the original full-res URL is returned — preserved for `getFullArtworkUrl` (AlbumDetailView's big artwork display). Updated callsites pass `220`: - LocalLibraryView `getArtworkUrl` (grid + list thumbs) - MixtapeCollectionDetailView resolve path - PlaylistDetailView track artwork Per the 2026-05-12 user call: 220 over 440 — fluidity wins over HiDPI sharpness on this surface. cc
This commit is contained in:
3 files changed
+36
-6
No files matched your search
@@ -3025,11 +3025,22 @@
|
||||
return enabled && baseUrl.length > 0 && token.length > 0;
|
||||
}
|
||||
|
||||
function buildPlexArtworkUrl(path: string): string {
|
||||
/**
|
||||
* Build a Plex artwork URL. When `size` is provided, wraps the path
|
||||
* in `/photo/:/transcode` so the Plex server returns a resized image
|
||||
* (server-side downscale) — critical for thumbnail contexts where
|
||||
* the original artwork is typically 1000x1000+ and blowing through
|
||||
* decode + memory budget otherwise. When `size` is omitted, returns
|
||||
* the original full-resolution path (used by AlbumDetailView).
|
||||
*/
|
||||
function buildPlexArtworkUrl(path: string, size?: number): string {
|
||||
const baseUrl = getUserItem('qbz-plex-poc-base-url') || '';
|
||||
const token = getUserItem('qbz-plex-poc-token') || '';
|
||||
if (!baseUrl || !token) return path;
|
||||
const base = baseUrl.replace(/\/+$/, '');
|
||||
if (size && size > 0) {
|
||||
return `${base}/photo/:/transcode?url=${encodeURIComponent(path)}&width=${size}&height=${size}&minSize=1&X-Plex-Token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
const separator = path.includes('?') ? '&' : '?';
|
||||
return `${base}${path}${separator}X-Plex-Token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
@@ -4608,7 +4619,7 @@
|
||||
function getArtworkUrl(path?: string): string {
|
||||
if (!path) return '';
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
if (path.startsWith('/library/')) return buildPlexArtworkUrl(path);
|
||||
if (path.startsWith('/library/')) return buildPlexArtworkUrl(path, 220);
|
||||
|
||||
// For grid/list views, prefer thumbnails
|
||||
const cachedThumb = thumbnailUrlCache.get(path);
|
||||
|
||||
@@ -996,11 +996,21 @@
|
||||
|
||||
let resolvedById = $state<Record<string, ResolvedItem>>({});
|
||||
|
||||
function buildPlexArtworkUrl(path: string): string | null {
|
||||
/**
|
||||
* Build a Plex artwork URL. When `size` is provided, wraps the path
|
||||
* in `/photo/:/transcode` so the Plex server returns a resized image
|
||||
* (server-side downscale). Without `size`, returns the original
|
||||
* full-res path. Mixtape items render at thumbnail-sized rows so
|
||||
* 220px is plenty.
|
||||
*/
|
||||
function buildPlexArtworkUrl(path: string, size?: number): string | null {
|
||||
const baseUrl = (getUserItem('qbz-plex-poc-base-url') || '').trim();
|
||||
const token = (getUserItem('qbz-plex-poc-token') || '').trim();
|
||||
if (!baseUrl || !token) return null;
|
||||
const base = baseUrl.replace(/\/+$/, '');
|
||||
if (size && size > 0) {
|
||||
return `${base}/photo/:/transcode?url=${encodeURIComponent(path)}&width=${size}&height=${size}&minSize=1&X-Plex-Token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
const separator = path.includes('?') ? '&' : '?';
|
||||
return `${base}${path}${separator}X-Plex-Token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
@@ -1070,7 +1080,7 @@
|
||||
next[key] = {
|
||||
kind: 'plex',
|
||||
artworkUrl: plexHit.artworkPath
|
||||
? buildPlexArtworkUrl(plexHit.artworkPath)
|
||||
? buildPlexArtworkUrl(plexHit.artworkPath, 220)
|
||||
: null,
|
||||
bitDepth: plexHit.bitDepth ?? null,
|
||||
sampleRateKhz: plexHit.sampleRate ? plexHit.sampleRate / 1000 : null,
|
||||
|
||||
@@ -1498,12 +1498,21 @@
|
||||
// is a library-relative URI like /library/metadata/123/thumb/…,
|
||||
// which only resolves when combined with the configured base URL +
|
||||
// X-Plex-Token. Matches LocalLibraryView's buildPlexArtworkUrl.
|
||||
function buildPlexArtworkUrl(path: string): string {
|
||||
/**
|
||||
* Build a Plex artwork URL. When `size` is provided, wraps the path
|
||||
* in `/photo/:/transcode` so Plex returns a server-side resized
|
||||
* image (critical for thumbnail rows; the original is typically
|
||||
* 1000x1000+). Without `size`, returns the original full-res URL.
|
||||
*/
|
||||
function buildPlexArtworkUrl(path: string, size?: number): string {
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) return path;
|
||||
const baseUrl = getUserItem('qbz-plex-poc-base-url') || '';
|
||||
const token = getUserItem('qbz-plex-poc-token') || '';
|
||||
if (!baseUrl || !token) return path;
|
||||
const base = baseUrl.replace(/\/+$/, '');
|
||||
if (size && size > 0) {
|
||||
return `${base}/photo/:/transcode?url=${encodeURIComponent(path)}&width=${size}&height=${size}&minSize=1&X-Plex-Token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
const separator = path.includes('?') ? '&' : '?';
|
||||
return `${base}${path}${separator}X-Plex-Token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
@@ -1527,7 +1536,7 @@
|
||||
title: track.title,
|
||||
artist: track.artist,
|
||||
album: track.album,
|
||||
albumArt: track.artwork_path ? buildPlexArtworkUrl(track.artwork_path) : undefined,
|
||||
albumArt: track.artwork_path ? buildPlexArtworkUrl(track.artwork_path, 220) : undefined,
|
||||
duration: formatDuration(track.duration_secs),
|
||||
durationSeconds: track.duration_secs,
|
||||
hires: (track.bit_depth && track.bit_depth >= 24) || track.sample_rate > 48000,
|
||||
|
||||
Reference in new issue
Block a user