import { AudioTrackSelector } from "@/components/AudioTrackSelector"; import { Bitrate, BitrateSelector } from "@/components/BitrateSelector"; import { DownloadSingleItem } from "@/components/DownloadItem"; import { OverviewText } from "@/components/OverviewText"; import { ParallaxScrollView } from "@/components/ParallaxPage"; const PlayButton = !Platform.isTV ? require("@/components/PlayButton") : null; import { PlayedStatus } from "@/components/PlayedStatus"; import { SimilarItems } from "@/components/SimilarItems"; import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector"; import { ItemImage } from "@/components/common/ItemImage"; import { CastAndCrew } from "@/components/series/CastAndCrew"; import { CurrentSeries } from "@/components/series/CurrentSeries"; import { SeasonEpisodesCarousel } from "@/components/series/SeasonEpisodesCarousel"; import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings"; import { useImageColors } from "@/hooks/useImageColors"; import { useOrientation } from "@/hooks/useOrientation"; import { apiAtom } from "@/providers/JellyfinProvider"; import { SubtitleHelper } from "@/utils/SubtitleHelper"; import { useSettings } from "@/utils/atoms/settings"; import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; import { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client/models"; import { Image } from "expo-image"; import { useNavigation } from "expo-router"; import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useAtom } from "jotai"; import React, { useEffect, useMemo, useState } from "react"; import { Platform, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; const Chromecast = !Platform.isTV ? require("./Chromecast") : null; import { ItemHeader } from "./ItemHeader"; import { ItemTechnicalDetails } from "./ItemTechnicalDetails"; import { MediaSourceSelector } from "./MediaSourceSelector"; import { MoreMoviesWithActor } from "./MoreMoviesWithActor"; import { AddToFavorites } from "./AddToFavorites"; export type SelectedOptions = { bitrate: Bitrate; mediaSource: MediaSourceInfo | undefined; audioIndex: number | undefined; subtitleIndex: number; }; export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( ({ item }) => { const [api] = useAtom(apiAtom); const [settings] = useSettings(); const { orientation } = useOrientation(); const navigation = useNavigation(); const insets = useSafeAreaInsets(); useImageColors({ item }); const [loadingLogo, setLoadingLogo] = useState(true); const [headerHeight, setHeaderHeight] = useState(350); const [selectedOptions, setSelectedOptions] = useState< SelectedOptions | undefined >(undefined); const { defaultAudioIndex, defaultBitrate, defaultMediaSource, defaultSubtitleIndex, } = useDefaultPlaySettings(item, settings); // Needs to automatically change the selected to the default values for default indexes. useEffect(() => { setSelectedOptions(() => ({ bitrate: defaultBitrate, mediaSource: defaultMediaSource, subtitleIndex: defaultSubtitleIndex ?? -1, audioIndex: defaultAudioIndex, })); }, [ defaultAudioIndex, defaultBitrate, defaultSubtitleIndex, defaultMediaSource, ]); if (!Platform.isTV) { useEffect(() => { navigation.setOptions({ headerRight: () => item && ( {item.Type !== "Program" && ( )} ), }); }, [item]); } useEffect(() => { if (orientation !== ScreenOrientation.OrientationLock.PORTRAIT_UP) setHeaderHeight(230); else if (item.Type === "Movie") setHeaderHeight(500); else setHeaderHeight(350); }, [item.Type, orientation]); const logoUrl = useMemo(() => getLogoImageUrlById({ api, item }), [item]); const loading = useMemo(() => { return Boolean(logoUrl && loadingLogo); }, [loadingLogo, logoUrl]); const [isTranscoding, setIsTranscoding] = useState(false); const [previouslyChosenSubtitleIndex, setPreviouslyChosenSubtitleIndex] = useState(selectedOptions?.subtitleIndex); useEffect(() => { const isTranscoding = Boolean(selectedOptions?.bitrate.value); if (isTranscoding) { setPreviouslyChosenSubtitleIndex(selectedOptions?.subtitleIndex); const subHelper = new SubtitleHelper( selectedOptions?.mediaSource?.MediaStreams ?? [] ); const newSubtitleIndex = subHelper.getMostCommonSubtitleByName( selectedOptions?.subtitleIndex ); setSelectedOptions((prev) => ({ ...prev!, subtitleIndex: newSubtitleIndex ?? -1, })); } if (!isTranscoding && previouslyChosenSubtitleIndex !== undefined) { setSelectedOptions((prev) => ({ ...prev!, subtitleIndex: previouslyChosenSubtitleIndex, })); } setIsTranscoding(isTranscoding); }, [selectedOptions?.bitrate]); if (!selectedOptions) return null; return ( } logo={ <> {logoUrl ? ( setLoadingLogo(false)} onError={() => setLoadingLogo(false)} /> ) : null} } > {item.Type !== "Program" && ( setSelectedOptions( (prev) => prev && { ...prev, bitrate: val } ) } selected={selectedOptions.bitrate} /> setSelectedOptions( (prev) => prev && { ...prev, mediaSource: val, } ) } selected={selectedOptions.mediaSource} /> { setSelectedOptions( (prev) => prev && { ...prev, audioIndex: val, } ); }} selected={selectedOptions.audioIndex} /> setSelectedOptions( (prev) => prev && { ...prev, subtitleIndex: val, } ) } selected={selectedOptions.subtitleIndex} /> )} {!Platform.isTV && ( )} {item.Type === "Episode" && ( )} {item.Type !== "Program" && ( <> {item.Type === "Episode" && ( )} {item.People && item.People.length > 0 && ( {item.People.slice(0, 3).map((person, idx) => ( ))} )} )} ); } );