pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/

Speed up episodes carousel loading by making it async

Maps an array to a promise that resolves with an array of results, limiting the number of concurrent operations.

Pas 94f6ecf0 9aa6e408

+61 -18
+19 -8
src/backend/metadata/tmdb.ts
··· 55 55 throw new Error("unsupported type"); 56 56 } 57 57 58 + export function formatTMDBEpisode(v: TMDBEpisodeShort): { 59 + id: string; 60 + number: number; 61 + title: string; 62 + air_date: string; 63 + still_path: string | null; 64 + overview: string; 65 + } { 66 + return { 67 + id: v.id.toString(), 68 + number: v.episode_number, 69 + title: v.title, 70 + air_date: v.air_date, 71 + still_path: v.still_path, 72 + overview: v.overview, 73 + }; 74 + } 75 + 58 76 export function formatTMDBMeta( 59 77 media: TMDBMediaResult, 60 78 season?: TMDBSeasonMetaResult, ··· 88 106 title: season.title, 89 107 episodes: season.episodes 90 108 .sort((a, b) => a.episode_number - b.episode_number) 91 - .map((v) => ({ 92 - id: v.id.toString(), 93 - number: v.episode_number, 94 - title: v.title, 95 - air_date: v.air_date, 96 - still_path: v.still_path, 97 - overview: v.overview, 98 - })), 109 + .map(formatTMDBEpisode), 99 110 } 100 111 : (undefined as any), 101 112 };
+10 -10
src/components/player/atoms/Episodes.tsx
··· 4 4 import { useAsync } from "react-use"; 5 5 6 6 import { getMetaFromId } from "@/backend/metadata/getmeta"; 7 + import { formatTMDBEpisode, getEpisodes } from "@/backend/metadata/tmdb"; 7 8 import { MWMediaType, MWSeasonMeta } from "@/backend/metadata/types/mw"; 8 9 import { Icon, Icons } from "@/components/Icon"; 9 10 import { ProgressRing } from "@/components/layout/ProgressRing"; ··· 20 21 import { usePlayerStore } from "@/stores/player/store"; 21 22 import { usePreferencesStore } from "@/stores/preferences"; 22 23 import { useProgressStore } from "@/stores/progress"; 24 + import { concurrentMap } from "@/utils/async"; 23 25 import { scrollToElement } from "@/utils/scroll"; 24 26 25 27 import { hasAired } from "../utils/aired"; ··· 594 596 if (selectedSeason === "favorites" && meta?.tmdbId && seasons) { 595 597 setAllSeasonsLoading(true); 596 598 const loadAllSeasons = async () => { 597 - const seasonPromises = seasons.map(async (season) => { 599 + const results = await concurrentMap(seasons, 5, async (season) => { 598 600 try { 599 - const data = await getMetaFromId( 600 - MWMediaType.SERIES, 601 - meta.tmdbId, 602 - season.id, 603 - ); 604 - return data?.meta.type === MWMediaType.SERIES 605 - ? data.meta.seasonData 606 - : null; 601 + const episodes = await getEpisodes(meta.tmdbId!, season.number); 602 + return { 603 + id: season.id, 604 + number: season.number, 605 + title: season.title, 606 + episodes: episodes.map(formatTMDBEpisode), 607 + }; 607 608 } catch (error) { 608 609 console.error(`Failed to load season ${season.id}:`, error); 609 610 return null; 610 611 } 611 612 }); 612 613 613 - const results = await Promise.all(seasonPromises); 614 614 setAllSeasonsData(results.filter(Boolean)); 615 615 setAllSeasonsLoading(false); 616 616 };
+32
src/utils/async.ts
··· 1 + /** 2 + * Maps an array to a promise that resolves with an array of results, 3 + * limiting the number of concurrent operations. 4 + * 5 + * @param items The array of items to map 6 + * @param concurrency The maximum number of concurrent operations 7 + * @param fn The async function to apply to each item 8 + * @returns A promise that resolves with an array of results 9 + */ 10 + export async function concurrentMap<T, R>( 11 + items: T[], 12 + concurrency: number, 13 + fn: (item: T) => Promise<R>, 14 + ): Promise<R[]> { 15 + const results: R[] = new Array(items.length); 16 + const queue = items.map((item, index) => ({ item, index })); 17 + 18 + const workers = Array.from( 19 + { length: Math.min(concurrency, items.length) }, 20 + async () => { 21 + while (queue.length > 0) { 22 + const entry = queue.shift(); 23 + if (!entry) break; 24 + const { item, index } = entry; 25 + results[index] = await fn(item); 26 + } 27 + }, 28 + ); 29 + 30 + await Promise.all(workers); 31 + return results; 32 + }