diff --git a/app/(auth)/(tabs)/(home)/index.tsx b/app/(auth)/(tabs)/(home)/index.tsx
index 7f0fd2f6..cb0b43bd 100644
--- a/app/(auth)/(tabs)/(home)/index.tsx
+++ b/app/(auth)/(tabs)/(home)/index.tsx
@@ -169,7 +169,7 @@ export default function index() {
setLoading(true);
await queryClient.invalidateQueries();
setLoading(false);
- }, [queryClient, user?.Id]);
+ }, []);
const createCollectionConfig = useCallback(
(
diff --git a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx b/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx
index c496ea88..d01acb29 100644
--- a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx
@@ -1,15 +1,96 @@
+import { Text } from "@/components/common/Text";
import { ItemContent } from "@/components/ItemContent";
-import { Stack, useLocalSearchParams } from "expo-router";
-import React from "react";
+import { Loader } from "@/components/Loader";
+import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
+import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
+import { useQuery } from "@tanstack/react-query";
+import { useLocalSearchParams } from "expo-router";
+import { useAtom } from "jotai";
+import React, { useEffect } from "react";
+import { View } from "react-native";
+import Animated, {
+ runOnJS,
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+} from "react-native-reanimated";
+import { opacity } from "react-native-reanimated/lib/typescript/Colors";
const Page: React.FC = () => {
+ const [api] = useAtom(apiAtom);
+ const [user] = useAtom(userAtom);
const { id } = useLocalSearchParams() as { id: string };
+ const { data: item, isError } = useQuery({
+ queryKey: ["item", id],
+ queryFn: async () => {
+ const res = await getUserItemData({
+ api,
+ userId: user?.Id,
+ itemId: id,
+ });
+
+ return res;
+ },
+ enabled: !!id && !!api,
+ staleTime: 60 * 1000 * 5, // 5 minutes
+ });
+
+ const opacity = useSharedValue(1);
+ const animatedStyle = useAnimatedStyle(() => {
+ return {
+ opacity: opacity.value,
+ };
+ });
+
+ const fadeOut = (callback: any) => {
+ opacity.value = withTiming(0, { duration: 300 }, (finished) => {
+ if (finished) {
+ runOnJS(callback)();
+ }
+ });
+ };
+
+ const fadeIn = (callback: any) => {
+ opacity.value = withTiming(1, { duration: 300 }, (finished) => {
+ if (finished) {
+ runOnJS(callback)();
+ }
+ });
+ };
+ useEffect(() => {
+ if (item) {
+ fadeOut(() => {});
+ } else {
+ fadeIn(() => {});
+ }
+ }, [item]);
+
+ if (isError)
+ return (
+
+ Could not load item
+
+ );
+
return (
- <>
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
+ {item && }
+
);
};
diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx
index 9bbae531..24b69113 100644
--- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx
@@ -1,11 +1,142 @@
+import { ItemImage } from "@/components/common/ItemImage";
import { Text } from "@/components/common/Text";
-import React from "react";
-import { View } from "react-native";
+import { HourHeader } from "@/components/livetv/HourHeader";
+import { LiveTVGuideRow } from "@/components/livetv/LiveTVGuideRow";
+import { TAB_HEIGHT } from "@/constants/Values";
+import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
+import {
+ BaseItemDto,
+ BaseItemDtoQueryResult,
+} from "@jellyfin/sdk/lib/generated-client";
+import { getLiveTvApi } from "@jellyfin/sdk/lib/utils/api";
+import { useQuery } from "@tanstack/react-query";
+import { useAtom } from "jotai";
+import React, { useState } from "react";
+import { Dimensions, ScrollView, View } from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+const HOUR_HEIGHT = 30
export default function page() {
+ const [api] = useAtom(apiAtom);
+ const [user] = useAtom(userAtom);
+ const insets = useSafeAreaInsets();
+ const [date, setDate] = useState(new Date());
+
+ const { data: guideInfo } = useQuery({
+ queryKey: ["livetv", "guideInfo"],
+ queryFn: async () => {
+ const res = await getLiveTvApi(api!).getGuideInfo();
+ return res.data;
+ },
+ });
+ const { data: channels } = useQuery({
+ queryKey: ["livetv", "channels"],
+ queryFn: async () => {
+ const res = await getLiveTvApi(api!).getLiveTvChannels({
+ startIndex: 0,
+ limit: 500,
+ enableFavoriteSorting: true,
+ userId: user?.Id,
+ addCurrentProgram: false,
+ enableUserData: false,
+ enableImageTypes: ["Primary"],
+ });
+ return res.data;
+ },
+ });
+
+ const { data: programs } = useQuery({
+ queryKey: ["livetv", "programs", date],
+ queryFn: async () => {
+ const startOfDay = new Date(date);
+ startOfDay.setHours(0, 0, 0, 0);
+ const endOfDay = new Date(date);
+ endOfDay.setHours(23, 59, 59, 999);
+
+ const now = new Date();
+ const isToday = startOfDay.toDateString() === now.toDateString();
+
+ const res = await getLiveTvApi(api!).getPrograms({
+ getProgramsDto: {
+ MaxStartDate: endOfDay.toISOString(),
+ MinEndDate: isToday ? now.toISOString() : startOfDay.toISOString(),
+ ChannelIds: channels?.Items?.map((c) => c.Id).filter(
+ Boolean
+ ) as string[],
+ ImageTypeLimit: 1,
+ EnableImages: false,
+ SortBy: ["StartDate"],
+ EnableTotalRecordCount: false,
+ EnableUserData: false,
+ },
+ });
+ return res.data;
+ },
+ enabled: !!channels,
+ });
+
+ const screenWidth = Dimensions.get("window").width;
+
+ const [scrollX, setScrollX] = useState(0);
+
return (
-
- Not implemented
-
+
+
+
+
+ {channels?.Items?.map((c, i) => (
+
+
+
+ ))}
+
+ {
+ setScrollX(e.nativeEvent.contentOffset.x);
+ }}
+ >
+
+
+ {channels?.Items?.map((c, i) => (
+
+ ))}
+
+
+
+
);
}
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 9629f22d..828f4005 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -337,6 +337,7 @@ function Layout() {
name="(auth)/play"
options={{
headerShown: false,
+ autoHideHomeIndicator: true,
title: "",
animation: "fade",
}}
@@ -345,6 +346,7 @@ function Layout() {
name="(auth)/play-music"
options={{
headerShown: false,
+ autoHideHomeIndicator: true,
title: "",
animation: "fade",
}}
diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx
index 270829f7..e9d8a004 100644
--- a/components/ItemContent.tsx
+++ b/components/ItemContent.tsx
@@ -14,322 +14,242 @@ import { SeasonEpisodesCarousel } from "@/components/series/SeasonEpisodesCarous
import { useImageColors } from "@/hooks/useImageColors";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
-import { getItemImage } from "@/utils/getItemImage";
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
-import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
import { chromecastProfile } from "@/utils/profiles/chromecast";
-import ios from "@/utils/profiles/ios";
import iosFmp4 from "@/utils/profiles/iosFmp4";
import native from "@/utils/profiles/native";
import old from "@/utils/profiles/old";
-import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
+import {
+ BaseItemDto,
+ MediaSourceInfo,
+} from "@jellyfin/sdk/lib/generated-client/models";
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { Image } from "expo-image";
-import { Stack, useNavigation } from "expo-router";
+import { useNavigation } from "expo-router";
import * as ScreenOrientation from "expo-screen-orientation";
import { useAtom } from "jotai";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { View } from "react-native";
import { useCastDevice } from "react-native-google-cast";
-import Animated, {
- runOnJS,
- useAnimatedStyle,
- useSharedValue,
- withTiming,
-} from "react-native-reanimated";
+import Animated from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Chromecast } from "./Chromecast";
import { ItemHeader } from "./ItemHeader";
-import { Loader } from "./Loader";
import { MediaSourceSelector } from "./MediaSourceSelector";
import { MoreMoviesWithActor } from "./MoreMoviesWithActor";
-export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => {
- const [api] = useAtom(apiAtom);
- const [user] = useAtom(userAtom);
+export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
+ ({ item }) => {
+ const [api] = useAtom(apiAtom);
+ const [user] = useAtom(userAtom);
- const opacity = useSharedValue(0);
- const castDevice = useCastDevice();
- const navigation = useNavigation();
- const [settings] = useSettings();
- const [selectedMediaSource, setSelectedMediaSource] =
- useState(null);
- const [selectedAudioStream, setSelectedAudioStream] = useState(-1);
- const [selectedSubtitleStream, setSelectedSubtitleStream] =
- useState(-1);
- const [maxBitrate, setMaxBitrate] = useState({
- key: "Max",
- value: undefined,
- });
+ const castDevice = useCastDevice();
+ const navigation = useNavigation();
+ const [settings] = useSettings();
+ const [selectedMediaSource, setSelectedMediaSource] =
+ useState(null);
+ const [selectedAudioStream, setSelectedAudioStream] = useState(-1);
+ const [selectedSubtitleStream, setSelectedSubtitleStream] =
+ useState(-1);
+ const [maxBitrate, setMaxBitrate] = useState({
+ key: "Max",
+ value: undefined,
+ });
- const [loadingLogo, setLoadingLogo] = useState(true);
+ const [loadingLogo, setLoadingLogo] = useState(true);
- const [orientation, setOrientation] = useState(
- ScreenOrientation.Orientation.PORTRAIT_UP
- );
-
- useEffect(() => {
- const subscription = ScreenOrientation.addOrientationChangeListener(
- (event) => {
- setOrientation(event.orientationInfo.orientation);
- }
+ const [orientation, setOrientation] = useState(
+ ScreenOrientation.Orientation.PORTRAIT_UP
);
- ScreenOrientation.getOrientationAsync().then((initialOrientation) => {
- setOrientation(initialOrientation);
- });
-
- return () => {
- ScreenOrientation.removeOrientationChangeListener(subscription);
- };
- }, []);
-
- const animatedStyle = useAnimatedStyle(() => {
- return {
- opacity: opacity.value,
- };
- });
-
- const fadeIn = () => {
- opacity.value = withTiming(1, { duration: 300 });
- };
-
- const fadeOut = (callback: any) => {
- opacity.value = withTiming(0, { duration: 300 }, (finished) => {
- if (finished) {
- runOnJS(callback)();
- }
- });
- };
-
- const headerHeightRef = useRef(400);
-
- const {
- data: item,
- isLoading,
- isFetching,
- } = useQuery({
- queryKey: ["item", id],
- queryFn: async () => {
- const res = await getUserItemData({
- api,
- userId: user?.Id,
- itemId: id,
- });
-
- return res;
- },
- enabled: !!id && !!api,
- staleTime: 60 * 1000 * 5,
- });
-
- const [localItem, setLocalItem] = useState(item);
- useImageColors({ item });
-
- useEffect(() => {
- if (item) {
- if (localItem) {
- // Fade out current item
- fadeOut(() => {
- // Update local item after fade out
- setLocalItem(item);
- // Then fade in
- fadeIn();
- });
- } else {
- // If there's no current item, just set and fade in
- setLocalItem(item);
- fadeIn();
- }
- } else {
- // If item is null, fade out and clear local item
- fadeOut(() => setLocalItem(null));
- }
- }, [item]);
-
- useEffect(() => {
- navigation.setOptions({
- headerRight: () =>
- item && (
-
-
- {item.Type !== "Program" && (
- <>
-
-
- >
- )}
-
- ),
- });
- }, [item]);
-
- useEffect(() => {
- if (orientation !== ScreenOrientation.Orientation.PORTRAIT_UP) {
- headerHeightRef.current = 230;
- return;
- }
- if (item?.Type === "Episode") headerHeightRef.current = 400;
- else if (item?.Type === "Movie") headerHeightRef.current = 500;
- else headerHeightRef.current = 400;
- }, [item, orientation]);
-
- const { data: sessionData } = useQuery({
- queryKey: ["sessionData", item?.Id],
- queryFn: async () => {
- if (!api || !user?.Id || !item?.Id) return null;
- const playbackData = await getMediaInfoApi(api!).getPlaybackInfo(
- {
- itemId: item?.Id,
- userId: user?.Id,
- },
- {
- method: "POST",
+ useEffect(() => {
+ const subscription = ScreenOrientation.addOrientationChangeListener(
+ (event) => {
+ setOrientation(event.orientationInfo.orientation);
}
);
- return playbackData.data;
- },
- enabled: !!item?.Id && !!api && !!user?.Id,
- staleTime: 0,
- });
-
- const { data: playbackUrl } = useQuery({
- queryKey: [
- "playbackUrl",
- item?.Id,
- maxBitrate,
- castDevice,
- selectedMediaSource,
- selectedAudioStream,
- selectedSubtitleStream,
- settings,
- ],
- queryFn: async () => {
- if (!api || !user?.Id) {
- console.warn("No api, userid or selected media source", {
- api: api,
- user: user,
- });
- return null;
- }
-
- if (
- item?.Type !== "Program" &&
- (!sessionData || !selectedMediaSource?.Id)
- ) {
- console.warn("No session data or media source", {
- sessionData: sessionData,
- selectedMediaSource: selectedMediaSource,
- });
- return null;
- }
-
- let deviceProfile: any = iosFmp4;
-
- if (castDevice?.deviceId) {
- deviceProfile = chromecastProfile;
- } else if (settings?.deviceProfile === "Native") {
- deviceProfile = native;
- } else if (settings?.deviceProfile === "Old") {
- deviceProfile = old;
- }
-
- console.log("playbackUrl...");
-
- const url = await getStreamUrl({
- api,
- userId: user.Id,
- item,
- startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0,
- maxStreamingBitrate: maxBitrate.value,
- sessionData,
- deviceProfile,
- audioStreamIndex: selectedAudioStream,
- subtitleStreamIndex: selectedSubtitleStream,
- forceDirectPlay: settings?.forceDirectPlay,
- height: maxBitrate.height,
- mediaSourceId: selectedMediaSource?.Id,
+ ScreenOrientation.getOrientationAsync().then((initialOrientation) => {
+ setOrientation(initialOrientation);
});
- console.info("Stream URL:", url);
+ return () => {
+ ScreenOrientation.removeOrientationChangeListener(subscription);
+ };
+ }, []);
- return url;
- },
- enabled: !!api && !!user?.Id && !!item?.Id,
- staleTime: 0,
- });
+ const headerHeightRef = useRef(400);
- const logoUrl = useMemo(() => getLogoImageUrlById({ api, item }), [item]);
+ useImageColors({ item });
- const loading = useMemo(() => {
- return Boolean(isLoading || isFetching || (logoUrl && loadingLogo));
- }, [isLoading, isFetching, loadingLogo, logoUrl]);
+ useEffect(() => {
+ navigation.setOptions({
+ headerRight: () =>
+ item && (
+
+
+ {item.Type !== "Program" && (
+ <>
+
+
+ >
+ )}
+
+ ),
+ });
+ }, [item]);
- const insets = useSafeAreaInsets();
+ useEffect(() => {
+ if (orientation !== ScreenOrientation.Orientation.PORTRAIT_UP) {
+ headerHeightRef.current = 230;
+ return;
+ }
+ if (item.Type === "Episode") headerHeightRef.current = 400;
+ else if (item.Type === "Movie") headerHeightRef.current = 500;
+ else headerHeightRef.current = 400;
+ }, [item, orientation]);
- return (
-
- {loading && (
-
-
-
- )}
-
-
- {localItem && (
+ const { data: sessionData } = useQuery({
+ queryKey: ["sessionData", item.Id],
+ queryFn: async () => {
+ if (!api || !user?.Id || !item.Id) {
+ return null;
+ }
+ const playbackData = await getMediaInfoApi(api!).getPlaybackInfo(
+ {
+ itemId: item.Id,
+ userId: user?.Id,
+ },
+ {
+ method: "POST",
+ }
+ );
+
+ return playbackData.data;
+ },
+ enabled: !!item.Id && !!api && !!user?.Id,
+ staleTime: 0,
+ });
+
+ const { data: playbackUrl } = useQuery({
+ queryKey: [
+ "playbackUrl",
+ item.Id,
+ maxBitrate,
+ castDevice?.deviceId,
+ selectedMediaSource?.Id,
+ selectedAudioStream,
+ selectedSubtitleStream,
+ settings,
+ sessionData?.PlaySessionId,
+ ],
+ queryFn: async () => {
+ if (!api || !user?.Id) {
+ return null;
+ }
+
+ if (
+ item.Type !== "Program" &&
+ (!sessionData || !selectedMediaSource?.Id)
+ ) {
+ return null;
+ }
+
+ let deviceProfile: any = iosFmp4;
+
+ if (castDevice?.deviceId) {
+ deviceProfile = chromecastProfile;
+ } else if (settings?.deviceProfile === "Native") {
+ deviceProfile = native;
+ } else if (settings?.deviceProfile === "Old") {
+ deviceProfile = old;
+ }
+
+ console.log("playbackUrl...");
+
+ const url = await getStreamUrl({
+ api,
+ userId: user.Id,
+ item,
+ startTimeTicks: item.UserData?.PlaybackPositionTicks || 0,
+ maxStreamingBitrate: maxBitrate.value,
+ sessionData,
+ deviceProfile,
+ audioStreamIndex: selectedAudioStream,
+ subtitleStreamIndex: selectedSubtitleStream,
+ forceDirectPlay: settings?.forceDirectPlay,
+ height: maxBitrate.height,
+ mediaSourceId: selectedMediaSource?.Id,
+ });
+
+ console.info("Stream URL:", url);
+
+ return url;
+ },
+ enabled: !!api && !!user?.Id && !!item.Id,
+ staleTime: 0,
+ });
+
+ const logoUrl = useMemo(() => getLogoImageUrlById({ api, item }), [item]);
+
+ const loading = useMemo(() => {
+ return Boolean(logoUrl && loadingLogo);
+ }, [loadingLogo, logoUrl]);
+
+ const insets = useSafeAreaInsets();
+
+ return (
+
+
+
- )}
-
- >
- }
- logo={
- <>
- {logoUrl ? (
- setLoadingLogo(false)}
- onError={() => setLoadingLogo(false)}
- />
- ) : null}
- >
- }
- >
-
-
-
-
- {localItem ? (
+
+ >
+ }
+ logo={
+ <>
+ {logoUrl ? (
+ setLoadingLogo(false)}
+ onError={() => setLoadingLogo(false)}
+ />
+ ) : null}
+ >
+ }
+ >
+
+
+
+
= React.memo(({ id }) => {
/>
@@ -358,46 +278,42 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => {
>
)}
- ) : (
-
-
-
-
- )}
-
+
-
-
-
- {item?.Type === "Episode" && (
-
- )}
-
-
-
-
-
- {item?.People && item.People.length > 0 && (
-
- {item.People.slice(0, 3).map((person) => (
-
- ))}
+
- )}
- {item?.Type === "Episode" && (
-
- )}
-
+ {item.Type === "Episode" && (
+
+ )}
-
-
-
-
- );
-});
+
+
+
+
+ {item.People && item.People.length > 0 && (
+
+ {item.People.slice(0, 3).map((person) => (
+
+ ))}
+
+ )}
+
+ {item.Type === "Episode" && (
+
+ )}
+
+
+
+
+
+
+
+ );
+ }
+);
diff --git a/components/livetv/HourHeader.tsx b/components/livetv/HourHeader.tsx
new file mode 100644
index 00000000..00a51d9e
--- /dev/null
+++ b/components/livetv/HourHeader.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import { View } from "react-native";
+import { Text } from "../common/Text";
+
+export const HourHeader = ({ height }: { height: number }) => {
+ const now = new Date();
+ const currentHour = now.getHours();
+ const hoursRemaining = 24 - currentHour;
+ const hours = generateHours(currentHour, hoursRemaining);
+
+ return (
+
+ {hours.map((hour, index) => (
+
+ ))}
+
+ );
+};
+
+const HourCell = ({ hour }: { hour: Date }) => (
+
+
+ {hour.toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+);
+
+const generateHours = (startHour: number, count: number): Date[] => {
+ const now = new Date();
+ return Array.from({ length: count }, (_, i) => {
+ const hour = new Date(now);
+ hour.setHours(startHour + i, 0, 0, 0);
+ return hour;
+ });
+};
diff --git a/components/livetv/LiveTVGuideRow.tsx b/components/livetv/LiveTVGuideRow.tsx
new file mode 100644
index 00000000..59842e1f
--- /dev/null
+++ b/components/livetv/LiveTVGuideRow.tsx
@@ -0,0 +1,91 @@
+import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
+import { View, ScrollView, Dimensions } from "react-native";
+import { ItemImage } from "../common/ItemImage";
+import { Text } from "../common/Text";
+import { useCallback, useMemo, useRef, useState } from "react";
+import { TouchableItemRouter } from "../common/TouchableItemRouter";
+
+export const LiveTVGuideRow = ({
+ channel,
+ programs,
+ scrollX = 0,
+}: {
+ channel: BaseItemDto;
+ programs?: BaseItemDto[] | null;
+ scrollX?: number;
+}) => {
+ const positionRefs = useRef<{ [key: string]: number }>({});
+ const screenWidth = Dimensions.get("window").width;
+
+ const calculateWidth = (s?: string | null, e?: string | null) => {
+ if (!s || !e) return 0;
+ const start = new Date(s);
+ const end = new Date(e);
+ const duration = end.getTime() - start.getTime();
+ const minutes = duration / 60000;
+ const width = (minutes / 60) * 200;
+ return width;
+ };
+
+ const programsWithPositions = useMemo(() => {
+ let cumulativeWidth = 0;
+ return programs
+ ?.filter((p) => p.ChannelId === channel.Id)
+ .map((p) => {
+ const width = calculateWidth(p.StartDate, p.EndDate);
+ const position = cumulativeWidth;
+ cumulativeWidth += width;
+ return { ...p, width, position };
+ });
+ }, [programs, channel.Id]);
+
+ const isCurrentlyLive = (program: BaseItemDto) => {
+ if (!program.StartDate || !program.EndDate) return false;
+ const now = new Date();
+ const start = new Date(program.StartDate);
+ const end = new Date(program.EndDate);
+ return now >= start && now <= end;
+ };
+
+ return (
+
+ {programsWithPositions?.map((p) => (
+
+
+ {(() => {
+ return (
+ screenWidth && scrollX > p.position
+ ? scrollX - p.position
+ : 0,
+ }}
+ className="px-4 self-start"
+ >
+
+ {p.Name}
+
+
+ );
+ })()}
+
+
+ ))}
+
+ );
+};
diff --git a/components/series/SeasonEpisodesCarousel.tsx b/components/series/SeasonEpisodesCarousel.tsx
index 43bd6780..e03d590d 100644
--- a/components/series/SeasonEpisodesCarousel.tsx
+++ b/components/series/SeasonEpisodesCarousel.tsx
@@ -85,7 +85,7 @@ export const SeasonEpisodesCarousel: React.FC = ({
userId: user?.Id,
itemId: previousId,
}),
- staleTime: 60 * 1000,
+ staleTime: 60 * 1000 * 5,
});
}
@@ -101,7 +101,7 @@ export const SeasonEpisodesCarousel: React.FC = ({
userId: user?.Id,
itemId: nextId,
}),
- staleTime: 60 * 1000,
+ staleTime: 60 * 1000 * 5,
});
}
}, [episodes, api, user?.Id, item]);