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; };