import React, { useEffect, useMemo, useRef, useState } from "react"; import { ActivityIndicator, Switch, TouchableOpacity, View, } from "react-native"; import * as DropdownMenu from "zeego/dropdown-menu"; import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; import { useQuery } from "@tanstack/react-query"; import { useAtom } from "jotai"; import Video, { OnBufferData, OnPlaybackStateChangedData, OnProgressData, OnVideoErrorData, VideoRef, } from "react-native-video"; import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider"; import { getBackdrop, getStreamUrl, getUserItemData, reportPlaybackProgress, reportPlaybackStopped, } from "@/utils/jellyfin"; import { Ionicons } from "@expo/vector-icons"; import { Button } from "./Button"; import { runtimeTicksToMinutes } from "@/utils/time"; import { Text } from "./common/Text"; type VideoPlayerProps = { itemId: string; }; const BITRATES = [ { key: "Max", value: 140000000, }, { key: "10 Mb/s", value: 10000000, }, { key: "4 Mb/s", value: 4000000, }, { key: "2 Mb/s", value: 2000000, }, { key: "1 Mb/s", value: 1000000, }, { key: "500 Kb/s", value: 500000, }, ]; export const VideoPlayer: React.FC = ({ itemId }) => { const videoRef = useRef(null); const [showPoster, setShowPoster] = useState(true); const [isPlaying, setIsPlaying] = useState(false); const [buffering, setBuffering] = useState(false); const [maxBitrate, setMaxbitrate] = useState(140000000); const [paused, setPaused] = useState(true); const [forceTranscoding, setForceTranscoding] = useState(false); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const {} = useJellyfin(); const { data: sessionData } = useQuery({ queryKey: ["sessionData", itemId], queryFn: async () => { const playbackData = await getMediaInfoApi(api!).getPlaybackInfo({ itemId, userId: user?.Id, }); return playbackData.data; }, enabled: !!itemId && !!api && !!user?.Id, staleTime: Infinity, }); const { data: item } = useQuery({ queryKey: ["item", itemId], queryFn: async () => await getUserItemData({ api, userId: user?.Id, itemId, }), enabled: !!itemId && !!api, staleTime: Infinity, }); const { data: playbackURL } = useQuery({ queryKey: ["playbackUrl", itemId, maxBitrate, forceTranscoding], queryFn: async () => { if (!api || !user?.Id || !sessionData) return null; const url = await getStreamUrl({ api, userId: user.Id, item, startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0, maxStreamingBitrate: maxBitrate, sessionData, forceTranscoding: forceTranscoding, }); console.log("Transcode URL:", url); return url; }, enabled: !!itemId && !!api && !!user?.Id && !!item && !!sessionData, staleTime: 0, }); const [progress, setProgress] = useState(0); const onProgress = ({ currentTime, playableDuration, seekableDuration, }: OnProgressData) => { setProgress(currentTime * 10000000); reportPlaybackProgress({ api, itemId: itemId, positionTicks: currentTime * 10000000, sessionId: sessionData?.PlaySessionId, }); }; const onSeek = ({ currentTime, seekTime, }: { currentTime: number; seekTime: number; }) => { // console.log("Seek to time: ", seekTime); }; const onError = (error: OnVideoErrorData) => { console.log("Video Error: ", JSON.stringify(error.error)); }; const onBuffer = (error: OnBufferData) => { console.log("Video buffering: ", error.isBuffering); }; const play = () => { if (videoRef.current) { videoRef.current.resume(); } }; const startPosition = useMemo(() => { return Math.round((item?.UserData?.PlaybackPositionTicks || 0) / 10000); }, [item]); useEffect(() => { if (videoRef.current) { videoRef.current.pause(); } }, []); const enableVideo = useMemo(() => { return ( playbackURL !== undefined && item !== undefined && item !== null && startPosition !== undefined && sessionData !== undefined ); }, [playbackURL, item, startPosition, sessionData]); return ( {enableVideo && ( ); };