import React, { useCallback, useMemo } from "react"; import { View, TouchableOpacity } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import * as DropdownMenu from "zeego/dropdown-menu"; import { useControlContext } from "./contexts/ControlContext"; import { useVideoContext } from "./contexts/VideoContext"; import { EmbeddedSubtitle, ExternalSubtitle, TranscodedSubtitle, } from "./types"; import { useAtomValue } from "jotai"; import { apiAtom } from "@/providers/JellyfinProvider"; import { useLocalSearchParams, useRouter } from "expo-router"; interface DropdownViewProps { showControls: boolean; } const DropdownView: React.FC = ({ showControls }) => { const router = useRouter(); const api = useAtomValue(apiAtom); const ControlContext = useControlContext(); const mediaSource = ControlContext?.mediaSource; const item = ControlContext?.item; const isVideoLoaded = ControlContext?.isVideoLoaded; const videoContext = useVideoContext(); const { subtitleTracks, audioTracks, setSubtitleURL, setSubtitleTrack, setAudioTrack, } = videoContext; const allSubtitleTracksForDirectPlay = useMemo(() => { if (mediaSource?.TranscodingUrl) return null; const embeddedSubs = subtitleTracks ?.map((s) => ({ name: s.name, index: s.index, deliveryUrl: undefined, })) .filter((sub) => !sub.name.endsWith("[External]")) || []; const externalSubs = mediaSource?.MediaStreams?.filter( (stream) => stream.Type === "Subtitle" && !!stream.DeliveryUrl ).map((s) => ({ name: s.DisplayTitle! + " [External]", index: s.Index!, deliveryUrl: s.DeliveryUrl, })) || []; // Combine embedded and unique external subs return [...embeddedSubs, ...externalSubs] as ( | EmbeddedSubtitle | ExternalSubtitle )[]; }, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams]); // const audioForTranscodingStream = mediaSource?.MediaStreams?.filter( // (x) => x.Type === "Audio" // ).map((x) => ({ // name: x.DisplayTitle!, // index: x.Index!, // })); // Only used for transcoding streams. const { subtitleIndex: subtitleIndexStr, audioIndex, bitrateValue, } = useLocalSearchParams<{ itemId: string; audioIndex: string; subtitleIndex: string; mediaSourceId: string; bitrateValue: string; }>(); // Either its on a text subtitle or its on not on any subtitle therefore it should show all the embedded HLS subtitles. const isOnTextSubtitle = mediaSource?.MediaStreams?.find( (x) => x.Index === parseInt(subtitleIndexStr) && x.IsTextSubtitleStream ) || subtitleIndexStr === "-1"; // TODO: Add support for text sorting subtitles renaming. const allSubtitleTracksForTranscodingStream = useMemo(() => { const allSubs = mediaSource?.MediaStreams?.filter((x) => x.Type === "Subtitle") ?? []; if (isOnTextSubtitle) { const textSubtitles = subtitleTracks?.map((s) => ({ name: s.name, index: s.index, IsTextSubtitleStream: true, })) || []; const imageSubtitles = allSubs .filter((x) => !x.IsTextSubtitleStream) .map( (x) => ({ name: x.DisplayTitle!, index: x.Index!, IsTextSubtitleStream: x.IsTextSubtitleStream, } as TranscodedSubtitle) ); return [...textSubtitles, ...imageSubtitles]; } const transcodedSubtitle: TranscodedSubtitle[] = allSubs.map((x) => ({ name: x.DisplayTitle!, index: x.Index!, IsTextSubtitleStream: x.IsTextSubtitleStream!, })); return [ { name: 'Disable', index: -1, IsTextSubtitleStream: true } as TranscodedSubtitle, ...transcodedSubtitle ]; }, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams]); const ChangeTranscodingSubtitle = useCallback( (subtitleIndex: number) => { const queryParams = new URLSearchParams({ itemId: item.Id ?? "", // Ensure itemId is a string audioIndex: audioIndex?.toString() ?? "", subtitleIndex: subtitleIndex?.toString() ?? "", mediaSourceId: mediaSource?.Id ?? "", // Ensure mediaSourceId is a string bitrateValue: bitrateValue, }).toString(); // @ts-expect-error router.replace(`player/player?${queryParams}`); }, [mediaSource] ); return ( Subtitle {!mediaSource?.TranscodingUrl && allSubtitleTracksForDirectPlay?.map((sub, idx: number) => ( { if ("deliveryUrl" in sub && sub.deliveryUrl) { setSubtitleURL && setSubtitleURL( api?.basePath + sub.deliveryUrl, sub.name ); console.log( "Set external subtitle: ", api?.basePath + sub.deliveryUrl ); } else { console.log("Set sub index: ", sub.index); setSubtitleTrack && setSubtitleTrack(sub.index); } console.log("Subtitle: ", sub); }} > {sub.name} ))} {mediaSource?.TranscodingUrl && allSubtitleTracksForTranscodingStream?.map( (sub, idx: number) => ( { if (subtitleIndexStr === sub.index.toString()) return; if (sub.IsTextSubtitleStream && isOnTextSubtitle) { setSubtitleTrack && setSubtitleTrack(sub.index); return; } ChangeTranscodingSubtitle(sub.index); }} > {sub.name} ) )} Audio {audioTracks?.map((track, idx: number) => ( { setAudioTrack && setAudioTrack(track.index); }} > {track.name} ))} ); }; export default DropdownView;