From 49c95a091cef9e81e6fdeb5859e0b0e61090b4d2 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Mon, 12 Aug 2024 14:03:22 +0200 Subject: [PATCH] feat: currently playing floating bar --- app/(auth)/items/[id]/page.tsx | 78 +++++++- app/_layout.tsx | 29 ++- app/login.tsx | 1 - components/BitrateSelector.tsx | 75 +++++++ components/CurrentlyPlayingBar.tsx | 288 +++++++++++++++++++++++++++ components/DownloadItem.tsx | 6 +- components/PlayButton.tsx | 34 ++++ components/VideoPlayer.tsx | 39 ++-- components/common/Input.tsx | 8 +- components/downloads/EpisodeCard.tsx | 40 +--- components/downloads/MovieCard.tsx | 46 +---- 11 files changed, 519 insertions(+), 125 deletions(-) create mode 100644 components/BitrateSelector.tsx create mode 100644 components/CurrentlyPlayingBar.tsx create mode 100644 components/PlayButton.tsx diff --git a/app/(auth)/items/[id]/page.tsx b/app/(auth)/items/[id]/page.tsx index ddbb4e41..4e85ec07 100644 --- a/app/(auth)/items/[id]/page.tsx +++ b/app/(auth)/items/[id]/page.tsx @@ -11,7 +11,7 @@ import { useQuery } from "@tanstack/react-query"; import { Image } from "expo-image"; import { router, useLocalSearchParams } from "expo-router"; import { useAtom } from "jotai"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { ActivityIndicator, ScrollView, @@ -22,16 +22,29 @@ import { ParallaxScrollView } from "../../../../components/ParallaxPage"; import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; +import { PlayButton } from "@/components/PlayButton"; +import { Bitrate, BitrateSelector } from "@/components/BitrateSelector"; +import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; +import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; +import { useCastDevice } from "react-native-google-cast"; +import { chromecastProfile } from "@/utils/profiles/chromecast"; +import ios12 from "@/utils/profiles/ios12"; +import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar"; const page: React.FC = () => { const local = useLocalSearchParams(); const { id } = local as { id: string }; - const [playbackURL, setPlaybackURL] = useState(null); - const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); + const castDevice = useCastDevice(); + + const [maxBitrate, setMaxBitrate] = useState({ + key: "Max", + value: undefined, + }); + const { data: item, isLoading: l1 } = useQuery({ queryKey: ["item", id], queryFn: async () => @@ -60,6 +73,52 @@ const page: React.FC = () => { [item], ); + const { data: sessionData } = useQuery({ + queryKey: ["sessionData", item?.Id], + queryFn: async () => { + if (!api || !user?.Id || !item?.Id) return null; + const playbackData = await getMediaInfoApi(api!).getPlaybackInfo({ + itemId: item?.Id, + userId: user?.Id, + }); + + return playbackData.data; + }, + enabled: !!item?.Id && !!api && !!user?.Id, + staleTime: 0, + }); + + const { data: playbackUrl } = useQuery({ + queryKey: ["playbackUrl", item?.Id, maxBitrate, castDevice], + 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.value, + sessionData, + deviceProfile: castDevice?.deviceId ? chromecastProfile : ios12, + }); + + return url; + }, + enabled: !!sessionData, + staleTime: 0, + }); + + const [cp, setCp] = useAtom(currentlyPlayingItemAtom); + + const onPressPlay = useCallback(() => { + if (!playbackUrl || !item) return; + setCp({ + item, + playbackUrl, + }); + }, [playbackUrl, item]); + if (l1) return ( @@ -151,20 +210,19 @@ const page: React.FC = () => { - {playbackURL && ( - + {playbackUrl && ( + )} {item.Overview} - { - setPlaybackURL(val); - }} + setMaxBitrate(val)} + selected={maxBitrate} /> + diff --git a/app/_layout.tsx b/app/_layout.tsx index 2561a157..7b07e5c1 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -13,6 +13,12 @@ import "react-native-reanimated"; import Feather from "@expo/vector-icons/Feather"; import { StatusBar } from "expo-status-bar"; import { Colors } from "@/constants/Colors"; +import { View } from "react-native"; +import { Text } from "@/components/common/Text"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Ionicons } from "@expo/vector-icons"; +import Video from "react-native-video"; +import { CurrentlyPlayingBar } from "@/components/CurrentlyPlayingBar"; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); @@ -40,6 +46,8 @@ export default function RootLayout() { }), ); + const insets = useSafeAreaInsets(); + useEffect(() => { if (loaded) { SplashScreen.hideAsync(); @@ -56,7 +64,7 @@ export default function RootLayout() { - + ( - router.back()}> - - - ), + headerStyle: { backgroundColor: "black" }, + headerShadowVisible: false, }} /> - + diff --git a/app/login.tsx b/app/login.tsx index b5a6dc86..b6edc7ba 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -151,7 +151,6 @@ const Login: React.FC = () => { returnKeyType="done" autoCapitalize="none" textContentType="URL" - clearButtonMode="while-editing" maxLength={500} /> + ); +}; diff --git a/components/VideoPlayer.tsx b/components/VideoPlayer.tsx index eccb6440..a86e1814 100644 --- a/components/VideoPlayer.tsx +++ b/components/VideoPlayer.tsx @@ -23,31 +23,13 @@ import { chromecastProfile } from "@/utils/profiles/chromecast"; import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped"; import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; +import { currentlyPlayingItemAtom } from "./CurrentlyPlayingBar"; type VideoPlayerProps = { itemId: string; onChangePlaybackURL: (url: string | null) => void; }; -const BITRATES = [ - { - key: "Max", - value: undefined, - }, - { - key: "4 Mb/s", - value: 4000000, - }, - { - key: "2 Mb/s", - value: 2000000, - }, - { - key: "500 Kb/s", - value: 500000, - }, -]; - export const VideoPlayer: React.FC = ({ itemId, onChangePlaybackURL, @@ -194,6 +176,8 @@ export const VideoPlayer: React.FC = ({ }); }, [item, client, playbackURL]); + const [cp, setCp] = useAtom(currentlyPlayingItemAtom); + useEffect(() => { videoRef.current?.pause(); }, []); @@ -263,14 +247,15 @@ export const VideoPlayer: React.FC = ({