mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-03-05 17:26:18 +00:00
fix: refactor
This commit is contained in:
@@ -1,43 +1,42 @@
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { AudioTrackSelector } from "@/components/AudioTrackSelector";
|
||||
import { Bitrate, BitrateSelector } from "@/components/BitrateSelector";
|
||||
import {
|
||||
currentlyPlayingItemAtom,
|
||||
playingAtom,
|
||||
} from "@/components/CurrentlyPlayingBar";
|
||||
import { DownloadItem } from "@/components/DownloadItem";
|
||||
import { OverviewText } from "@/components/OverviewText";
|
||||
import { PlayButton } from "@/components/PlayButton";
|
||||
import { PlayedStatus } from "@/components/PlayedStatus";
|
||||
import { Ratings } from "@/components/Ratings";
|
||||
import { SimilarItems } from "@/components/SimilarItems";
|
||||
import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { MoviesTitleHeader } from "@/components/movies/MoviesTitleHeader";
|
||||
import { CastAndCrew } from "@/components/series/CastAndCrew";
|
||||
import { CurrentSeries } from "@/components/series/CurrentSeries";
|
||||
import { SimilarItems } from "@/components/SimilarItems";
|
||||
import { NextEpisodeButton } from "@/components/series/NextEpisodeButton";
|
||||
import { SeriesTitleHeader } from "@/components/series/SeriesTitleHeader";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||
import { chromecastProfile } from "@/utils/profiles/chromecast";
|
||||
import ios12 from "@/utils/profiles/ios12";
|
||||
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||
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 CastContext, {
|
||||
PlayServicesState,
|
||||
useCastDevice,
|
||||
useRemoteMediaClient,
|
||||
} from "react-native-google-cast";
|
||||
import { chromecastProfile } from "@/utils/profiles/chromecast";
|
||||
import ios12 from "@/utils/profiles/ios12";
|
||||
import {
|
||||
currentlyPlayingItemAtom,
|
||||
playingAtom,
|
||||
triggerPlayAtom,
|
||||
} from "@/components/CurrentlyPlayingBar";
|
||||
import { AudioTrackSelector } from "@/components/AudioTrackSelector";
|
||||
import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector";
|
||||
import { NextEpisodeButton } from "@/components/series/NextEpisodeButton";
|
||||
import { Ratings } from "@/components/Ratings";
|
||||
import { SeriesTitleHeader } from "@/components/series/SeriesTitleHeader";
|
||||
import { MoviesTitleHeader } from "@/components/movies/MoviesTitleHeader";
|
||||
import { OverviewText } from "@/components/OverviewText";
|
||||
import { ParallaxScrollView } from "../../../components/ParallaxPage";
|
||||
|
||||
const page: React.FC = () => {
|
||||
const local = useLocalSearchParams();
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||
import { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress";
|
||||
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { useRouter, useSegments } from "expo-router";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Text } from "./common/Text";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
Ref,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
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 { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress";
|
||||
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { useRouter, useSegments } from "expo-router";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
import Video, { OnProgressData, VideoRef } from "react-native-video";
|
||||
import { Text } from "./common/Text";
|
||||
|
||||
export const currentlyPlayingItemAtom = atom<{
|
||||
item: BaseItemDto;
|
||||
@@ -108,14 +99,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
||||
}
|
||||
}, [segments]);
|
||||
|
||||
// TODO: Fix this
|
||||
// useEffect(() => {
|
||||
// if (settings?.forceLandscapeInVideoPlayer === true && fullScreen)
|
||||
// ScreenOrientation.lockAsync(
|
||||
// ScreenOrientation.OrientationLock.LANDSCAPE_LEFT,
|
||||
// );
|
||||
// }, [settings, fullScreen]);
|
||||
|
||||
const { data: item } = useQuery({
|
||||
queryKey: ["item", currentlyPlaying?.item.Id],
|
||||
queryFn: async () =>
|
||||
@@ -207,29 +190,6 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
||||
[item],
|
||||
);
|
||||
|
||||
/**
|
||||
* These two useEffects are used to start playing the
|
||||
* video when the playbackUrl is available.
|
||||
*
|
||||
* The trigger playback is triggered from the button component.
|
||||
*/
|
||||
// useEffect(() => {
|
||||
// if (currentlyPlaying?.playbackUrl) {
|
||||
// play();
|
||||
// if (settings?.openFullScreenVideoPlayerByDefault) {
|
||||
// videoRef.current?.presentFullscreenPlayer();
|
||||
// }
|
||||
// }
|
||||
// }, [currentlyPlaying?.playbackUrl]);
|
||||
|
||||
// const [triggerPlay] = useAtom(triggerPlayAtom);
|
||||
// useEffect(() => {
|
||||
// setPlaying(true)
|
||||
// if (settings?.openFullScreenVideoPlayerByDefault) {
|
||||
// videoRef.current?.presentFullscreenPlayer();
|
||||
// }
|
||||
// }, [triggerPlay]);
|
||||
|
||||
if (!currentlyPlaying || !api) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Button } from "./Button";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||
import { useActionSheet } from "@expo/react-native-action-sheet";
|
||||
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { View } from "react-native";
|
||||
import { useAtom } from "jotai";
|
||||
import { playingAtom } from "./CurrentlyPlayingBar";
|
||||
import { Button } from "./Button";
|
||||
|
||||
interface Props extends React.ComponentProps<typeof Button> {
|
||||
item: BaseItemDto;
|
||||
|
||||
@@ -1,97 +1,85 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import React, { useCallback } from "react";
|
||||
import { TouchableOpacity } from "react-native";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import * as ContextMenu from "zeego/context-menu";
|
||||
import { Text } from "../common/Text";
|
||||
import { useFiles } from "@/hooks/useFiles";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { useCallback } from "react";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { Text } from "../common/Text";
|
||||
import { useFiles } from "@/hooks/useFiles";
|
||||
import { currentlyPlayingItemAtom } from "../CurrentlyPlayingBar";
|
||||
|
||||
export const EpisodeCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
interface EpisodeCardProps {
|
||||
item: BaseItemDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* EpisodeCard component displays an episode with context menu options.
|
||||
* @param {EpisodeCardProps} props - The component props.
|
||||
* @returns {React.ReactElement} The rendered EpisodeCard component.
|
||||
*/
|
||||
export const EpisodeCard: React.FC<EpisodeCardProps> = ({ item }) => {
|
||||
const { deleteFile } = useFiles();
|
||||
const [_, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||
const [, setCurrentlyPlaying] = useAtom(currentlyPlayingItemAtom);
|
||||
|
||||
// const fetchFileSize = async () => {
|
||||
// try {
|
||||
// const filePath = `${FileSystem.documentDirectory}/${item.Id}.mp4`;
|
||||
// const info = await FileSystem.getInfoAsync(filePath);
|
||||
// return info.exists ? info.size : null;
|
||||
// } catch (e) {
|
||||
// console.log(e);
|
||||
// return null;
|
||||
// }
|
||||
// };
|
||||
|
||||
// const { data: fileSize } = useQuery({
|
||||
// queryKey: ["fileSize", item?.Id],
|
||||
// queryFn: fetchFileSize,
|
||||
// });
|
||||
|
||||
const openFile = useCallback(() => {
|
||||
setCp({
|
||||
/**
|
||||
* Handles opening the file for playback.
|
||||
*/
|
||||
const handleOpenFile = useCallback(() => {
|
||||
setCurrentlyPlaying({
|
||||
item,
|
||||
playbackUrl: `${FileSystem.documentDirectory}/${item.Id}.mp4`,
|
||||
});
|
||||
}, [item]);
|
||||
}, [item, setCurrentlyPlaying]);
|
||||
|
||||
const options = [
|
||||
/**
|
||||
* Handles deleting the file with haptic feedback.
|
||||
*/
|
||||
const handleDeleteFile = useCallback(() => {
|
||||
if (item.Id) {
|
||||
deleteFile(item.Id);
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
}
|
||||
}, [deleteFile, item.Id]);
|
||||
|
||||
const contextMenuOptions = [
|
||||
{
|
||||
label: "Delete",
|
||||
onSelect: (id: string) => {
|
||||
deleteFile(id);
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
},
|
||||
onSelect: handleDeleteFile,
|
||||
destructive: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<TouchableOpacity
|
||||
onPress={openFile}
|
||||
className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4"
|
||||
>
|
||||
<Text className=" font-bold">{item.Name}</Text>
|
||||
<Text className=" text-xs opacity-50">
|
||||
Episode {item.IndexNumber}
|
||||
</Text>
|
||||
{/* <Text className=" text-xs opacity-50">
|
||||
Size:{" "}
|
||||
{fileSize
|
||||
? `${(fileSize / 1000000).toFixed(0)} MB`
|
||||
: "Calculating..."}{" "}
|
||||
</Text> */}
|
||||
</TouchableOpacity>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Content
|
||||
alignOffset={0}
|
||||
avoidCollisions
|
||||
collisionPadding={10}
|
||||
loop={false}
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<TouchableOpacity
|
||||
onPress={handleOpenFile}
|
||||
className="bg-neutral-900 border border-neutral-800 rounded-2xl p-4"
|
||||
>
|
||||
{options.map((i) => (
|
||||
<ContextMenu.Item
|
||||
onSelect={() => {
|
||||
i.onSelect(item.Id!);
|
||||
}}
|
||||
key={i.label}
|
||||
destructive={i.destructive}
|
||||
>
|
||||
<ContextMenu.ItemTitle
|
||||
style={{
|
||||
color: "red",
|
||||
}}
|
||||
>
|
||||
{i.label}
|
||||
</ContextMenu.ItemTitle>
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
</>
|
||||
<Text className="font-bold">{item.Name}</Text>
|
||||
<Text className="text-xs opacity-50">Episode {item.IndexNumber}</Text>
|
||||
</TouchableOpacity>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Content
|
||||
alignOffset={0}
|
||||
avoidCollisions
|
||||
collisionPadding={10}
|
||||
loop={false}
|
||||
>
|
||||
{contextMenuOptions.map((option) => (
|
||||
<ContextMenu.Item
|
||||
key={option.label}
|
||||
onSelect={option.onSelect}
|
||||
destructive={option.destructive}
|
||||
>
|
||||
<ContextMenu.ItemTitle style={{ color: "red" }}>
|
||||
{option.label}
|
||||
</ContextMenu.ItemTitle>
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,8 +2,8 @@ import ios from "@/utils/profiles/ios";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import {
|
||||
BaseItemDto,
|
||||
PlaybackInfoResponse,
|
||||
MediaSourceInfo,
|
||||
PlaybackInfoResponse,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
|
||||
export const getStreamUrl = async ({
|
||||
|
||||
Reference in New Issue
Block a user