From 387add4c831c64fa86223971c31e11949190abb1 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 5 Oct 2024 09:17:54 +0200 Subject: [PATCH 1/9] first commit --- .../livetv/_layout.tsx | 46 ++++++ .../livetv/channels.tsx | 56 +++++++ .../(home,libraries,search)/livetv/guide.tsx | 11 ++ .../livetv/programs.tsx | 154 ++++++++++++++++++ .../livetv/recordings.tsx | 11 ++ .../livetv/schedule.tsx | 11 ++ .../(home,libraries,search)/livetv/series.tsx | 11 ++ bun.lockb | Bin 619713 -> 622019 bytes components/ContinueWatchingPoster.tsx | 6 + components/ItemContent.tsx | 32 +++- components/common/TouchableItemRouter.tsx | 4 + components/home/ScrollingCollectionList.tsx | 8 + package.json | 4 + utils/jellyfin/media/getStreamUrl.ts | 54 +++--- 14 files changed, 384 insertions(+), 24 deletions(-) create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/recordings.tsx create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/schedule.tsx create mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx new file mode 100644 index 00000000..e82e8c2b --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx @@ -0,0 +1,46 @@ +import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs"; +import type { + MaterialTopTabNavigationOptions, + MaterialTopTabNavigationEventMap, +} from "@react-navigation/material-top-tabs"; +import { ParamListBase, TabNavigationState } from "@react-navigation/native"; +import { withLayoutContext } from "expo-router"; + +const { Navigator } = createMaterialTopTabNavigator(); + +export const Tab = withLayoutContext< + MaterialTopTabNavigationOptions, + typeof Navigator, + TabNavigationState, + MaterialTopTabNavigationEventMap +>(Navigator); + +const Layout = () => { + return ( + + + + + + + + + ); +}; + +export default Layout; diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx new file mode 100644 index 00000000..f2a73887 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx @@ -0,0 +1,56 @@ +import { ItemImage } from "@/components/common/ItemImage"; +import { Text } from "@/components/common/Text"; +import { ItemPoster } from "@/components/posters/ItemPoster"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { getLiveTvApi } from "@jellyfin/sdk/lib/utils/api"; +import { FlashList } from "@shopify/flash-list"; +import { useQuery } from "@tanstack/react-query"; +import { Image } from "expo-image"; +import { useAtom } from "jotai"; +import React from "react"; +import { View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +export default function page() { + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + + const { data: channels } = useQuery({ + queryKey: ["livetv", "channels"], + queryFn: async () => { + if (!api) return []; + const res = await getLiveTvApi(api).getLiveTvChannels({ + startIndex: 0, + fields: ["PrimaryImageAspectRatio"], + limit: 100, + userId: user?.Id, + }); + return res.data.Items; + }, + }); + + return ( + + ( + + + + + {item.Name} + + )} + /> + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx new file mode 100644 index 00000000..9bbae531 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx @@ -0,0 +1,11 @@ +import { Text } from "@/components/common/Text"; +import React from "react"; +import { View } from "react-native"; + +export default function page() { + return ( + + Not implemented + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx new file mode 100644 index 00000000..27ed4385 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx @@ -0,0 +1,154 @@ +import { Text } from "@/components/common/Text"; +import { ScrollingCollectionList } from "@/components/home/ScrollingCollectionList"; +import { TAB_HEIGHT } from "@/constants/Values"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { BaseItemDto } 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 from "react"; +import { + RefreshControl, + ScrollView, + SectionListComponent, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +export default function page() { + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + + return ( + + + { + if (!api) return [] as BaseItemDto[]; + const res = await getLiveTvApi(api).getRecommendedPrograms({ + userId: user?.Id, + isAiring: true, + limit: 24, + imageTypeLimit: 1, + enableImageTypes: ["Primary", "Thumb", "Backdrop"], + enableTotalRecordCount: false, + fields: ["ChannelInfo", "PrimaryImageAspectRatio"], + }); + return res.data.Items || []; + }} + orientation="horizontal" + /> + { + if (!api) return [] as BaseItemDto[]; + const res = await getLiveTvApi(api).getLiveTvPrograms({ + userId: user?.Id, + hasAired: false, + limit: 9, + isMovie: false, + isSeries: true, + isSports: false, + isNews: false, + isKids: false, + enableTotalRecordCount: false, + fields: ["ChannelInfo", "PrimaryImageAspectRatio"], + enableImageTypes: ["Primary", "Thumb", "Backdrop"], + }); + return res.data.Items || []; + }} + orientation="horizontal" + /> + { + if (!api) return [] as BaseItemDto[]; + const res = await getLiveTvApi(api).getLiveTvPrograms({ + userId: user?.Id, + hasAired: false, + limit: 9, + isMovie: true, + enableTotalRecordCount: false, + fields: ["ChannelInfo"], + enableImageTypes: ["Primary", "Thumb", "Backdrop"], + }); + return res.data.Items || []; + }} + orientation="horizontal" + /> + { + if (!api) return [] as BaseItemDto[]; + const res = await getLiveTvApi(api).getLiveTvPrograms({ + userId: user?.Id, + hasAired: false, + limit: 9, + isSports: true, + enableTotalRecordCount: false, + fields: ["ChannelInfo"], + enableImageTypes: ["Primary", "Thumb", "Backdrop"], + }); + return res.data.Items || []; + }} + orientation="horizontal" + /> + { + if (!api) return [] as BaseItemDto[]; + const res = await getLiveTvApi(api).getLiveTvPrograms({ + userId: user?.Id, + hasAired: false, + limit: 9, + isKids: true, + enableTotalRecordCount: false, + fields: ["ChannelInfo"], + enableImageTypes: ["Primary", "Thumb", "Backdrop"], + }); + return res.data.Items || []; + }} + orientation="horizontal" + /> + { + if (!api) return [] as BaseItemDto[]; + const res = await getLiveTvApi(api).getLiveTvPrograms({ + userId: user?.Id, + hasAired: false, + limit: 9, + isNews: true, + enableTotalRecordCount: false, + fields: ["ChannelInfo"], + enableImageTypes: ["Primary", "Thumb", "Backdrop"], + }); + return res.data.Items || []; + }} + orientation="horizontal" + /> + + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/recordings.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/recordings.tsx new file mode 100644 index 00000000..9bbae531 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/recordings.tsx @@ -0,0 +1,11 @@ +import { Text } from "@/components/common/Text"; +import React from "react"; +import { View } from "react-native"; + +export default function page() { + return ( + + Not implemented + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/schedule.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/schedule.tsx new file mode 100644 index 00000000..9bbae531 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/schedule.tsx @@ -0,0 +1,11 @@ +import { Text } from "@/components/common/Text"; +import React from "react"; +import { View } from "react-native"; + +export default function page() { + return ( + + Not implemented + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx new file mode 100644 index 00000000..9bbae531 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx @@ -0,0 +1,11 @@ +import { Text } from "@/components/common/Text"; +import React from "react"; +import { View } from "react-native"; + +export default function page() { + return ( + + Not implemented + + ); +} diff --git a/bun.lockb b/bun.lockb index de0709bd1ed79c38d58a696c5fcae36e37360a36..133145f0fd596f83fedd4c599756851851e93dfb 100755 GIT binary patch delta 104534 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 y$~WJ$zdoySbO(n@_sWp&4rAI = ({ else return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`; } + if (item.Type === "Program") { + if (item.ImageTags?.["Thumb"]) + return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.["Thumb"]}`; + else + return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`; + } }, [item]); const [progress, setProgress] = useState( diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index e40d895f..66e530bd 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -155,8 +155,12 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => { item && ( - - + {item.Type !== "Program" && ( + <> + + + + )} ), }); @@ -199,8 +203,24 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => { settings, ], queryFn: async () => { - if (!api || !user?.Id || !sessionData || !selectedMediaSource?.Id) + 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; @@ -212,6 +232,8 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => { deviceProfile = old; } + console.log("playbackUrl..."); + const url = await getStreamUrl({ api, userId: user.Id, @@ -224,14 +246,14 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => { subtitleStreamIndex: selectedSubtitleStream, forceDirectPlay: settings?.forceDirectPlay, height: maxBitrate.height, - mediaSourceId: selectedMediaSource.Id, + mediaSourceId: selectedMediaSource?.Id, }); console.info("Stream URL:", url); return url; }, - enabled: !!sessionData && !!api && !!user?.Id && !!item?.Id, + enabled: !!api && !!user?.Id && !!item?.Id, staleTime: 0, }); diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index b81afafe..04d3a12f 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -9,6 +9,10 @@ interface Props extends TouchableOpacityProps { } export const itemRouter = (item: BaseItemDto, from: string) => { + if (item.CollectionType === "livetv") { + return `/(auth)/(tabs)/${from}/livetv`; + } + if (item.Type === "Series") { return `/(auth)/(tabs)/${from}/series/${item.Id}`; } diff --git a/components/home/ScrollingCollectionList.tsx b/components/home/ScrollingCollectionList.tsx index 0d47d112..4cf596dd 100644 --- a/components/home/ScrollingCollectionList.tsx +++ b/components/home/ScrollingCollectionList.tsx @@ -44,6 +44,11 @@ export const ScrollingCollectionList: React.FC = ({ {title} + {isLoading === false && data?.length === 0 && ( + + No items + + )} {isLoading ? ( = ({ )} {item.Type === "Series" && } + {item.Type === "Program" && ( + + )} ))} diff --git a/package.json b/package.json index 7376484b..8d2bb311 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/netinfo": "11.3.1", "@react-native-menu/menu": "^1.1.3", + "@react-navigation/material-top-tabs": "^6.6.14", "@react-navigation/native": "^6.0.2", "@shopify/flash-list": "1.6.4", "@tanstack/react-query": "^5.56.2", @@ -56,6 +57,7 @@ "expo-updates": "~0.25.26", "expo-web-browser": "~13.0.3", "ffmpeg-kit-react-native": "^6.0.2", + "install": "^0.13.0", "jotai": "^2.10.0", "lodash": "^4.17.21", "nativewind": "^2.0.11", @@ -72,11 +74,13 @@ "react-native-ios-context-menu": "^2.5.1", "react-native-ios-utilities": "^4.4.5", "react-native-mmkv": "^2.12.2", + "react-native-pager-view": "^6.4.1", "react-native-reanimated": "~3.15.0", "react-native-reanimated-carousel": "4.0.0-canary.15", "react-native-safe-area-context": "4.10.5", "react-native-screens": "~3.34.0", "react-native-svg": "15.2.0", + "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-uuid": "^2.0.2", "react-native-video": "^6.6.4", diff --git a/utils/jellyfin/media/getStreamUrl.ts b/utils/jellyfin/media/getStreamUrl.ts index c83d3d6b..210df3c6 100644 --- a/utils/jellyfin/media/getStreamUrl.ts +++ b/utils/jellyfin/media/getStreamUrl.ts @@ -7,6 +7,7 @@ import { } from "@jellyfin/sdk/lib/generated-client/models"; import { getAuthHeaders } from "../jellyfin"; import iosFmp4 from "@/utils/profiles/iosFmp4"; +import { getItemsApi, getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; export const getStreamUrl = async ({ api, @@ -19,7 +20,6 @@ export const getStreamUrl = async ({ audioStreamIndex = 0, subtitleStreamIndex = undefined, forceDirectPlay = false, - height, mediaSourceId, }: { api: Api | null | undefined; @@ -27,24 +27,40 @@ export const getStreamUrl = async ({ userId: string | null | undefined; startTimeTicks: number; maxStreamingBitrate?: number; - sessionData: PlaybackInfoResponse; + sessionData?: PlaybackInfoResponse | null; deviceProfile: any; audioStreamIndex?: number; subtitleStreamIndex?: number; forceDirectPlay?: boolean; height?: number; - mediaSourceId: string | null; + mediaSourceId?: string | null; }) => { - if (!api || !userId || !item?.Id || !mediaSourceId) { + if (!api || !userId || !item?.Id) { return null; } + console.log("[0] getStreamUrl ~"); + const itemId = item.Id; - /** - * Build the stream URL for videos - */ - const response = await api.axiosInstance.post( + console.log("[1] getStreamUrl ~"); + const res1 = await api.axiosInstance.post( + `${api.basePath}/Items/${itemId}/PlaybackInfo`, + { + UserId: itemId, + StartTimeTicks: 0, + IsPlayback: true, + AutoOpenLiveStream: true, + MaxStreamingBitrate: 140000000, + }, + { + headers: getAuthHeaders(api), + } + ); + + console.log("[2] getStreamUrl ~", res1.status, res1.statusText); + + const res2 = await api.axiosInstance.post( `${api.basePath}/Items/${itemId}/PlaybackInfo`, { DeviceProfile: deviceProfile, @@ -67,23 +83,23 @@ export const getStreamUrl = async ({ } ); - const mediaSource: MediaSourceInfo = response.data.MediaSources.find( - (source: MediaSourceInfo) => source.Id === mediaSourceId + console.log("[3] getStreamUrl ~"); + + console.log( + `${api.basePath}/Items/${itemId}/PlaybackInfo`, + res2.status, + res2.statusText ); - if (!mediaSource) { - throw new Error("No media source"); - } - - if (!sessionData.PlaySessionId) { - throw new Error("no PlaySessionId"); - } + const mediaSource: MediaSourceInfo = res2.data.MediaSources.find( + (source: MediaSourceInfo) => source.Id === mediaSourceId + ); let url: string | null | undefined; if (mediaSource.SupportsDirectPlay || forceDirectPlay === true) { if (item.MediaType === "Video") { - url = `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData.PlaySessionId}&mediaSourceId=${mediaSource.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`; + url = `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData?.PlaySessionId}&mediaSourceId=${mediaSource.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`; } else if (item.MediaType === "Audio") { const searchParams = new URLSearchParams({ UserId: userId, @@ -95,7 +111,7 @@ export const getStreamUrl = async ({ TranscodingProtocol: "hls", AudioCodec: "aac", api_key: api.accessToken, - PlaySessionId: sessionData.PlaySessionId, + PlaySessionId: sessionData?.PlaySessionId || "", StartTimeTicks: "0", EnableRedirection: "true", EnableRemoteMedia: "false", From 1c20a3453f5336ee1046f43ac0a4673cca0eac63 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 5 Oct 2024 10:24:49 +0200 Subject: [PATCH 2/9] fix: streaming live tv now works --- components/ItemContent.tsx | 15 +++-- components/library/LibraryItemCard.tsx | 52 ++--------------- hooks/useImageColors.ts | 29 +++++++--- providers/PlaybackProvider.tsx | 15 +++-- utils/jellyfin/media/getStreamUrl.ts | 78 +++++++++++++------------- 5 files changed, 86 insertions(+), 103 deletions(-) diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 66e530bd..270829f7 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -126,7 +126,7 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => { }); const [localItem, setLocalItem] = useState(item); - useImageColors(item); + useImageColors({ item }); useEffect(() => { if (item) { @@ -180,10 +180,15 @@ export const ItemContent: React.FC<{ id: string }> = React.memo(({ id }) => { 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, - }); + const playbackData = await getMediaInfoApi(api!).getPlaybackInfo( + { + itemId: item?.Id, + userId: user?.Id, + }, + { + method: "POST", + } + ); return playbackData.data; }, diff --git a/components/library/LibraryItemCard.tsx b/components/library/LibraryItemCard.tsx index 10a925c8..58aa9ad6 100644 --- a/components/library/LibraryItemCard.tsx +++ b/components/library/LibraryItemCard.tsx @@ -15,17 +15,13 @@ import { useEffect, useMemo, useState } from "react"; import { TouchableOpacityProps, View } from "react-native"; import { getColors } from "react-native-image-colors"; import { TouchableItemRouter } from "../common/TouchableItemRouter"; +import { useImageColors } from "@/hooks/useImageColors"; +import { itemThemeColorAtom } from "@/utils/atoms/primaryColor"; interface Props extends TouchableOpacityProps { library: BaseItemDto; } -type LibraryColor = { - dominantColor: string; - averageColor: string; - secondary: string; -}; - type IconName = React.ComponentProps["name"]; const icons: Record = { @@ -48,12 +44,6 @@ export const LibraryItemCard: React.FC = ({ library, ...props }) => { const [user] = useAtom(userAtom); const [settings] = useSettings(); - const [imageInfo, setImageInfo] = useState({ - dominantColor: "#fff", - averageColor: "#fff", - secondary: "#fff", - }); - const url = useMemo( () => getPrimaryImageUrl({ @@ -63,6 +53,10 @@ export const LibraryItemCard: React.FC = ({ library, ...props }) => { [library] ); + // If we want to use image colors for library cards + // const [color] = useAtom(itemThemeColorAtom) + // useImageColors({ url }); + const { data: itemsCount } = useQuery({ queryKey: ["library-count", library.Id], queryFn: async () => { @@ -76,40 +70,6 @@ export const LibraryItemCard: React.FC = ({ library, ...props }) => { }, }); - useEffect(() => { - if (url) { - getColors(url, { - fallback: "#fff", - cache: true, - key: url, - }) - .then((colors) => { - let dominantColor: string = "#fff"; - let averageColor: string = "#fff"; - let secondary: string = "#fff"; - - if (colors.platform === "android") { - dominantColor = colors.dominant; - averageColor = colors.average; - secondary = colors.muted; - } else if (colors.platform === "ios") { - dominantColor = colors.primary; - averageColor = colors.background; - secondary = colors.detail; - } - - setImageInfo({ - dominantColor, - averageColor, - secondary, - }); - }) - .catch((error) => { - console.error("Error getting colors", error); - }); - } - }, [url]); - if (!url) return null; if (settings?.libraryOptions?.display === "row") { diff --git a/hooks/useImageColors.ts b/hooks/useImageColors.ts index 9d0a3264..0a7cf821 100644 --- a/hooks/useImageColors.ts +++ b/hooks/useImageColors.ts @@ -19,19 +19,30 @@ import { getColors } from "react-native-image-colors"; * @param disabled - A boolean flag to disable color extraction. * */ -export const useImageColors = (item?: BaseItemDto | null, disabled = false) => { +export const useImageColors = ({ + item, + url, + disabled, +}: { + item?: BaseItemDto | null; + url?: string | null; + disabled?: boolean; +}) => { const [api] = useAtom(apiAtom); const [, setPrimaryColor] = useAtom(itemThemeColorAtom); const source = useMemo(() => { - if (!api || !item) return; - return getItemImage({ - item, - api, - variant: "Primary", - quality: 80, - width: 300, - }); + if (!api) return; + if (url) return { uri: url }; + else if (item) + return getItemImage({ + item, + api, + variant: "Primary", + quality: 80, + width: 300, + }); + else return; }, [api, item]); useEffect(() => { diff --git a/providers/PlaybackProvider.tsx b/providers/PlaybackProvider.tsx index f3569a03..37286540 100644 --- a/providers/PlaybackProvider.tsx +++ b/providers/PlaybackProvider.tsx @@ -128,10 +128,17 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({ return; } - const res = await getMediaInfoApi(api!).getPlaybackInfo({ - itemId: state.item.Id, - userId: user.Id, - }); + // Support live tv + const res = + state.item.Type !== "Program" + ? await getMediaInfoApi(api!).getPlaybackInfo({ + itemId: state.item.Id, + userId: user.Id, + }) + : await getMediaInfoApi(api!).getPlaybackInfo({ + itemId: state.item.ChannelId!, + userId: user.Id, + }); await postCapabilities({ api, diff --git a/utils/jellyfin/media/getStreamUrl.ts b/utils/jellyfin/media/getStreamUrl.ts index 210df3c6..b8b802e0 100644 --- a/utils/jellyfin/media/getStreamUrl.ts +++ b/utils/jellyfin/media/getStreamUrl.ts @@ -8,6 +8,8 @@ import { import { getAuthHeaders } from "../jellyfin"; import iosFmp4 from "@/utils/profiles/iosFmp4"; import { getItemsApi, getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; +import { isPlainObject } from "lodash"; +import { Alert } from "react-native"; export const getStreamUrl = async ({ api, @@ -39,27 +41,42 @@ export const getStreamUrl = async ({ return null; } - console.log("[0] getStreamUrl ~"); + let mediaSource: MediaSourceInfo | undefined; + let url: string | null | undefined; + + if (item.Type === "Program") { + const res0 = await getMediaInfoApi(api).getPlaybackInfo( + { + userId, + itemId: item.ChannelId!, + }, + { + method: "POST", + params: { + startTimeTicks: 0, + isPlayback: true, + autoOpenLiveStream: true, + maxStreamingBitrate, + audioStreamIndex, + }, + data: { + deviceProfile, + }, + } + ); + + const mediaSourceId = res0.data.MediaSources?.[0].Id; + const liveStreamId = res0.data.MediaSources?.[0].LiveStreamId; + + const transcodeUrl = res0.data.MediaSources?.[0].TranscodingUrl; + + console.log("transcodeUrl", transcodeUrl); + + if (transcodeUrl) return `${api.basePath}${transcodeUrl}`; + } const itemId = item.Id; - console.log("[1] getStreamUrl ~"); - const res1 = await api.axiosInstance.post( - `${api.basePath}/Items/${itemId}/PlaybackInfo`, - { - UserId: itemId, - StartTimeTicks: 0, - IsPlayback: true, - AutoOpenLiveStream: true, - MaxStreamingBitrate: 140000000, - }, - { - headers: getAuthHeaders(api), - } - ); - - console.log("[2] getStreamUrl ~", res1.status, res1.statusText); - const res2 = await api.axiosInstance.post( `${api.basePath}/Items/${itemId}/PlaybackInfo`, { @@ -83,23 +100,13 @@ export const getStreamUrl = async ({ } ); - console.log("[3] getStreamUrl ~"); - - console.log( - `${api.basePath}/Items/${itemId}/PlaybackInfo`, - res2.status, - res2.statusText - ); - - const mediaSource: MediaSourceInfo = res2.data.MediaSources.find( + mediaSource = res2.data.MediaSources.find( (source: MediaSourceInfo) => source.Id === mediaSourceId ); - let url: string | null | undefined; - - if (mediaSource.SupportsDirectPlay || forceDirectPlay === true) { + if (mediaSource?.SupportsDirectPlay || forceDirectPlay === true) { if (item.MediaType === "Video") { - url = `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData?.PlaySessionId}&mediaSourceId=${mediaSource.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`; + url = `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData?.PlaySessionId}&mediaSourceId=${mediaSource?.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`; } else if (item.MediaType === "Audio") { const searchParams = new URLSearchParams({ UserId: userId, @@ -120,18 +127,11 @@ export const getStreamUrl = async ({ api.basePath }/Audio/${itemId}/universal?${searchParams.toString()}`; } - } else if (mediaSource.TranscodingUrl) { + } else if (mediaSource?.TranscodingUrl) { url = `${api.basePath}${mediaSource.TranscodingUrl}`; } if (!url) throw new Error("No url"); - console.log( - mediaSource.VideoType, - mediaSource.Container, - mediaSource.TranscodingContainer, - mediaSource.TranscodingSubProtocol - ); - return url; }; From 200ccc6070816e4690372fe109479d07d310f5c3 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 5 Oct 2024 12:18:47 +0200 Subject: [PATCH 3/9] feat: guide grid view --- app/(auth)/(tabs)/(home)/index.tsx | 2 +- .../(home,libraries,search)/items/page.tsx | 93 ++- .../(home,libraries,search)/livetv/guide.tsx | 141 ++++- app/_layout.tsx | 2 + components/ItemContent.tsx | 542 ++++++++---------- components/livetv/HourHeader.tsx | 43 ++ components/livetv/LiveTVGuideRow.tsx | 91 +++ components/series/SeasonEpisodesCarousel.tsx | 4 +- 8 files changed, 591 insertions(+), 327 deletions(-) create mode 100644 components/livetv/HourHeader.tsx create mode 100644 components/livetv/LiveTVGuideRow.tsx 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]); From 2c14a18e531c44d51fb449ec6a6254ecae1e4b14 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 5 Oct 2024 12:22:35 +0200 Subject: [PATCH 4/9] chore --- .../(tabs)/(home,libraries,search)/livetv/_layout.tsx | 2 -- .../(tabs)/(home,libraries,search)/livetv/guide.tsx | 4 ++-- .../(home,libraries,search)/livetv/schedule.tsx | 11 ----------- .../(tabs)/(home,libraries,search)/livetv/series.tsx | 11 ----------- components/livetv/LiveTVGuideRow.tsx | 2 +- 5 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/schedule.tsx delete mode 100644 app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx index e82e8c2b..e3a776ff 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx @@ -37,8 +37,6 @@ const Layout = () => { - - ); }; diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx index 24b69113..75d3d2b1 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx @@ -15,7 +15,7 @@ import React, { useState } from "react"; import { Dimensions, ScrollView, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -const HOUR_HEIGHT = 30 +const HOUR_HEIGHT = 30; export default function page() { const [api] = useAtom(apiAtom); @@ -102,7 +102,7 @@ export default function page() { }} > {channels?.Items?.map((c, i) => ( - + - Not implemented - - ); -} diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx deleted file mode 100644 index 9bbae531..00000000 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Text } from "@/components/common/Text"; -import React from "react"; -import { View } from "react-native"; - -export default function page() { - return ( - - Not implemented - - ); -} diff --git a/components/livetv/LiveTVGuideRow.tsx b/components/livetv/LiveTVGuideRow.tsx index 59842e1f..2993b2c2 100644 --- a/components/livetv/LiveTVGuideRow.tsx +++ b/components/livetv/LiveTVGuideRow.tsx @@ -50,7 +50,7 @@ export const LiveTVGuideRow = ({ return ( {programsWithPositions?.map((p) => ( - + Date: Sat, 5 Oct 2024 12:38:02 +0200 Subject: [PATCH 5/9] chore --- .../livetv/_layout.tsx | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx index e3a776ff..5b3f0fd5 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx @@ -4,7 +4,8 @@ import type { MaterialTopTabNavigationEventMap, } from "@react-navigation/material-top-tabs"; import { ParamListBase, TabNavigationState } from "@react-navigation/native"; -import { withLayoutContext } from "expo-router"; +import { Stack, withLayoutContext } from "expo-router"; +import React from "react"; const { Navigator } = createMaterialTopTabNavigator(); @@ -17,27 +18,31 @@ export const Tab = withLayoutContext< const Layout = () => { return ( - - - - - - + <> + + + + + + + + ); }; From 95de03f8b1bea48aac27f98d840d7df7f17adba8 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 5 Oct 2024 13:15:33 +0200 Subject: [PATCH 6/9] chore --- .../(home,libraries,search)/items/page.tsx | 2 -- .../(home,libraries,search)/livetv/_layout.tsx | 4 ++-- .../(home,libraries,search)/livetv/channels.tsx | 16 ++++++++-------- .../(home,libraries,search)/livetv/guide.tsx | 6 +----- components/livetv/LiveTVGuideRow.tsx | 5 ++--- components/stacks/NestedTabPageStack.tsx | 2 -- 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx b/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx index d01acb29..071d9127 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx @@ -1,6 +1,5 @@ import { Text } from "@/components/common/Text"; import { ItemContent } from "@/components/ItemContent"; -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"; @@ -14,7 +13,6 @@ import Animated, { useSharedValue, withTiming, } from "react-native-reanimated"; -import { opacity } from "react-native-reanimated/lib/typescript/Colors"; const Page: React.FC = () => { const [api] = useAtom(apiAtom); diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx index 5b3f0fd5..7225e677 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx @@ -1,8 +1,8 @@ -import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs"; import type { - MaterialTopTabNavigationOptions, MaterialTopTabNavigationEventMap, + MaterialTopTabNavigationOptions, } from "@react-navigation/material-top-tabs"; +import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs"; import { ParamListBase, TabNavigationState } from "@react-navigation/native"; import { Stack, withLayoutContext } from "expo-router"; import React from "react"; diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx index f2a73887..dd1c1f85 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/channels.tsx @@ -1,11 +1,9 @@ import { ItemImage } from "@/components/common/ItemImage"; import { Text } from "@/components/common/Text"; -import { ItemPoster } from "@/components/posters/ItemPoster"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { getLiveTvApi } from "@jellyfin/sdk/lib/utils/api"; import { FlashList } from "@shopify/flash-list"; import { useQuery } from "@tanstack/react-query"; -import { Image } from "expo-image"; import { useAtom } from "jotai"; import React from "react"; import { View } from "react-native"; @@ -19,21 +17,23 @@ export default function page() { const { data: channels } = useQuery({ queryKey: ["livetv", "channels"], queryFn: async () => { - if (!api) return []; - const res = await getLiveTvApi(api).getLiveTvChannels({ + const res = await getLiveTvApi(api!).getLiveTvChannels({ startIndex: 0, - fields: ["PrimaryImageAspectRatio"], - limit: 100, + limit: 500, + enableFavoriteSorting: true, userId: user?.Id, + addCurrentProgram: false, + enableUserData: false, + enableImageTypes: ["Primary"], }); - return res.data.Items; + return res.data; }, }); return ( ( diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx index 75d3d2b1..d8092001 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx @@ -1,13 +1,8 @@ import { ItemImage } from "@/components/common/ItemImage"; -import { Text } from "@/components/common/Text"; 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"; @@ -30,6 +25,7 @@ export default function page() { return res.data; }, }); + const { data: channels } = useQuery({ queryKey: ["livetv", "channels"], queryFn: async () => { diff --git a/components/livetv/LiveTVGuideRow.tsx b/components/livetv/LiveTVGuideRow.tsx index 2993b2c2..96c58481 100644 --- a/components/livetv/LiveTVGuideRow.tsx +++ b/components/livetv/LiveTVGuideRow.tsx @@ -1,8 +1,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; -import { View, ScrollView, Dimensions } from "react-native"; -import { ItemImage } from "../common/ItemImage"; +import { useMemo, useRef } from "react"; +import { Dimensions, View } from "react-native"; import { Text } from "../common/Text"; -import { useCallback, useMemo, useRef, useState } from "react"; import { TouchableItemRouter } from "../common/TouchableItemRouter"; export const LiveTVGuideRow = ({ diff --git a/components/stacks/NestedTabPageStack.tsx b/components/stacks/NestedTabPageStack.tsx index d67f224a..12409ab0 100644 --- a/components/stacks/NestedTabPageStack.tsx +++ b/components/stacks/NestedTabPageStack.tsx @@ -1,5 +1,3 @@ -import { Stack } from "expo-router"; -import { Chromecast } from "../Chromecast"; import { HeaderBackButton } from "../common/HeaderBackButton"; const commonScreenOptions = { From 0080874213cfd6b8da6056d415a5435d6000b3d3 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 5 Oct 2024 19:19:28 +0200 Subject: [PATCH 7/9] fix: pages --- .../(home,libraries,search)/livetv/guide.tsx | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx index d8092001..101b3fe8 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx @@ -6,17 +6,21 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; 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 React, { useCallback, useMemo, useState } from "react"; +import { Button, Dimensions, ScrollView, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; const HOUR_HEIGHT = 30; +const ITEMS_PER_PAGE = 20; + +const MemoizedLiveTVGuideRow = React.memo(LiveTVGuideRow); export default function page() { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const insets = useSafeAreaInsets(); const [date, setDate] = useState(new Date()); + const [currentPage, setCurrentPage] = useState(1); const { data: guideInfo } = useQuery({ queryKey: ["livetv", "guideInfo"], @@ -27,11 +31,11 @@ export default function page() { }); const { data: channels } = useQuery({ - queryKey: ["livetv", "channels"], + queryKey: ["livetv", "channels", currentPage], queryFn: async () => { const res = await getLiveTvApi(api!).getLiveTvChannels({ - startIndex: 0, - limit: 500, + startIndex: (currentPage - 1) * ITEMS_PER_PAGE, + limit: ITEMS_PER_PAGE, enableFavoriteSorting: true, userId: user?.Id, addCurrentProgram: false, @@ -43,7 +47,7 @@ export default function page() { }); const { data: programs } = useQuery({ - queryKey: ["livetv", "programs", date], + queryKey: ["livetv", "programs", date, currentPage], queryFn: async () => { const startOfDay = new Date(date); startOfDay.setHours(0, 0, 0, 0); @@ -74,8 +78,18 @@ export default function page() { const screenWidth = Dimensions.get("window").width; + const memoizedChannels = useMemo(() => channels?.Items || [], [channels]); + const [scrollX, setScrollX] = useState(0); + const handleNextPage = useCallback(() => { + setCurrentPage((prev) => prev + 1); + }, []); + + const handlePrevPage = useCallback(() => { + setCurrentPage((prev) => Math.max(1, prev - 1)); + }, []); + return ( + +