From d39e3aeb805394591ccedcb2c06473cce7757e6e Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Fri, 16 Aug 2024 19:56:40 +0200 Subject: [PATCH] fix: refactor --- app/(auth)/(tabs)/home/index.tsx | 2 - app/(auth)/collections/[collectionId].tsx | 2 - app/(auth)/items/[id].tsx | 30 ++++- app/(auth)/series/[id].tsx | 6 - app/(auth)/songs/[songId].tsx | 9 +- components/CurrentlyPlayingBar.tsx | 23 +++- components/PlayButton.tsx | 2 +- components/downloads/EpisodeCard.tsx | 17 ++- components/downloads/MovieCard.tsx | 150 +++++++++++----------- components/music/SongsListItem.tsx | 13 +- 10 files changed, 143 insertions(+), 111 deletions(-) diff --git a/app/(auth)/(tabs)/home/index.tsx b/app/(auth)/(tabs)/home/index.tsx index 5cae987e..6d145340 100644 --- a/app/(auth)/(tabs)/home/index.tsx +++ b/app/(auth)/(tabs)/home/index.tsx @@ -210,8 +210,6 @@ export default function index() { limit: 10, }); - console.log("Popular", response.data.Items?.length); - return response.data.Items || []; }, enabled: !!api && !!user?.Id && !!mediaListCollection, diff --git a/app/(auth)/collections/[collectionId].tsx b/app/(auth)/collections/[collectionId].tsx index df1a3228..f5402009 100644 --- a/app/(auth)/collections/[collectionId].tsx +++ b/app/(auth)/collections/[collectionId].tsx @@ -60,8 +60,6 @@ const page: React.FC = () => { const sortBy: ItemSortBy[] = []; const includeItemTypes: BaseItemKind[] = []; - console.log("Collection", { collection }); - switch (collection?.CollectionType) { case "movies": sortBy.push("SortName", "ProductionYear"); diff --git a/app/(auth)/items/[id].tsx b/app/(auth)/items/[id].tsx index d46168d6..4d8d0070 100644 --- a/app/(auth)/items/[id].tsx +++ b/app/(auth)/items/[id].tsx @@ -2,6 +2,7 @@ import { AudioTrackSelector } from "@/components/AudioTrackSelector"; import { Bitrate, BitrateSelector } from "@/components/BitrateSelector"; import { currentlyPlayingItemAtom, + fullScreenAtom, playingAtom, } from "@/components/CurrentlyPlayingBar"; import { DownloadItem } from "@/components/DownloadItem"; @@ -37,6 +38,10 @@ import CastContext, { useRemoteMediaClient, } from "react-native-google-cast"; import { ParallaxScrollView } from "../../../components/ParallaxPage"; +import { useSettings } from "@/utils/atoms/settings"; +import ios from "@/utils/profiles/ios"; +import native from "@/utils/profiles/native"; +import old from "@/utils/profiles/old"; const page: React.FC = () => { const local = useLocalSearchParams(); @@ -45,10 +50,13 @@ const page: React.FC = () => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); + const [settings] = useSettings(); + const castDevice = useCastDevice(); const [, setCurrentlyPlying] = useAtom(currentlyPlayingItemAtom); const [, setPlaying] = useAtom(playingAtom); + const [, setFullscreen] = useAtom(fullScreenAtom); const client = useRemoteMediaClient(); const chromecastReady = useMemo(() => !!castDevice?.deviceId, [castDevice]); @@ -95,10 +103,21 @@ const page: React.FC = () => { castDevice, selectedAudioStream, selectedSubtitleStream, + settings, ], queryFn: async () => { if (!api || !user?.Id || !sessionData) return null; + let deviceProfile: any = ios; + + if (castDevice?.deviceId) { + deviceProfile = chromecastProfile; + } else if (settings?.deviceProfile === "Native") { + deviceProfile = native; + } else if (settings?.deviceProfile === "Old") { + deviceProfile = old; + } + const url = await getStreamUrl({ api, userId: user.Id, @@ -106,16 +125,17 @@ const page: React.FC = () => { startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0, maxStreamingBitrate: maxBitrate.value, sessionData, - deviceProfile: castDevice?.deviceId ? chromecastProfile : ios12, + deviceProfile, audioStreamIndex: selectedAudioStream, subtitleStreamIndex: selectedSubtitleStream, + forceDirectPlay: settings?.forceDirectPlay, }); console.log("Transcode URL: ", url); return url; }, - enabled: !!sessionData, + enabled: !!sessionData && !!api && !!user?.Id && !!item?.Id, staleTime: 0, }); @@ -147,11 +167,13 @@ const page: React.FC = () => { item, playbackUrl, }); - setPlaying(true); + if (settings?.openFullScreenVideoPlayerByDefault === true) { + setFullscreen(true); + } } }, - [playbackUrl, item], + [playbackUrl, item, settings], ); const backdropUrl = useMemo( diff --git a/app/(auth)/series/[id].tsx b/app/(auth)/series/[id].tsx index 7420ce85..d3481e6e 100644 --- a/app/(auth)/series/[id].tsx +++ b/app/(auth)/series/[id].tsx @@ -20,12 +20,6 @@ const page: React.FC = () => { seasonIndex: string; }; - useEffect(() => { - if (seriesId) { - console.log("seasonIndex", seasonIndex); - } - }, [seriesId]); - const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); diff --git a/app/(auth)/songs/[songId].tsx b/app/(auth)/songs/[songId].tsx index c6f77a7e..cf6e74f0 100644 --- a/app/(auth)/songs/[songId].tsx +++ b/app/(auth)/songs/[songId].tsx @@ -23,7 +23,7 @@ import CastContext, { import { chromecastProfile } from "@/utils/profiles/chromecast"; import { currentlyPlayingItemAtom, - triggerPlayAtom, + playingAtom, } from "@/components/CurrentlyPlayingBar"; import { AudioTrackSelector } from "@/components/AudioTrackSelector"; import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector"; @@ -40,6 +40,8 @@ const page: React.FC = () => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); + const [, setPlaying] = useAtom(playingAtom); + const castDevice = useCastDevice(); const navigation = useNavigation(); @@ -139,7 +141,6 @@ const page: React.FC = () => { const [, setCp] = useAtom(currentlyPlayingItemAtom); const client = useRemoteMediaClient(); - const [, setPlayTrigger] = useAtom(triggerPlayAtom); const onPressPlay = useCallback( async (type: "device" | "cast" = "device") => { @@ -169,9 +170,7 @@ const page: React.FC = () => { item, playbackUrl, }); - - // Use this trigger to initiate playback in another component (CurrentlyPlayingBar) - setPlayTrigger((prev) => prev + 1); + setPlaying(true); } }, [playbackUrl, item], diff --git a/components/CurrentlyPlayingBar.tsx b/components/CurrentlyPlayingBar.tsx index 2143672e..24825a42 100644 --- a/components/CurrentlyPlayingBar.tsx +++ b/components/CurrentlyPlayingBar.tsx @@ -16,6 +16,7 @@ import { atom, useAtom } from "jotai"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ActivityIndicator, + Alert, Platform, TouchableOpacity, View, @@ -33,14 +34,12 @@ export const currentlyPlayingItemAtom = atom<{ playbackUrl: string; } | null>(null); -export const triggerPlayAtom = atom(0); - export const playingAtom = atom(false); +export const fullScreenAtom = atom(false); export const CurrentlyPlayingBar: React.FC = () => { const queryClient = useQueryClient(); const segments = useSegments(); - const [settings] = useSettings(); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); @@ -48,10 +47,10 @@ export const CurrentlyPlayingBar: React.FC = () => { const [currentlyPlaying, setCurrentlyPlaying] = useAtom( currentlyPlayingItemAtom, ); + const [fullScreen, setFullScreen] = useAtom(fullScreenAtom); const videoRef = useRef(null); const [progress, setProgress] = useState(0); - const [fullScreen, setFullScreen] = useState(false); const aBottom = useSharedValue(0); const aPadding = useSharedValue(0); @@ -148,11 +147,13 @@ export const CurrentlyPlayingBar: React.FC = () => { ); useEffect(() => { + if (!item || !api) return; + if (playing) { videoRef.current?.resume(); } else { videoRef.current?.pause(); - if (progress > 0 && sessionData?.PlaySessionId && api) + if (progress > 0 && sessionData?.PlaySessionId) reportPlaybackStopped({ api, itemId: item?.Id, @@ -171,6 +172,15 @@ export const CurrentlyPlayingBar: React.FC = () => { } }, [playing, progress, item, sessionData]); + useEffect(() => { + console.log("Full screen changed", fullScreen); + if (fullScreen === true) { + videoRef.current?.presentFullscreenPlayer(); + } else { + videoRef.current?.dismissFullscreenPlayer(); + } + }, [fullScreen]); + const startPosition = useMemo( () => item?.UserData?.PlaybackPositionTicks @@ -276,6 +286,9 @@ export const CurrentlyPlayingBar: React.FC = () => { "ERROR", "Video playback error: " + JSON.stringify(e), ); + Alert.alert("Error", "Cannot play this video file."); + setPlaying(false); + setCurrentlyPlaying(null); }} renderLoader={ item?.Type !== "Audio" && ( diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index bebcdf09..316b3745 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -42,7 +42,7 @@ export const PlayButton: React.FC = ({ onPress("device"); break; case cancelButtonIndex: - console.log("calcel"); + break; } }, ); diff --git a/components/downloads/EpisodeCard.tsx b/components/downloads/EpisodeCard.tsx index f19a6e99..68678323 100644 --- a/components/downloads/EpisodeCard.tsx +++ b/components/downloads/EpisodeCard.tsx @@ -8,7 +8,12 @@ import { useAtom } from "jotai"; import { Text } from "../common/Text"; import { useFiles } from "@/hooks/useFiles"; -import { currentlyPlayingItemAtom } from "../CurrentlyPlayingBar"; +import { + currentlyPlayingItemAtom, + fullScreenAtom, + playingAtom, +} from "../CurrentlyPlayingBar"; +import { useSettings } from "@/utils/atoms/settings"; interface EpisodeCardProps { item: BaseItemDto; @@ -22,16 +27,22 @@ interface EpisodeCardProps { export const EpisodeCard: React.FC = ({ item }) => { const { deleteFile } = useFiles(); const [, setCurrentlyPlaying] = useAtom(currentlyPlayingItemAtom); + const [, setPlaying] = useAtom(playingAtom); + const [, setFullscreen] = useAtom(fullScreenAtom); + const [settings] = useSettings(); /** * Handles opening the file for playback. */ - const handleOpenFile = useCallback(() => { + const handleOpenFile = useCallback(async () => { setCurrentlyPlaying({ item, playbackUrl: `${FileSystem.documentDirectory}/${item.Id}.mp4`, }); - }, [item, setCurrentlyPlaying]); + setPlaying(true); + if (settings?.openFullScreenVideoPlayerByDefault === true) + setFullscreen(true); + }, [item, setCurrentlyPlaying, settings]); /** * Handles deleting the file with haptic feedback. diff --git a/components/downloads/MovieCard.tsx b/components/downloads/MovieCard.tsx index 215d6846..5deb999d 100644 --- a/components/downloads/MovieCard.tsx +++ b/components/downloads/MovieCard.tsx @@ -1,97 +1,99 @@ +import React, { useCallback } from "react"; import { TouchableOpacity, View } from "react-native"; -import { Text } from "../common/Text"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import { runtimeTicksToMinutes } from "@/utils/time"; import * as ContextMenu from "zeego/context-menu"; -import { useFiles } from "@/hooks/useFiles"; import * as FileSystem from "expo-file-system"; -import { useCallback } from "react"; import * as Haptics from "expo-haptics"; import { useAtom } from "jotai"; -import { currentlyPlayingItemAtom } from "../CurrentlyPlayingBar"; -import { useQuery } from "@tanstack/react-query"; -export const MovieCard: React.FC<{ item: BaseItemDto }> = ({ item }) => { +import { Text } from "../common/Text"; +import { useFiles } from "@/hooks/useFiles"; +import { runtimeTicksToMinutes } from "@/utils/time"; +import { + currentlyPlayingItemAtom, + fullScreenAtom, + playingAtom, +} from "../CurrentlyPlayingBar"; +import { useSettings } from "@/utils/atoms/settings"; + +interface MovieCardProps { + item: BaseItemDto; +} + +/** + * MovieCard component displays a movie with context menu options. + * @param {MovieCardProps} props - The component props. + * @returns {React.ReactElement} The rendered MovieCard component. + */ +export const MovieCard: React.FC = ({ item }) => { const { deleteFile } = useFiles(); - const [_, setCp] = useAtom(currentlyPlayingItemAtom); + const [, setCurrentlyPlaying] = useAtom(currentlyPlayingItemAtom); + const [, setPlaying] = useAtom(playingAtom); + const [, setFullscreen] = useAtom(fullScreenAtom); + const [settings] = useSettings(); - // const fetchFileSize = async () => { - // const filePath = `${FileSystem.documentDirectory}/${item.Id}.mp4`; - // const info = await FileSystem.getInfoAsync(filePath); - // return info.exists ? info.size : null; - // }; - - // const { data: fileSize } = useQuery({ - // queryKey: ["fileSize", item?.Id], - // queryFn: fetchFileSize, - // }); - - const openFile = useCallback(() => { - setCp({ + /** + * Handles opening the file for playback. + */ + const handleOpenFile = useCallback(() => { + console.log("Open movie file", item.Name); + setCurrentlyPlaying({ item, playbackUrl: `${FileSystem.documentDirectory}/${item.Id}.mp4`, }); - }, [item]); + setPlaying(true); + if (settings?.openFullScreenVideoPlayerByDefault === true) { + setFullscreen(true); + } + }, [item, setCurrentlyPlaying, setPlaying, settings]); - 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 ( - <> - - - - {item.Name} - - {item.ProductionYear} - - {runtimeTicksToMinutes(item.RunTimeTicks)} - - {/* - Size:{" "} - {fileSize - ? `${(fileSize / 1000000).toFixed(0)} MB` - : "Calculating..."}{" "} - */} - - - - + + - {options.map((i) => ( - { - i.onSelect(item.Id!); - }} - key={i.label} - destructive={i.destructive} - > - - {i.label} - - - ))} - - - + {item.Name} + + {item.ProductionYear} + + {runtimeTicksToMinutes(item.RunTimeTicks)} + + + + + + {contextMenuOptions.map((option) => ( + + + {option.label} + + + ))} + + ); }; diff --git a/components/music/SongsListItem.tsx b/components/music/SongsListItem.tsx index ce41114f..53abcba8 100644 --- a/components/music/SongsListItem.tsx +++ b/components/music/SongsListItem.tsx @@ -19,10 +19,7 @@ import CastContext, { useCastDevice, useRemoteMediaClient, } from "react-native-google-cast"; -import { - currentlyPlayingItemAtom, - triggerPlayAtom, -} from "../CurrentlyPlayingBar"; +import { currentlyPlayingItemAtom, playingAtom } from "../CurrentlyPlayingBar"; import { useActionSheet } from "@expo/react-native-action-sheet"; import ios from "@/utils/profiles/ios"; @@ -46,7 +43,7 @@ export const SongsListItem: React.FC = ({ const [user] = useAtom(userAtom); const castDevice = useCastDevice(); const [, setCp] = useAtom(currentlyPlayingItemAtom); - const [, setPlayTrigger] = useAtom(triggerPlayAtom); + const [, setPlaying] = useAtom(playingAtom); const client = useRemoteMediaClient(); const { showActionSheetWithOptions } = useActionSheet(); @@ -74,7 +71,7 @@ export const SongsListItem: React.FC = ({ play("device"); break; case cancelButtonIndex: - console.log("calcel"); + break; } }, ); @@ -125,9 +122,7 @@ export const SongsListItem: React.FC = ({ item, playbackUrl: url, }); - - // Use this trigger to initiate playback in another component (CurrentlyPlayingBar) - setPlayTrigger((prev) => prev + 1); + setPlaying(true); } };