import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { usePlayback } from "@/providers/PlaybackProvider"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; import { getAuthHeaders } from "@/utils/jellyfin/jellyfin"; import { writeToLog } from "@/utils/log"; import { Ionicons } from "@expo/vector-icons"; import { BlurView } from "expo-blur"; import { useRouter, useSegments } from "expo-router"; import { useAtom } from "jotai"; import { useEffect, useMemo, useRef, useState } from "react"; import { Alert, Platform, TouchableOpacity, View } from "react-native"; import Animated, { useAnimatedStyle, useSharedValue, withDecay, withTiming, } from "react-native-reanimated"; import Video from "react-native-video"; import { Text } from "./common/Text"; import { Loader } from "./Loader"; import { Dimensions } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Bubble, Slider } from "react-native-awesome-slider"; import { runtimeTicksToMinutes } from "@/utils/time"; export const CurrentlyPlayingBar: React.FC = () => { const segments = useSegments(); const { currentlyPlaying, pauseVideo, playVideo, stopPlayback, setVolume, setIsPlaying, isPlaying, videoRef, presentFullscreenPlayer, onProgress, } = usePlayback(); const router = useRouter(); const insets = useSafeAreaInsets(); const [api] = useAtom(apiAtom); const [size, setSize] = useState<"full" | "small">("small"); const screenHeight = Dimensions.get("window").height; const screenWiidth = Dimensions.get("window").width; const from = useMemo(() => segments[2], [segments]); const backgroundValues = useSharedValue({ bottom: 70, height: 80, padding: 0, width: screenWiidth - 100, left: 50, }); const videoValues = useSharedValue({ bottom: 90, height: 70, width: 125, left: 16, }); const buttonsValues = useSharedValue({ bottom: 90, opacity: 1, right: 16, }); const textValues = useSharedValue({ height: 70, bottom: 90, left: 149, width: 140, }); const animatedTextStyle = useAnimatedStyle(() => { return { bottom: withTiming(textValues.value.bottom, { duration: 500 }), left: withTiming(textValues.value.left, { duration: 500 }), height: withTiming(textValues.value.height, { duration: 500 }), width: withTiming(textValues.value.width, { duration: 500 }), }; }); const animatedButtonStyle = useAnimatedStyle(() => { return { bottom: withTiming(buttonsValues.value.bottom, { duration: 500 }), opacity: withTiming(buttonsValues.value.opacity, { duration: 500 }), right: withTiming(buttonsValues.value.right, { duration: 500 }), }; }); const animatedBackgroundStyle = useAnimatedStyle(() => { return { bottom: withTiming(backgroundValues.value.bottom, { duration: 500 }), width: withTiming(backgroundValues.value.width, { duration: 500 }), height: withTiming(backgroundValues.value.height, { duration: 500 }), padding: withTiming(backgroundValues.value.padding, { duration: 500 }), left: withTiming(backgroundValues.value.left, { duration: 500 }), }; }); const poster = useMemo(() => { if (currentlyPlaying?.item.Type === "Audio") return `${api?.basePath}/Items/${currentlyPlaying.item.AlbumId}/Images/Primary?tag=${currentlyPlaying.item.AlbumPrimaryImageTag}&quality=90&maxHeight=200&maxWidth=200`; else return getBackdropUrl({ api, item: currentlyPlaying?.item, quality: 70, width: 200, }); }, [currentlyPlaying?.item.Id, api]); const startPosition = useMemo( () => currentlyPlaying?.item?.UserData?.PlaybackPositionTicks ? Math.round( currentlyPlaying?.item.UserData.PlaybackPositionTicks / 10000 ) : 0, [currentlyPlaying?.item] ); const videoSource = useMemo(() => { if (!api || !currentlyPlaying || !poster) return null; return { uri: currentlyPlaying.url, isNetwork: true, startPosition, headers: getAuthHeaders(api), metadata: { artist: currentlyPlaying.item?.AlbumArtist ? currentlyPlaying.item?.AlbumArtist : undefined, title: currentlyPlaying.item?.Name || "Unknown", description: currentlyPlaying.item?.Overview ? currentlyPlaying.item?.Overview : undefined, imageUri: poster, subtitle: currentlyPlaying.item?.Album ? currentlyPlaying.item?.Album : undefined, }, }; }, [currentlyPlaying, startPosition, api, poster]); const animatedVideoStyle = useAnimatedStyle(() => { return { height: withTiming(videoValues.value.height, { duration: 500 }), width: withTiming(videoValues.value.width, { duration: 500 }), bottom: withTiming(videoValues.value.bottom, { duration: 500 }), left: withTiming(videoValues.value.left, { duration: 500 }), }; }); useEffect(() => { if (size === "full") { backgroundValues.value = { bottom: 0, height: screenHeight, padding: 0, width: screenWiidth, left: 0, }; buttonsValues.value = { bottom: screenHeight - insets.top - 38, opacity: 1, right: 16, }; videoValues.value = { bottom: 0, height: screenHeight, width: screenWiidth, left: 0, }; textValues.value = { bottom: 78, height: 64, left: 16, width: 140, }; } else { backgroundValues.value = { bottom: 70, height: 80, padding: 0, width: screenWiidth - 16, left: 8, }; buttonsValues.value = { bottom: 90, opacity: 1, right: 16, }; videoValues.value = { bottom: 78, height: 64, width: 113, left: 16, }; textValues.value = { bottom: 78, height: 64, left: 141, width: 140, }; } }, [size, screenHeight, insets]); const progress = useSharedValue(0); const min = useSharedValue(0); const max = useSharedValue(currentlyPlaying?.item.RunTimeTicks || 0); const sliding = useRef(false); useEffect(() => { max.value = currentlyPlaying?.item.RunTimeTicks || 0; }, [currentlyPlaying?.item.RunTimeTicks]); if (!api || !currentlyPlaying) return null; return ( <> { if (currentlyPlaying.item?.Type === "Audio") { router.push( // @ts-ignore `/(auth)/(tabs)/${from}/albums/${currentlyPlaying.item.AlbumId}` ); } else { router.push( // @ts-ignore `/(auth)/(tabs)/${from}/items/page?id=${currentlyPlaying.item?.Id}` ); } }} > {currentlyPlaying.item?.Name} {currentlyPlaying.item?.Type === "Episode" && ( { router.push( // @ts-ignore `/(auth)/(tabs)/${from}/series/${currentlyPlaying.item.SeriesId}` ); }} className="text-xs opacity-50" > {currentlyPlaying.item.SeriesName} )} {currentlyPlaying.item?.Type === "Movie" && ( {currentlyPlaying.item?.ProductionYear} )} {currentlyPlaying.item?.Type === "Audio" && ( { router.push(`/albums/${currentlyPlaying.item?.AlbumId}`); }} > {currentlyPlaying.item?.Album} )} { if (size === "small") setSize("full"); else setSize("small"); }} className="aspect-square rounded flex flex-col items-center justify-center p-2" > { stopPlayback(); }} className="aspect-square rounded flex flex-col items-center justify-center p-2" > {videoSource && ( {size === "full" && ( { sliding.current = true; }} onSlidingComplete={(val) => { const tick = Math.floor(val); videoRef.current?.seek(tick / 10000000); sliding.current = false; }} onValueChange={(val) => { const tick = Math.floor(val); progress.value = tick; }} containerStyle={{ borderRadius: 100, }} bubble={(s) => runtimeTicksToMinutes(s)} sliderHeight={12} thumbWidth={0} progress={progress} minimumValue={min} maximumValue={max} /> {runtimeTicksToMinutes(progress.value)} )} {/* */} ); };