fix(cover+downloads): channel art parsing, episode image fallback, local playback

The Fifth Column (and any feed added by URL) had NO coverUrl — the RSS
parser never captured channel artwork, so Now Playing had nothing to show.
- rss-parser: parseChannelCoverUrl (<itunes:image href> / RSS2 <image><url>);
  parseRSSFeed sets it on the Podcast.
- feed store: fetchEpisodes returns the channel cover; subscribe + both
  refresh paths backfill coverUrl when missing (no second fetch).
- useAudio + CLI --play: cover resolves feed.podcast.coverUrl ?? episode.imageUrl,
  so episodes without channel art still get their own image.
- useAudio play/load/switchBackend: prefer the downloaded file
  (getDownloadedFilePath) over the stream URL — downloaded episodes now
  play from disk.
Verified: Fifth Column episode, cold cache -> cover-art-files set at load
-> albumart track present.
This commit is contained in:
2026-08-12 10:52:54 -04:00
parent 0b4a551744
commit 13664c3cec
4 changed files with 72 additions and 23 deletions

View File

@@ -82,6 +82,16 @@ export const getRSSItems = (xml: string): string[] => {
return channel.match(/<item[\s\S]*?<\/item>/gi) ?? []
}
/** Channel-level artwork: `<itunes:image href>` (podcasts) or RSS 2.0
* `<image><url>`. Exported so the feed store can backfill a feed's
* coverUrl on refresh without re-deriving the channel block. */
export const parseChannelCoverUrl = (channel: string): string | undefined => {
const itunesHref = getAttr(channel, "itunes:image", "href")
if (itunesHref) return itunesHref
const url = getTagValue(channel, "image").match(/<url>([\s\S]*?)<\/url>/i)?.[1]
return url?.trim() || undefined
}
/** Parse a single `<item>` into an Episode. Exported so the feed store can
* parse large feeds in bounded chunks (yielding to the event loop between
* chunks) instead of one synchronous block. */
@@ -158,6 +168,7 @@ export const parseRSSFeed = (xml: string, feedUrl: string): Podcast & { episodes
feedUrl,
lastUpdated,
isSubscribed: true,
coverUrl: parseChannelCoverUrl(channel),
episodes,
}
}