Skip to content

Commit

Permalink
feat(series): add native support for next episodes
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonLantukh committed Jul 6, 2023
1 parent 2d8e4be commit 1f11d6a
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 86 deletions.
20 changes: 6 additions & 14 deletions src/hooks/series/useNextEpisode.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import { useQuery } from 'react-query';

import type { EpisodeMetadata, Series } from '#types/series';
import { getNextItem } from '#src/utils/series';
import type { Series } from '#types/series';
import { SERIES_CACHE_TIME } from '#src/config';
import { getEpisodes } from '#src/services/api.service';

export const useNextEpisode = ({
series,
episodeMetadata,
episodeId,
}: {
series: Series | undefined;
episodeMetadata: EpisodeMetadata | undefined;
episodeId: string | null;
}) => {
export const useNextEpisode = ({ series, episodeId }: { series: Series | undefined; episodeId: string | null }) => {
const { isLoading, data } = useQuery(
['next-episode', series?.series_id, episodeId],
async () => {
const item = await getNextItem(series, episodeMetadata);
const item = await getEpisodes(series?.series_id, null, 1, episodeId);

return item;
return item?.episodes?.[0];
},
{ staleTime: SERIES_CACHE_TIME, cacheTime: SERIES_CACHE_TIME, enabled: !!(series?.series_id && episodeId && episodeMetadata) },
{ staleTime: SERIES_CACHE_TIME, cacheTime: SERIES_CACHE_TIME, enabled: !!(series?.series_id && episodeId) },
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const MediaSeries: ScreenComponent<PlaylistItem> = ({ data: seriesMedia }) => {
[seriesMedia, series, episodes],
);
const episodesInSeason = getEpisodesInSeason(episodeMetadata, series);
const { data: nextItem } = useNextEpisode({ series, episodeMetadata, episodeId: episode?.mediaid || firstEpisode?.mediaid });
const { data: nextItem } = useNextEpisode({ series, episodeId: episode?.mediaid || firstEpisode?.mediaid });

// Watch history
const watchHistoryArray = useWatchHistoryStore((state) => state.watchHistory);
Expand Down
11 changes: 9 additions & 2 deletions src/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,20 @@ export const getSeriesByMediaIds = async (mediaIds: string[]): Promise<{ [mediaI
* Get all episodes of the selected series (when no particular season is selected or when episodes are attached to series)
* @param {string} seriesId
*/
export const getEpisodes = async (seriesId: string | undefined, pageOffset: number, pageLimit?: number): Promise<EpisodesWithPagination> => {
export const getEpisodes = async (
seriesId: string | undefined | null,
pageOffset: number | null,
pageLimit: number | null = PAGE_LIMIT,
afterId?: string | null,
): Promise<EpisodesWithPagination> => {
if (!seriesId) {
throw new Error('Series ID is required');
} else if (pageOffset && afterId) {
throw new Error('page_offset and after_id are not allowed in the same query');
}

const pathname = `/apps/series/${seriesId}/episodes`;
const url = addQueryParams(`${import.meta.env.APP_API_BASE_URL}${pathname}`, { page_offset: pageOffset, page_limit: pageLimit || PAGE_LIMIT });
const url = addQueryParams(`${import.meta.env.APP_API_BASE_URL}${pathname}`, { page_offset: pageOffset, page_limit: pageLimit, after_id: afterId });

const response = await fetch(url);
const { episodes, page, page_limit, total }: EpisodesRes = await getDataOrThrow(response);
Expand Down
69 changes: 0 additions & 69 deletions src/utils/series.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { EpisodeMetadata, Series } from '#types/series';
import { getEpisodes, getSeasonWithEpisodes } from '#src/services/api.service';

/**
* Get an array of options for a season filter
Expand All @@ -10,74 +9,6 @@ export const getFiltersFromSeries = (series: Series | undefined): { label: strin
: [];
};

/**
* Retrieve a first episode of the next season
*/
const getNextSeasonEpisode = async (seasonNumber: number, series: Series | undefined) => {
const seasons = series?.seasons;

// The seasons is the last one in case there is only one season available or it is the same as the last season in the seasons array
const isLastSeason = seasons?.length === 1 || seasons?.[seasons?.length - 1]?.season_number === seasonNumber;

if (isLastSeason) {
return;
} else {
// Need to know the order of seasons to choose the next season number
const hasAscendingOrder = (Number(seasons?.[1]?.season_number) || 0) > (Number(seasons?.[0]?.season_number) || 0);
const nextSeasonNumber = hasAscendingOrder ? seasonNumber + 1 : seasonNumber - 1;

return (await getSeasonWithEpisodes(series?.series_id, nextSeasonNumber, 0, 1))?.episodes?.[0];
}
};

/**
* Get a next episode of the selected season / episodes list
*/
const getNextEpisode = async (seasonNumber: number, seriesId: string, pageWithEpisode: number) => {
if (seasonNumber) {
return (await getSeasonWithEpisodes(seriesId, seasonNumber, pageWithEpisode, 1))?.episodes?.[0];
} else {
return (await getEpisodes(seriesId, pageWithEpisode, 1))?.episodes?.[0];
}
};

export const getNextItem = async (series: Series | undefined, episodeMetadata: EpisodeMetadata | undefined) => {
if (!episodeMetadata || !series) {
return;
}

const seasonNumber = Number(episodeMetadata.seasonNumber);
const episodeNumber = Number(episodeMetadata.episodeNumber);

// Get initial data to collect information about total number of elements
const { episodes, pagination } =
seasonNumber !== 0 ? await getSeasonWithEpisodes(series?.series_id, seasonNumber, 0) : await getEpisodes(series?.series_id, 0);

if (episodes.length === 1 && seasonNumber) {
return getNextSeasonEpisode(seasonNumber, series);
}

// Both episodes and seasons can be sorted by asc / desc
const hasAscendingOrder = (Number(episodes[1].episodeNumber) || 0) > (Number(episodes[0].episodeNumber) || 0);
const nextEpisodeNumber = hasAscendingOrder ? episodeNumber + 1 : episodeNumber - 1;
// First page has 0 number, that is why we use Math.floor here und subtract 1
const pageWithEpisode = Math.floor(hasAscendingOrder ? nextEpisodeNumber - 1 : pagination.total - nextEpisodeNumber - 1);
// Consider the case when we have a next episode in the retrieved list
const nextElementIndex = episodes.findIndex((el) => Number(el.episodeNumber) === nextEpisodeNumber);

if (nextElementIndex !== -1) {
return episodes[nextElementIndex];
}

// Fetch the next episodes of the season when there is more to fetch
if (pageWithEpisode < pagination.total) {
return getNextEpisode(seasonNumber, series?.series_id, pageWithEpisode);
// Switch selected season in case the current one has nor more episodes inside
} else if (seasonNumber) {
return getNextSeasonEpisode(seasonNumber, series);
}
};

/** Get a total amount of episodes in a season */
export const getEpisodesInSeason = (episodeMetadata: EpisodeMetadata | undefined, series: Series | undefined) => {
return (series?.seasons || []).find((el) => el.season_number === Number(episodeMetadata?.seasonNumber))?.episode_count;
Expand Down

0 comments on commit 1f11d6a

Please sign in to comment.