diff --git a/app/(auth)/items/[id]/page.tsx b/app/(auth)/items/[id]/page.tsx index 05f5e430..6beb0711 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 { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { ActivityIndicator, ScrollView, @@ -36,6 +36,10 @@ import ios12 from "@/utils/profiles/ios12"; import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar"; import { AudioTrackSelector } from "@/components/AudioTrackSelector"; import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector"; +import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { Button } from "@/components/Button"; +import { Ionicons } from "@expo/vector-icons"; +import { NextEpisodeButton } from "@/components/series/NextEpisodeButton"; const page: React.FC = () => { const local = useLocalSearchParams(); @@ -201,7 +205,7 @@ const page: React.FC = () => { } > - + {item.Type === "Episode" ? ( <> @@ -218,7 +222,6 @@ const page: React.FC = () => { {item?.Name} - @@ -243,7 +246,6 @@ const page: React.FC = () => { {item?.Name} - {item?.ProductionYear} @@ -253,14 +255,17 @@ const page: React.FC = () => { - {playbackUrl && ( + {playbackUrl ? ( + ) : ( + )} + {item.Overview} - + setMaxBitrate(val)} @@ -277,7 +282,16 @@ const page: React.FC = () => { selected={selectedSubtitleStream} /> - + + + + + diff --git a/components/Button.tsx b/components/Button.tsx index 52069f13..01b88963 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -7,7 +7,7 @@ interface ButtonProps extends React.ComponentProps { className?: string; textClassName?: string; disabled?: boolean; - children?: string; + children?: string | ReactNode; loading?: boolean; color?: "purple" | "red" | "black"; iconRight?: ReactNode; diff --git a/components/Chromecast.tsx b/components/Chromecast.tsx index 4286b64e..69608057 100644 --- a/components/Chromecast.tsx +++ b/components/Chromecast.tsx @@ -1,5 +1,6 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import React, { useEffect } from "react"; +import { View } from "react-native"; import { CastButton, useCastDevice, @@ -30,5 +31,9 @@ export const Chromecast: React.FC = () => { })(); }, [client, devices, castDevice, sessionManager, discoveryManager]); - return ; + return ( + + + + ); }; diff --git a/components/CurrentlyPlayingBar.tsx b/components/CurrentlyPlayingBar.tsx index 6a951bff..c13c675e 100644 --- a/components/CurrentlyPlayingBar.tsx +++ b/components/CurrentlyPlayingBar.tsx @@ -7,23 +7,13 @@ import { import { Text } from "./common/Text"; import { Ionicons } from "@expo/vector-icons"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import Video, { - OnProgressData, - SelectedTrack, - SelectedTrackType, - VideoRef, -} from "react-native-video"; +import Video, { OnProgressData, VideoRef } from "react-native-video"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { atom, useAtom } from "jotai"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { useCastDevice, useRemoteMediaClient } from "react-native-google-cast"; import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData"; import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; -import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; -import { chromecastProfile } from "@/utils/profiles/chromecast"; -import ios12 from "@/utils/profiles/ios12"; import { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress"; import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped"; import Animated, { @@ -35,7 +25,6 @@ import { useRouter, useSegments } from "expo-router"; import { BlurView } from "expo-blur"; import { writeToLog } from "@/utils/log"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; -import { Image } from "expo-image"; export const currentlyPlayingItemAtom = atom<{ item: BaseItemDto; @@ -43,13 +32,10 @@ export const currentlyPlayingItemAtom = atom<{ } | null>(null); export const CurrentlyPlayingBar: React.FC = () => { - const insets = useSafeAreaInsets(); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const [cp, setCp] = useAtom(currentlyPlayingItemAtom); - const castDevice = useCastDevice(); - const client = useRemoteMediaClient(); const queryClient = useQueryClient(); const segments = useSegments(); @@ -57,8 +43,6 @@ export const CurrentlyPlayingBar: React.FC = () => { const [paused, setPaused] = useState(true); const [progress, setProgress] = useState(0); - const [pip, setPip] = useState(false); - const aBottom = useSharedValue(0); const aPadding = useSharedValue(0); const aHeight = useSharedValue(100); @@ -239,9 +223,6 @@ export const CurrentlyPlayingBar: React.FC = () => { ignoreSilentSwitch="ignore" controls={false} pictureInPicture={true} - onPictureInPictureStatusChanged={(e) => { - setPip(e.isActive); - }} poster={ backdropUrl && item?.Type === "Audio" ? backdropUrl diff --git a/components/DownloadItem.tsx b/components/DownloadItem.tsx index bf8d6a6b..309e2a01 100644 --- a/components/DownloadItem.tsx +++ b/components/DownloadItem.tsx @@ -64,12 +64,16 @@ export const DownloadItem: React.FC = ({ }, [process]); if (isLoading) { - return ; + return ( + + + + ); } if (playbackInfo?.MediaSources?.[0].SupportsDirectPlay === false) { return ( - + ); @@ -77,21 +81,22 @@ export const DownloadItem: React.FC = ({ if (process && process.item.Id !== item.Id!) { return ( - {}} style={{ opacity: 0.5 }}> - + {}}> + + + ); } - return ( - - {process ? ( - { - router.push("/downloads"); - }} - className="flex flex-row items-center" - > + if (process) { + return ( + { + router.push("/downloads"); + }} + > + {process.progress === 0 ? ( ) : ( @@ -118,24 +123,32 @@ export const DownloadItem: React.FC = ({ {process.speed.toFixed(2)}x ) : null} - - ) : downloaded ? ( - { - router.push("/downloads"); - }} - > + + + ); + } else if (downloaded) { + return ( + { + router.push("/downloads"); + }} + > + - - ) : ( - { - startRemuxing(); - }} - > + + + ); + } else { + return ( + { + startRemuxing(); + }} + > + - - )} - - ); + + + ); + } }; diff --git a/components/NewVideoPlayer.tsx b/components/NewVideoPlayer.tsx new file mode 100644 index 00000000..13f002a4 --- /dev/null +++ b/components/NewVideoPlayer.tsx @@ -0,0 +1,56 @@ +import { useVideoPlayer, VideoView } from "expo-video"; +import { useEffect, useRef, useState } from "react"; +import { + PixelRatio, + StyleSheet, + View, + Button, + TouchableOpacity, +} from "react-native"; + +const videoSource = + "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; + +interface Props { + videoSource: string; +} + +export const NewVideoPlayer: React.FC = ({ videoSource }) => { + const ref = useRef(null); + const [isPlaying, setIsPlaying] = useState(true); + const player = useVideoPlayer(videoSource, (player) => { + player.loop = true; + player.play(); + }); + + useEffect(() => { + const subscription = player.addListener("playingChange", (isPlaying) => { + setIsPlaying(isPlaying); + }); + + return () => { + subscription.remove(); + }; + }, [player]); + + return ( + { + ref.current?.enterFullscreen(); + }} + className={`relative h-full bg-neutral-800 rounded-md overflow-hidden + `} + > + + + ); +}; diff --git a/components/VideoPlayer.tsx b/components/OldVideoPlayer.tsx similarity index 99% rename from components/VideoPlayer.tsx rename to components/OldVideoPlayer.tsx index a86e1814..905a9ae9 100644 --- a/components/VideoPlayer.tsx +++ b/components/OldVideoPlayer.tsx @@ -30,7 +30,7 @@ type VideoPlayerProps = { onChangePlaybackURL: (url: string | null) => void; }; -export const VideoPlayer: React.FC = ({ +export const OldVideoPlayer: React.FC = ({ itemId, onChangePlaybackURL, }) => { diff --git a/components/ParallaxPage.tsx b/components/ParallaxPage.tsx index 2260a804..90f8bff6 100644 --- a/components/ParallaxPage.tsx +++ b/components/ParallaxPage.tsx @@ -89,7 +89,9 @@ export const ParallaxScrollView: React.FC = ({ > {headerImage} - {children} + + {children} + ); diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 850aeef0..2ebfe94e 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -1,21 +1,19 @@ -import { useState } from "react"; import { Button } from "./Button"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import { currentlyPlayingItemAtom } from "./CurrentlyPlayingBar"; -import { useAtom } from "jotai"; import { Feather, Ionicons } from "@expo/vector-icons"; import { runtimeTicksToMinutes } from "@/utils/time"; -type Props = { +interface Props extends React.ComponentProps { item: BaseItemDto; onPress: () => void; chromecastReady: boolean; -}; +} export const PlayButton: React.FC = ({ item, onPress, chromecastReady, + ...props }) => { return ( diff --git a/components/PlayedStatus.tsx b/components/PlayedStatus.tsx index 9901f5a1..fa706625 100644 --- a/components/PlayedStatus.tsx +++ b/components/PlayedStatus.tsx @@ -47,7 +47,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => { invalidateQueries(); }} > - + + + ) : ( = ({ item }) => { invalidateQueries(); }} > - + + + )}