fix: refactor

This commit is contained in:
Fredrik Burmester
2024-08-13 20:25:30 +02:00
parent 5289c0519f
commit 855e00a676
10 changed files with 143 additions and 69 deletions

View File

@@ -11,7 +11,7 @@ import { useQuery } from "@tanstack/react-query";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { import {
ActivityIndicator, ActivityIndicator,
ScrollView, ScrollView,
@@ -36,6 +36,10 @@ import ios12 from "@/utils/profiles/ios12";
import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar"; import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar";
import { AudioTrackSelector } from "@/components/AudioTrackSelector"; import { AudioTrackSelector } from "@/components/AudioTrackSelector";
import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector"; 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 page: React.FC = () => {
const local = useLocalSearchParams(); const local = useLocalSearchParams();
@@ -201,7 +205,7 @@ const page: React.FC = () => {
</> </>
} }
> >
<View className="flex flex-col px-4 mb-4 pt-4"> <View className="flex flex-col px-4 pt-4">
<View className="flex flex-col"> <View className="flex flex-col">
{item.Type === "Episode" ? ( {item.Type === "Episode" ? (
<> <>
@@ -218,7 +222,6 @@ const page: React.FC = () => {
<Text className="text-center font-bold text-2xl mr-2"> <Text className="text-center font-bold text-2xl mr-2">
{item?.Name} {item?.Name}
</Text> </Text>
<PlayedStatus item={item} />
</View> </View>
<View> <View>
<View className="flex flex-row items-center self-center"> <View className="flex flex-row items-center self-center">
@@ -243,7 +246,6 @@ const page: React.FC = () => {
<Text className="text-center font-bold text-2xl mr-2"> <Text className="text-center font-bold text-2xl mr-2">
{item?.Name} {item?.Name}
</Text> </Text>
<PlayedStatus item={item} />
</View> </View>
<Text className="text-center opacity-50"> <Text className="text-center opacity-50">
{item?.ProductionYear} {item?.ProductionYear}
@@ -253,14 +255,17 @@ const page: React.FC = () => {
</View> </View>
<View className="flex flex-row justify-between items-center w-full my-4"> <View className="flex flex-row justify-between items-center w-full my-4">
{playbackUrl && ( {playbackUrl ? (
<DownloadItem item={item} playbackUrl={playbackUrl} /> <DownloadItem item={item} playbackUrl={playbackUrl} />
) : (
<View className="h-12 aspect-square flex items-center justify-center"></View>
)} )}
<PlayedStatus item={item} />
<Chromecast /> <Chromecast />
</View> </View>
<Text>{item.Overview}</Text> <Text>{item.Overview}</Text>
</View> </View>
<View className="flex flex-col p-4"> <View className="flex flex-col p-4 w-full">
<View className="flex flex-row items-center space-x-2 w-full"> <View className="flex flex-row items-center space-x-2 w-full">
<BitrateSelector <BitrateSelector
onChange={(val) => setMaxBitrate(val)} onChange={(val) => setMaxBitrate(val)}
@@ -277,7 +282,16 @@ const page: React.FC = () => {
selected={selectedSubtitleStream} selected={selectedSubtitleStream}
/> />
</View> </View>
<PlayButton item={item} chromecastReady={false} onPress={onPressPlay} /> <View className="flex flex-row items-center justify-between space-x-2 w-full">
<NextEpisodeButton item={item} type="previous" />
<PlayButton
item={item}
chromecastReady={false}
onPress={onPressPlay}
className="grow"
/>
<NextEpisodeButton item={item} />
</View>
</View> </View>
<ScrollView horizontal className="flex px-4 mb-4"> <ScrollView horizontal className="flex px-4 mb-4">
<View className="flex flex-row space-x-2 "> <View className="flex flex-row space-x-2 ">

View File

@@ -7,7 +7,7 @@ interface ButtonProps extends React.ComponentProps<typeof TouchableOpacity> {
className?: string; className?: string;
textClassName?: string; textClassName?: string;
disabled?: boolean; disabled?: boolean;
children?: string; children?: string | ReactNode;
loading?: boolean; loading?: boolean;
color?: "purple" | "red" | "black"; color?: "purple" | "red" | "black";
iconRight?: ReactNode; iconRight?: ReactNode;

View File

@@ -1,5 +1,6 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { View } from "react-native";
import { import {
CastButton, CastButton,
useCastDevice, useCastDevice,
@@ -30,5 +31,9 @@ export const Chromecast: React.FC<Props> = () => {
})(); })();
}, [client, devices, castDevice, sessionManager, discoveryManager]); }, [client, devices, castDevice, sessionManager, discoveryManager]);
return <CastButton style={{ tintColor: "white", height: 48, width: 48 }} />; return (
<View className="rounded h-12 aspect-square flex items-center justify-center">
<CastButton style={{ tintColor: "white", height: 48, width: 48 }} />
</View>
);
}; };

View File

@@ -7,23 +7,13 @@ import {
import { Text } from "./common/Text"; import { Text } from "./common/Text";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import Video, { OnProgressData, VideoRef } from "react-native-video";
import Video, {
OnProgressData,
SelectedTrack,
SelectedTrackType,
VideoRef,
} from "react-native-video";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useCastDevice, useRemoteMediaClient } from "react-native-google-cast";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData"; import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; 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 { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress";
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped"; import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
import Animated, { import Animated, {
@@ -35,7 +25,6 @@ import { useRouter, useSegments } from "expo-router";
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import { writeToLog } from "@/utils/log"; import { writeToLog } from "@/utils/log";
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
import { Image } from "expo-image";
export const currentlyPlayingItemAtom = atom<{ export const currentlyPlayingItemAtom = atom<{
item: BaseItemDto; item: BaseItemDto;
@@ -43,13 +32,10 @@ export const currentlyPlayingItemAtom = atom<{
} | null>(null); } | null>(null);
export const CurrentlyPlayingBar: React.FC = () => { export const CurrentlyPlayingBar: React.FC = () => {
const insets = useSafeAreaInsets();
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom); const [user] = useAtom(userAtom);
const [cp, setCp] = useAtom(currentlyPlayingItemAtom); const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
const castDevice = useCastDevice();
const client = useRemoteMediaClient();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const segments = useSegments(); const segments = useSegments();
@@ -57,8 +43,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
const [paused, setPaused] = useState(true); const [paused, setPaused] = useState(true);
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
const [pip, setPip] = useState(false);
const aBottom = useSharedValue(0); const aBottom = useSharedValue(0);
const aPadding = useSharedValue(0); const aPadding = useSharedValue(0);
const aHeight = useSharedValue(100); const aHeight = useSharedValue(100);
@@ -239,9 +223,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
ignoreSilentSwitch="ignore" ignoreSilentSwitch="ignore"
controls={false} controls={false}
pictureInPicture={true} pictureInPicture={true}
onPictureInPictureStatusChanged={(e) => {
setPip(e.isActive);
}}
poster={ poster={
backdropUrl && item?.Type === "Audio" backdropUrl && item?.Type === "Audio"
? backdropUrl ? backdropUrl

View File

@@ -64,12 +64,16 @@ export const DownloadItem: React.FC<DownloadProps> = ({
}, [process]); }, [process]);
if (isLoading) { if (isLoading) {
return <ActivityIndicator size={"small"} color={"white"} />; return (
<View className="rounded h-12 aspect-square flex items-center justify-center">
<ActivityIndicator size={"small"} color={"white"} />
</View>
);
} }
if (playbackInfo?.MediaSources?.[0].SupportsDirectPlay === false) { if (playbackInfo?.MediaSources?.[0].SupportsDirectPlay === false) {
return ( return (
<View style={{ opacity: 0.5 }}> <View className="rounded h-12 aspect-square flex items-center justify-center opacity-50">
<Ionicons name="cloud-download-outline" size={24} color="white" /> <Ionicons name="cloud-download-outline" size={24} color="white" />
</View> </View>
); );
@@ -77,21 +81,22 @@ export const DownloadItem: React.FC<DownloadProps> = ({
if (process && process.item.Id !== item.Id!) { if (process && process.item.Id !== item.Id!) {
return ( return (
<TouchableOpacity onPress={() => {}} style={{ opacity: 0.5 }}> <TouchableOpacity onPress={() => {}}>
<Ionicons name="cloud-download-outline" size={24} color="white" /> <View className="rounded h-12 aspect-square flex items-center justify-center opacity-50">
<Ionicons name="cloud-download-outline" size={24} color="white" />
</View>
</TouchableOpacity> </TouchableOpacity>
); );
} }
return ( if (process) {
<View> return (
{process ? ( <TouchableOpacity
<TouchableOpacity onPress={() => {
onPress={() => { router.push("/downloads");
router.push("/downloads"); }}
}} >
className="flex flex-row items-center" <View className="rounded h-12 aspect-square flex items-center justify-center">
>
{process.progress === 0 ? ( {process.progress === 0 ? (
<ActivityIndicator size={"small"} color={"white"} /> <ActivityIndicator size={"small"} color={"white"} />
) : ( ) : (
@@ -118,24 +123,32 @@ export const DownloadItem: React.FC<DownloadProps> = ({
<Text>{process.speed.toFixed(2)}x</Text> <Text>{process.speed.toFixed(2)}x</Text>
</View> </View>
) : null} ) : null}
</TouchableOpacity> </View>
) : downloaded ? ( </TouchableOpacity>
<TouchableOpacity );
onPress={() => { } else if (downloaded) {
router.push("/downloads"); return (
}} <TouchableOpacity
> onPress={() => {
router.push("/downloads");
}}
>
<View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="cloud-download" size={26} color="#9333ea" /> <Ionicons name="cloud-download" size={26} color="#9333ea" />
</TouchableOpacity> </View>
) : ( </TouchableOpacity>
<TouchableOpacity );
onPress={() => { } else {
startRemuxing(); return (
}} <TouchableOpacity
> onPress={() => {
startRemuxing();
}}
>
<View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="cloud-download-outline" size={26} color="white" /> <Ionicons name="cloud-download-outline" size={26} color="white" />
</TouchableOpacity> </View>
)} </TouchableOpacity>
</View> );
); }
}; };

View File

@@ -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<Props> = ({ videoSource }) => {
const ref = useRef<VideoView | null>(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 (
<TouchableOpacity
onPress={() => {
ref.current?.enterFullscreen();
}}
className={`relative h-full bg-neutral-800 rounded-md overflow-hidden
`}
>
<VideoView
ref={ref}
style={{
width: "100%",
height: "100%",
}}
player={player}
allowsFullscreen
allowsPictureInPicture
/>
</TouchableOpacity>
);
};

View File

@@ -30,7 +30,7 @@ type VideoPlayerProps = {
onChangePlaybackURL: (url: string | null) => void; onChangePlaybackURL: (url: string | null) => void;
}; };
export const VideoPlayer: React.FC<VideoPlayerProps> = ({ export const OldVideoPlayer: React.FC<VideoPlayerProps> = ({
itemId, itemId,
onChangePlaybackURL, onChangePlaybackURL,
}) => { }) => {

View File

@@ -89,7 +89,9 @@ export const ParallaxScrollView: React.FC<Props> = ({
> >
{headerImage} {headerImage}
</Animated.View> </Animated.View>
<View className="flex-1 overflow-hidden bg-black">{children}</View> <View className="flex-1 overflow-hidden bg-black pb-24">
{children}
</View>
</Animated.ScrollView> </Animated.ScrollView>
</View> </View>
); );

View File

@@ -1,21 +1,19 @@
import { useState } from "react";
import { Button } from "./Button"; import { Button } from "./Button";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; 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 { Feather, Ionicons } from "@expo/vector-icons";
import { runtimeTicksToMinutes } from "@/utils/time"; import { runtimeTicksToMinutes } from "@/utils/time";
type Props = { interface Props extends React.ComponentProps<typeof Button> {
item: BaseItemDto; item: BaseItemDto;
onPress: () => void; onPress: () => void;
chromecastReady: boolean; chromecastReady: boolean;
}; }
export const PlayButton: React.FC<Props> = ({ export const PlayButton: React.FC<Props> = ({
item, item,
onPress, onPress,
chromecastReady, chromecastReady,
...props
}) => { }) => {
return ( return (
<Button <Button
@@ -27,6 +25,7 @@ export const PlayButton: React.FC<Props> = ({
<Ionicons name="play-circle" size={24} color="white" /> <Ionicons name="play-circle" size={24} color="white" />
) )
} }
{...props}
> >
{runtimeTicksToMinutes(item?.RunTimeTicks)} {runtimeTicksToMinutes(item?.RunTimeTicks)}
</Button> </Button>

View File

@@ -47,7 +47,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => {
invalidateQueries(); invalidateQueries();
}} }}
> >
<Ionicons name="checkmark-circle" size={26} color="white" /> <View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="checkmark-circle" size={26} color="white" />
</View>
</TouchableOpacity> </TouchableOpacity>
) : ( ) : (
<TouchableOpacity <TouchableOpacity
@@ -61,7 +63,9 @@ export const PlayedStatus: React.FC<{ item: BaseItemDto }> = ({ item }) => {
invalidateQueries(); invalidateQueries();
}} }}
> >
<Ionicons name="checkmark-circle-outline" size={26} color="white" /> <View className="rounded h-12 aspect-square flex items-center justify-center">
<Ionicons name="checkmark-circle-outline" size={26} color="white" />
</View>
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>