Merge branch 'dev' into feat/autoplay

This commit is contained in:
William Oldham 2024-04-14 21:30:44 +01:00 committed by GitHub
commit ad83797451
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 99 additions and 16 deletions

View File

@ -362,7 +362,8 @@
}, },
"nextEpisode": { "nextEpisode": {
"cancel": "Cancel", "cancel": "Cancel",
"next": "Next episode" "next": "Next episode",
"nextSeason": "Next season"
}, },
"playbackError": { "playbackError": {
"badge": "Playback error", "badge": "Playback error",

View File

@ -143,26 +143,37 @@ export function decodeTMDBId(
}; };
} }
const baseURL = "https://api.themoviedb.org/3"; const tmdbBaseUrl1 = "https://api.themoviedb.org/3";
const tmdbBaseUrl2 = "https://api.tmdb.org/3";
const apiKey = conf().TMDB_READ_API_KEY; const apiKey = conf().TMDB_READ_API_KEY;
const headers = { const tmdbHeaders = {
accept: "application/json", accept: "application/json",
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}; };
async function get<T>(url: string, params?: object): Promise<T> { async function get<T>(url: string, params?: object): Promise<T> {
if (!apiKey) throw new Error("TMDB API key not set"); if (!apiKey) throw new Error("TMDB API key not set");
try {
const res = await mwFetch<any>(encodeURI(url), { return await mwFetch<T>(encodeURI(url), {
headers, headers: tmdbHeaders,
baseURL, baseURL: tmdbBaseUrl1,
params: { params: {
...params, ...params,
}, },
}); signal: AbortSignal.timeout(5000),
return res; });
} catch (err) {
return mwFetch<T>(encodeURI(url), {
headers: tmdbHeaders,
baseURL: tmdbBaseUrl2,
params: {
...params,
},
signal: AbortSignal.timeout(30000),
});
}
} }
export async function multiSearch( export async function multiSearch(

View File

@ -1,7 +1,10 @@
import classNames from "classnames"; import classNames from "classnames";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAsync } from "react-use";
import { getMetaFromId } from "@/backend/metadata/getmeta";
import { MWMediaType, MWSeasonMeta } from "@/backend/metadata/types/mw";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
import { Transition } from "@/components/utils/Transition"; import { Transition } from "@/components/utils/Transition";
@ -11,6 +14,8 @@ import { usePreferencesStore } from "@/stores/preferences";
import { useProgressStore } from "@/stores/progress"; import { useProgressStore } from "@/stores/progress";
import { isAutoplayAllowed } from "@/utils/autoplay"; import { isAutoplayAllowed } from "@/utils/autoplay";
import { hasAired } from "../utils/aired";
function shouldShowNextEpisodeButton( function shouldShowNextEpisodeButton(
time: number, time: number,
duration: number, duration: number,
@ -41,6 +46,45 @@ function Button(props: {
); );
} }
function useSeasons(mediaId: string, isLastEpisode: boolean = false) {
const state = useAsync(async () => {
if (isLastEpisode) {
const data = await getMetaFromId(MWMediaType.SERIES, mediaId ?? "");
if (data?.meta.type !== MWMediaType.SERIES) return null;
return data.meta.seasons;
}
}, [mediaId, isLastEpisode]);
return state;
}
function useNextSeasonEpisode(
nextSeason: MWSeasonMeta | undefined,
mediaId: string,
) {
const state = useAsync(async () => {
if (nextSeason) {
const data = await getMetaFromId(
MWMediaType.SERIES,
mediaId ?? "",
nextSeason?.id,
);
if (data?.meta.type !== MWMediaType.SERIES) return null;
const nextSeasonEpisodes = data?.meta?.seasonData?.episodes
.filter((episode) => hasAired(episode.air_date))
.map((episode) => ({
number: episode.number,
title: episode.title,
tmdbId: episode.id,
}));
if (nextSeasonEpisodes.length > 0) return nextSeasonEpisodes[0];
}
}, [mediaId, nextSeason?.id]);
return state;
}
export function NextEpisodeButton(props: { export function NextEpisodeButton(props: {
controlsShowing: boolean; controlsShowing: boolean;
onChange?: (meta: PlayerMeta) => void; onChange?: (meta: PlayerMeta) => void;
@ -61,6 +105,20 @@ export function NextEpisodeButton(props: {
const updateItem = useProgressStore((s) => s.updateItem); const updateItem = useProgressStore((s) => s.updateItem);
const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay); const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay);
const isLastEpisode =
meta?.episode?.number === meta?.episodes?.at(-1)?.number;
const seasons = useSeasons(meta?.tmdbId ?? "", isLastEpisode);
const nextSeason = seasons.value?.find(
(season) => season.number === (meta?.season?.number ?? 0) + 1,
);
const nextSeasonEpisode = useNextSeasonEpisode(
nextSeason,
meta?.tmdbId ?? "",
);
let show = false; let show = false;
const hasAutoplayed = useRef(false); const hasAutoplayed = useRef(false);
if (showingState === "always") show = true; if (showingState === "always") show = true;
@ -74,14 +132,23 @@ export function NextEpisodeButton(props: {
? bottom ? bottom
: "bottom-[calc(3rem+env(safe-area-inset-bottom))]"; : "bottom-[calc(3rem+env(safe-area-inset-bottom))]";
const nextEp = meta?.episodes?.find( const nextEp = isLastEpisode
(v) => v.number === (meta?.episode?.number ?? 0) + 1, ? nextSeasonEpisode.value
); : meta?.episodes?.find(
(v) => v.number === (meta?.episode?.number ?? 0) + 1,
);
const loadNextEpisode = useCallback(() => { const loadNextEpisode = useCallback(() => {
if (!meta || !nextEp) return; if (!meta || !nextEp) return;
const metaCopy = { ...meta }; const metaCopy = { ...meta };
metaCopy.episode = nextEp; metaCopy.episode = nextEp;
metaCopy.season =
isLastEpisode && nextSeason
? {
...nextSeason,
tmdbId: nextSeason.id,
}
: metaCopy.season;
setShouldStartFromBeginning(true); setShouldStartFromBeginning(true);
setDirectMeta(metaCopy); setDirectMeta(metaCopy);
props.onChange?.(metaCopy); props.onChange?.(metaCopy);
@ -97,6 +164,8 @@ export function NextEpisodeButton(props: {
props, props,
setShouldStartFromBeginning, setShouldStartFromBeginning,
updateItem, updateItem,
isLastEpisode,
nextSeason,
]); ]);
useEffect(() => { useEffect(() => {
@ -137,7 +206,9 @@ export function NextEpisodeButton(props: {
className="bg-buttons-primary hover:bg-buttons-primaryHover text-buttons-primaryText flex justify-center items-center" className="bg-buttons-primary hover:bg-buttons-primaryHover text-buttons-primaryText flex justify-center items-center"
> >
<Icon className="text-xl mr-1" icon={Icons.SKIP_EPISODE} /> <Icon className="text-xl mr-1" icon={Icons.SKIP_EPISODE} />
{t("player.nextEpisode.next")} {isLastEpisode && nextEp
? t("player.nextEpisode.nextSeason")
: t("player.nextEpisode.next")}
</Button> </Button>
</div> </div>
</Transition> </Transition>