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