Skip to content

Commit 94f6ecf

Browse files
committed
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.
1 parent 9aa6e40 commit 94f6ecf

File tree

3 files changed

+61
-18
lines changed

3 files changed

+61
-18
lines changed

src/backend/metadata/tmdb.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ export function TMDBMediaToMediaItemType(
5555
throw new Error("unsupported type");
5656
}
5757

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+
5876
export function formatTMDBMeta(
5977
media: TMDBMediaResult,
6078
season?: TMDBSeasonMetaResult,
@@ -88,14 +106,7 @@ export function formatTMDBMeta(
88106
title: season.title,
89107
episodes: season.episodes
90108
.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),
99110
}
100111
: (undefined as any),
101112
};

src/components/player/atoms/Episodes.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
44
import { useAsync } from "react-use";
55

66
import { getMetaFromId } from "@/backend/metadata/getmeta";
7+
import { formatTMDBEpisode, getEpisodes } from "@/backend/metadata/tmdb";
78
import { MWMediaType, MWSeasonMeta } from "@/backend/metadata/types/mw";
89
import { Icon, Icons } from "@/components/Icon";
910
import { ProgressRing } from "@/components/layout/ProgressRing";
@@ -20,6 +21,7 @@ import { PlayerMeta } from "@/stores/player/slices/source";
2021
import { usePlayerStore } from "@/stores/player/store";
2122
import { usePreferencesStore } from "@/stores/preferences";
2223
import { useProgressStore } from "@/stores/progress";
24+
import { concurrentMap } from "@/utils/async";
2325
import { scrollToElement } from "@/utils/scroll";
2426

2527
import { hasAired } from "../utils/aired";
@@ -594,23 +596,21 @@ export function EpisodesView({
594596
if (selectedSeason === "favorites" && meta?.tmdbId && seasons) {
595597
setAllSeasonsLoading(true);
596598
const loadAllSeasons = async () => {
597-
const seasonPromises = seasons.map(async (season) => {
599+
const results = await concurrentMap(seasons, 5, async (season) => {
598600
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+
};
607608
} catch (error) {
608609
console.error(`Failed to load season ${season.id}:`, error);
609610
return null;
610611
}
611612
});
612613

613-
const results = await Promise.all(seasonPromises);
614614
setAllSeasonsData(results.filter(Boolean));
615615
setAllSeasonsLoading(false);
616616
};

src/utils/async.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
}

0 commit comments

Comments
 (0)