From e82b15403265e8960131c4d951d195b2b44c13f6 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Tue, 24 Sep 2024 20:05:04 +0200 Subject: [PATCH] feat: skip credits --- components/FullScreenVideoPlayer.tsx | 115 +++++++++++++-------------- hooks/useCreditSkipper.ts | 72 +++++++++++++++++ 2 files changed, 126 insertions(+), 61 deletions(-) create mode 100644 hooks/useCreditSkipper.ts diff --git a/components/FullScreenVideoPlayer.tsx b/components/FullScreenVideoPlayer.tsx index c80fdb76..2729aa98 100644 --- a/components/FullScreenVideoPlayer.tsx +++ b/components/FullScreenVideoPlayer.tsx @@ -44,6 +44,8 @@ import { runtimeTicksToSeconds, ticksToSeconds, } from "@/utils/time"; +import { useIntroSkipper } from "@/hooks/useIntroSkipper"; +import { useCreditSkipper } from "@/hooks/useCreditSkipper"; const windowDimensions = Dimensions.get("window"); const screenDimensions = Dimensions.get("screen"); @@ -116,6 +118,18 @@ export const FullScreenVideoPlayer: React.FC = () => { [] ); + const { showSkipButton, skipIntro } = useIntroSkipper( + currentlyPlaying?.item.Id, + currentTime, + videoRef + ); + + const { showSkipCreditButton, skipCredit } = useCreditSkipper( + currentlyPlaying?.item.Id, + currentTime, + videoRef + ); + useAnimatedReaction( () => ({ progress: progress.value, @@ -321,46 +335,6 @@ export const FullScreenVideoPlayer: React.FC = () => { setIgnoreSafeArea((prev) => !prev); }, []); - const { data: introTimestamps } = useQuery({ - queryKey: ["introTimestamps", currentlyPlaying?.item.Id], - queryFn: async () => { - if (!currentlyPlaying?.item.Id) { - console.log("No item id"); - return null; - } - - const res = await api?.axiosInstance.get( - `${api.basePath}/Episode/${currentlyPlaying.item.Id}/IntroTimestamps`, - { - headers: getAuthHeaders(api), - } - ); - - if (res?.status !== 200) { - return null; - } - - return res?.data as { - EpisodeId: string; - HideSkipPromptAt: number; - IntroEnd: number; - IntroStart: number; - ShowSkipPromptAt: number; - Valid: boolean; - }; - }, - enabled: !!currentlyPlaying?.item.Id, - }); - - const skipIntro = useCallback(async () => { - if (!introTimestamps || !videoRef.current) return; - try { - videoRef.current.seek(introTimestamps.IntroEnd); - } catch (error) { - writeToLog("ERROR", "Error skipping intro", error); - } - }, [introTimestamps]); - if (!currentlyPlaying) return null; return ( @@ -428,28 +402,47 @@ export const FullScreenVideoPlayer: React.FC = () => { )} - {introTimestamps && - currentTime > introTimestamps.ShowSkipPromptAt && - currentTime < introTimestamps.HideSkipPromptAt && ( - + - - Skip Intro - - - )} + Skip Intro + + + )} + + {showSkipCreditButton && ( + + + Skip Credits + + + )} {showControls && ( <> diff --git a/hooks/useCreditSkipper.ts b/hooks/useCreditSkipper.ts new file mode 100644 index 00000000..2fb2940d --- /dev/null +++ b/hooks/useCreditSkipper.ts @@ -0,0 +1,72 @@ +import { useCallback, useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { useAtom } from "jotai"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { getAuthHeaders } from "@/utils/jellyfin/jellyfin"; +import { writeToLog } from "@/utils/log"; + +interface CreditTimestamps { + Introduction: { + Start: number; + End: number; + Valid: boolean; + }; + Credits: { + Start: number; + End: number; + Valid: boolean; + }; +} + +export const useCreditSkipper = ( + itemId: string | undefined, + currentTime: number, + videoRef: React.RefObject +) => { + const [api] = useAtom(apiAtom); + const [showSkipCreditButton, setShowSkipCreditButton] = useState(false); + + const { data: creditTimestamps } = useQuery({ + queryKey: ["creditTimestamps", itemId], + queryFn: async () => { + if (!itemId) { + console.log("No item id"); + return null; + } + + const res = await api?.axiosInstance.get( + `${api.basePath}/Episode/${itemId}/Timestamps`, + { + headers: getAuthHeaders(api), + } + ); + + if (res?.status !== 200) { + return null; + } + + return res?.data; + }, + enabled: !!itemId, + }); + + useEffect(() => { + if (creditTimestamps) { + setShowSkipCreditButton( + currentTime > creditTimestamps.Credits.Start && + currentTime < creditTimestamps.Credits.End + ); + } + }, [creditTimestamps, currentTime]); + + const skipCredit = useCallback(() => { + if (!creditTimestamps || !videoRef.current) return; + try { + videoRef.current.seek(creditTimestamps.Credits.End); + } catch (error) { + writeToLog("ERROR", "Error skipping intro", error); + } + }, [creditTimestamps, videoRef]); + + return { showSkipCreditButton, skipCredit }; +};