feat(library): focused music/TV/movie landing screens + self-draining download queue
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 9m49s
Traceability Validation / Check Requirement Traces (pull_request) Successful in 25s
🏗️ Build and Test JellyTau / Build Android APK (pull_request) Successful in 22m33s

Library screens:
- Add dedicated music, TV, and movie landing pages (hero banner +
  horizontal carousels) backed by new music/tv/movies stores.
- Route tvshows libraries to /library/tv; surface rediscover ("haven't
  listened to in a while") albums via a new repository method across
  online/offline/hybrid repos plus the repository_get_rediscover_albums
  command.
- Add an A-Z jump bar for long alphabetically-sorted lists, with grid
  index anchors in LibraryGrid/LibraryListView/TrackList.
- Filter the "Podcasts" folder out of music library queries.

Downloads:
- Add a backend queue pump: enqueue_download / enqueue_video_downloads
  persist the resolved stream URL + target dir on each row (migration
  017), and the pump starts up to max_concurrent and drains the rest
  automatically as slots free, instead of the frontend silently dropping
  items past the concurrency limit. Album/series/season buttons now
  enqueue rather than calling start_download directly.

Other fixes:
- Hybrid search now returns instant cache results and pushes the merged
  cache+server union via a request-id-tagged search-event, so superseded
  queries can't clobber fresher results.
- URL-encode SearchTerm / genres / item types in online repo requests.
- Android: pause on audio-becoming-noisy (headphone/BT disconnect).
This commit is contained in:
2026-06-24 20:44:17 +02:00
parent dcf08f30bc
commit 17a35573a0
33 changed files with 2045 additions and 188 deletions
+30
View File
@@ -0,0 +1,30 @@
// HACK: hide "Podcasts" from the music library.
//
// The user stores podcasts inside the music library under a folder/album named
// "Podcasts", so they leak into album/artist/track/playlist queries. Jellyfin's
// item queries here don't give us a clean server-side exclusion for that folder,
// so we filter client-side by name. This is intentionally a blunt instrument:
// anything whose own name, album, or (album) artist is literally "Podcasts" is
// dropped. If the folder is ever renamed, update PODCAST_FOLDER_NAME.
import type { MediaItem } from "$lib/api/types";
const PODCAST_FOLDER_NAME = "podcasts";
function isPodcastName(value: string | null | undefined): boolean {
return value?.trim().toLowerCase() === PODCAST_FOLDER_NAME;
}
/** True when an item belongs to the "Podcasts" folder/album and should be hidden. */
export function isPodcastItem(item: MediaItem): boolean {
return (
isPodcastName(item.name) ||
isPodcastName(item.albumName) ||
isPodcastName(item.albumArtist) ||
(item.artists?.some(isPodcastName) ?? false)
);
}
/** Remove "Podcasts" entries from a list of music items. */
export function excludePodcasts(items: MediaItem[]): MediaItem[] {
return items.filter((item) => !isPodcastItem(item));
}