mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
Merge branch 'dev' into feat/autoplay
This commit is contained in:
commit
ad83797451
3 changed files with 99 additions and 16 deletions
|
@ -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",
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue