fix(cover): await bounded cover fetch on cold-cache play

Prefetch alone can't cover every first play (any surface, feeds outside
the FeedPage focus window, quick plays) — the live session showed
cover-art-files empty with zero albumart tracks on a cold-cache play.
play() now serves the disk cache synchronously and, on a miss, awaits the
single-flight fetch with a 1.2s cap (covers fetch in ~300ms typically);
past the cap it plays bare and warms the cache. Verified: cold-cache play
-> cover present at load -> albumart track.
This commit is contained in:
2026-08-12 10:44:20 -04:00
parent bd7d988741
commit ed75c2fff7

View File

@@ -16,7 +16,6 @@ import { onCleanup } from "solid-js";
import {
cachedCoverPath,
fetchCoverArt,
prefetchCoverArt,
} from "../utils/cover-art";
import {
createAudioBackend,
@@ -316,13 +315,21 @@ async function play(episode: Episode): Promise<void> {
const feedStore = useFeedStore();
const feed = feedStore.feeds().find((f) => f.podcast.id === episode.podcastId);
const podcastTitle = feed?.customName || feed?.podcast.title || "";
// Cover art must NEVER gate playback (it was a curl subprocess blocking
// play() by up to 8s). Serve the disk-cached file synchronously when it
// exists; on a miss, play bare and warm the cache for the next play —
// runtime video-add can't become an albumart track, so late art only
// works via the load-time cover-art-files property.
// Cover art only applies at file LOAD (the runtime video-add fallback
// never becomes an albumart track), so a cold-cache play must wait for
// the fetch or play artless. Serve the disk cache synchronously; on a
// miss, await the single-flight fetch with a 1.2s cap (covers fetch in
// ~300ms typically) — past the cap, play bare and let the fetch warm
// the cache for next time.
const coverUrl = feed?.podcast.coverUrl;
const coverArtPath = coverUrl ? cachedCoverPath(coverUrl) : null;
let coverArtPath = coverUrl ? cachedCoverPath(coverUrl) : null;
if (coverUrl && !coverArtPath) {
const path = await Promise.race([
fetchCoverArt(coverUrl),
new Promise<null>((resolve) => setTimeout(() => resolve(null), 1200)),
]);
if (path) coverArtPath = path;
}
// Resume from saved progress if available and not completed
const savedProgress = progressStore.get(episode.id);
@@ -339,10 +346,6 @@ async function play(episode: Episode): Promise<void> {
coverArtPath: coverArtPath ?? undefined,
});
if (coverUrl && !coverArtPath) {
prefetchCoverArt(coverUrl);
}
setCurrentEpisode(episode);
setIsPlaying(true);
setPosition(startPos);