|
|
|
@ -1,7 +1,10 @@
@@ -1,7 +1,10 @@
|
|
|
|
|
import classNames from "classnames"; |
|
|
|
|
import { useCallback } from "react"; |
|
|
|
|
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 { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; |
|
|
|
|
import { Transition } from "@/components/utils/Transition"; |
|
|
|
@ -9,6 +12,8 @@ import { PlayerMeta } from "@/stores/player/slices/source";
@@ -9,6 +12,8 @@ import { PlayerMeta } from "@/stores/player/slices/source";
|
|
|
|
|
import { usePlayerStore } from "@/stores/player/store"; |
|
|
|
|
import { useProgressStore } from "@/stores/progress"; |
|
|
|
|
|
|
|
|
|
import { hasAired } from "../utils/aired"; |
|
|
|
|
|
|
|
|
|
function shouldShowNextEpisodeButton( |
|
|
|
|
time: number, |
|
|
|
|
duration: number, |
|
|
|
@ -39,6 +44,45 @@ function Button(props: {
@@ -39,6 +44,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: { |
|
|
|
|
controlsShowing: boolean; |
|
|
|
|
onChange?: (meta: PlayerMeta) => void; |
|
|
|
@ -58,6 +102,20 @@ export function NextEpisodeButton(props: {
@@ -58,6 +102,20 @@ export function NextEpisodeButton(props: {
|
|
|
|
|
); |
|
|
|
|
const updateItem = useProgressStore((s) => s.updateItem); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
if (showingState === "always") show = true; |
|
|
|
|
else if (showingState === "hover" && props.controlsShowing) show = true; |
|
|
|
@ -70,14 +128,23 @@ export function NextEpisodeButton(props: {
@@ -70,14 +128,23 @@ export function NextEpisodeButton(props: {
|
|
|
|
|
? bottom |
|
|
|
|
: "bottom-[calc(3rem+env(safe-area-inset-bottom))]"; |
|
|
|
|
|
|
|
|
|
const nextEp = meta?.episodes?.find( |
|
|
|
|
(v) => v.number === (meta?.episode?.number ?? 0) + 1, |
|
|
|
|
); |
|
|
|
|
const nextEp = isLastEpisode |
|
|
|
|
? nextSeasonEpisode.value |
|
|
|
|
: meta?.episodes?.find( |
|
|
|
|
(v) => v.number === (meta?.episode?.number ?? 0) + 1, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const loadNextEpisode = useCallback(() => { |
|
|
|
|
if (!meta || !nextEp) return; |
|
|
|
|
const metaCopy = { ...meta }; |
|
|
|
|
metaCopy.episode = nextEp; |
|
|
|
|
metaCopy.season = |
|
|
|
|
isLastEpisode && nextSeason |
|
|
|
|
? { |
|
|
|
|
...nextSeason, |
|
|
|
|
tmdbId: nextSeason.id, |
|
|
|
|
} |
|
|
|
|
: metaCopy.season; |
|
|
|
|
setShouldStartFromBeginning(true); |
|
|
|
|
setDirectMeta(metaCopy); |
|
|
|
|
props.onChange?.(metaCopy); |
|
|
|
@ -93,6 +160,8 @@ export function NextEpisodeButton(props: {
@@ -93,6 +160,8 @@ export function NextEpisodeButton(props: {
|
|
|
|
|
props, |
|
|
|
|
setShouldStartFromBeginning, |
|
|
|
|
updateItem, |
|
|
|
|
isLastEpisode, |
|
|
|
|
nextSeason, |
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
if (!meta?.episode || !nextEp) return null; |
|
|
|
@ -121,7 +190,9 @@ export function NextEpisodeButton(props: {
@@ -121,7 +190,9 @@ export function NextEpisodeButton(props: {
|
|
|
|
|
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} /> |
|
|
|
|
{t("player.nextEpisode.next")} |
|
|
|
|
{isLastEpisode && nextEp |
|
|
|
|
? t("player.nextEpisode.nextSeason") |
|
|
|
|
: t("player.nextEpisode.next")} |
|
|
|
|
</Button> |
|
|
|
|
</div> |
|
|
|
|
</Transition> |
|
|
|
|