import { TrackInfo } from "@/modules/VlcPlayer.types"; import React, { createContext, useContext, useState, ReactNode, useEffect, useMemo } from "react"; import { useControlContext } from "./ControlContext"; import { Track } from "../types"; import { router, useLocalSearchParams } from "expo-router"; interface VideoContextProps { audioTracks: Track[] | null; subtitleTracks: Track[] | null; setAudioTrack: ((index: number) => void) | undefined; setSubtitleTrack: ((index: number) => void) | undefined; setSubtitleURL: ((url: string, customName: string) => void) | undefined; } const VideoContext = createContext(undefined); interface VideoProviderProps { children: ReactNode; getAudioTracks: (() => Promise) | (() => TrackInfo[]) | undefined; getSubtitleTracks: (() => Promise) | (() => TrackInfo[]) | undefined; setAudioTrack: ((index: number) => void) | undefined; setSubtitleTrack: ((index: number) => void) | undefined; setSubtitleURL: ((url: string, customName: string) => void) | undefined; } export const VideoProvider: React.FC = ({ children, getSubtitleTracks, getAudioTracks, setSubtitleTrack, setSubtitleURL, setAudioTrack, }) => { const [audioTracks, setAudioTracks] = useState(null); const [subtitleTracks, setSubtitleTracks] = useState(null); const ControlContext = useControlContext(); const isVideoLoaded = ControlContext?.isVideoLoaded; const mediaSource = ControlContext?.mediaSource; const allSubs = mediaSource?.MediaStreams?.filter((s) => s.Type === "Subtitle") || []; const { itemId, audioIndex, bitrateValue, subtitleIndex } = useLocalSearchParams<{ itemId: string; audioIndex: string; subtitleIndex: string; mediaSourceId: string; bitrateValue: string; }>(); const onTextBasedSubtitle = useMemo( () => allSubs.find((s) => s.Index?.toString() === subtitleIndex && s.IsTextSubtitleStream) || subtitleIndex === "-1", [allSubs, subtitleIndex] ); const setPlayerParams = ({ chosenAudioIndex = audioIndex, chosenSubtitleIndex = subtitleIndex, }: { chosenAudioIndex?: string; chosenSubtitleIndex?: string; }) => { console.log("chosenSubtitleIndex", chosenSubtitleIndex); const queryParams = new URLSearchParams({ itemId: itemId ?? "", audioIndex: chosenAudioIndex, subtitleIndex: chosenSubtitleIndex, mediaSourceId: mediaSource?.Id ?? "", bitrateValue: bitrateValue, }).toString(); //@ts-ignore router.replace(`player/direct-player?${queryParams}`); }; const setTrackParams = (type: "audio" | "subtitle", index: number, serverIndex: number) => { const setTrack = type === "audio" ? setAudioTrack : setSubtitleTrack; const paramKey = type === "audio" ? "audioIndex" : "subtitleIndex"; // If we're transcoding and we're going from a image based subtitle // to a text based subtitle, we need to change the player params. const shouldChangePlayerParams = type === "subtitle" && mediaSource?.TranscodingUrl && !onTextBasedSubtitle; console.log("Set player params", index, serverIndex); if (shouldChangePlayerParams) { setPlayerParams({ chosenSubtitleIndex: serverIndex.toString(), }); return; } setTrack && setTrack(index); router.setParams({ [paramKey]: serverIndex.toString(), }); }; useEffect(() => { const fetchTracks = async () => { if (getSubtitleTracks) { const subtitleData = await getSubtitleTracks(); // Step 1: Move external subs to the end, because VLC puts external subs at the end const sortedSubs = allSubs.sort((a, b) => Number(a.IsExternal) - Number(b.IsExternal)); // Step 2: Apply VLC indexing logic let textSubIndex = 0; const processedSubs: Track[] = sortedSubs?.map((sub) => { // Always increment for non-transcoding subtitles // Only increment for text-based subtitles when transcoding const shouldIncrement = !mediaSource?.TranscodingUrl || sub.IsTextSubtitleStream; const vlcIndex = subtitleData?.at(textSubIndex)?.index ?? -1; const finalIndex = shouldIncrement ? vlcIndex : sub.Index ?? -1; if (shouldIncrement) textSubIndex++; return { name: sub.DisplayTitle || "Undefined Subtitle", index: sub.Index ?? -1, setTrack: () => shouldIncrement ? setTrackParams("subtitle", finalIndex, sub.Index ?? -1) : setPlayerParams({ chosenSubtitleIndex: sub.Index?.toString(), }), }; }); // Step 3: Restore the original order const subtitles: Track[] = processedSubs.sort((a, b) => a.index - b.index); // Add a "Disable Subtitles" option subtitles.unshift({ name: "Disable", index: -1, setTrack: () => !mediaSource?.TranscodingUrl || onTextBasedSubtitle ? setTrackParams("subtitle", -1, -1) : setPlayerParams({ chosenSubtitleIndex: "-1" }), }); setSubtitleTracks(subtitles); } if (getAudioTracks) { const audioData = await getAudioTracks(); const allAudio = mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") || []; const audioTracks: Track[] = allAudio?.map((audio, idx) => { if (!mediaSource?.TranscodingUrl) { const vlcIndex = audioData?.at(idx)?.index ?? -1; return { name: audio.DisplayTitle ?? "Undefined Audio", index: audio.Index ?? -1, setTrack: () => setTrackParams("audio", vlcIndex, audio.Index ?? -1), }; } return { name: audio.DisplayTitle ?? "Undefined Audio", index: audio.Index ?? -1, setTrack: () => setPlayerParams({ chosenAudioIndex: audio.Index?.toString() }), }; }); setAudioTracks(audioTracks); } }; fetchTracks(); }, [isVideoLoaded, getAudioTracks, getSubtitleTracks]); return ( {children} ); }; export const useVideoContext = () => { const context = useContext(VideoContext); if (context === undefined) { throw new Error("useVideoContext must be used within a VideoProvider"); } return context; };