import { Ionicons } from "@expo/vector-icons"; import { BottomSheetBackdrop, type BottomSheetBackdropProps, BottomSheetModal, BottomSheetScrollView, } from "@gorhom/bottom-sheet"; import type { MediaSourceInfo, MediaStream, } from "@jellyfin/sdk/lib/generated-client"; import type React from "react"; import { useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { TouchableOpacity, View } from "react-native"; import { formatBitrate } from "@/utils/bitrate"; import { Badge } from "./Badge"; import { Text } from "./common/Text"; interface Props { source?: MediaSourceInfo; } export const ItemTechnicalDetails: React.FC = ({ source }) => { const bottomSheetModalRef = useRef(null); const { t } = useTranslation(); return ( {t("item_card.video")} bottomSheetModalRef.current?.present()}> {t("item_card.more_details")} ( )} > {t("item_card.video")} {t("item_card.audio")} stream.Type === "Audio", ) || [] } /> {t("item_card.subtitles")} stream.Type === "Subtitle", ) || [] } /> ); }; const SubtitleStreamInfo = ({ subtitleStreams, }: { subtitleStreams: MediaStream[]; }) => { return ( {subtitleStreams.map((stream, _index) => ( {stream.DisplayTitle} } text={stream.Language} /> } /> ))} ); }; const AudioStreamInfo = ({ audioStreams }: { audioStreams: MediaStream[] }) => { return ( {audioStreams.map((audioStreams, index) => ( {audioStreams.DisplayTitle} } text={audioStreams.Language} /> } text={audioStreams.Codec} /> } text={audioStreams.ChannelLayout} /> } text={formatBitrate(audioStreams.BitRate)} /> ))} ); }; const VideoStreamInfo = ({ source }: { source?: MediaSourceInfo }) => { const videoStream = useMemo(() => { return source?.MediaStreams?.find((stream) => stream.Type === "Video") as | MediaStream | undefined; }, [source?.MediaStreams]); if (!source || !videoStream) return null; // Dolby Vision video check const isDolbyVision = videoStream.VideoRangeType === "DOVI" || videoStream.DvVersionMajor != null || videoStream.DvVersionMinor != null; return ( } text={formatFileSize(source.Size)} /> } text={`${videoStream.Width}x${videoStream.Height}`} /> {isDolbyVision && ( } text={"DV"} /> )} } text={videoStream.VideoRange} /> } text={videoStream.Codec} /> } text={formatBitrate(videoStream.BitRate)} /> } text={ videoStream.AverageFrameRate != null ? `${videoStream.AverageFrameRate.toFixed(0)} fps` : "" } /> ); }; const formatFileSize = (bytes?: number | null) => { if (!bytes) return "N/A"; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; if (bytes === 0) return "0 Byte"; const i = Number.parseInt( Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10, ); return `${Math.round((bytes / 1024 ** i) * 100) / 100} ${sizes[i]}`; };