From 67214a81c4348650e90d9ba2fc3ceca7a0139545 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Tue, 3 Sep 2024 08:55:03 +0300 Subject: [PATCH] fix: can not play offline content --- components/PlayButton.tsx | 104 +++++++++++++++---------- components/downloads/EpisodeCard.tsx | 6 +- components/downloads/MovieCard.tsx | 6 +- providers/PlaybackProvider.tsx | 112 +++++++++++++++++++-------- 4 files changed, 147 insertions(+), 81 deletions(-) diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 595afd25..1ff37d5e 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -27,6 +27,7 @@ import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; import { apiAtom } from "@/providers/JellyfinProvider"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; import { getParentBackdropImageUrl } from "@/utils/jellyfin/image/getParentBackdropImageUrl"; +import { Text } from "./common/Text"; interface Props extends React.ComponentProps { item?: BaseItemDto | null; @@ -55,6 +56,10 @@ export const PlayButton: React.FC = ({ item, url, ...props }) => { const widthProgress = useSharedValue(0); const colorChangeProgress = useSharedValue(0); + const directStream = useMemo(() => { + return url?.includes("m3u8"); + }, []); + const onPress = async () => { if (!url || !item) return; if (!client) { @@ -254,51 +259,64 @@ export const PlayButton: React.FC = ({ item, url, ...props }) => { */ return ( - - - - - - - + - - - {runtimeTicksToMinutes(item?.RunTimeTicks)} - - - - - {client && ( - - - - )} + + + + + + + + {runtimeTicksToMinutes(item?.RunTimeTicks)} + + + + + {client && ( + + + + )} + + + + + + + {directStream ? "Direct stream" : "Transcoded stream"} + - + ); }; diff --git a/components/downloads/EpisodeCard.tsx b/components/downloads/EpisodeCard.tsx index 9cbfbdd4..2f276b3b 100644 --- a/components/downloads/EpisodeCard.tsx +++ b/components/downloads/EpisodeCard.tsx @@ -23,14 +23,14 @@ interface EpisodeCardProps { export const EpisodeCard: React.FC = ({ item }) => { const { deleteFile } = useFiles(); - const { setCurrentlyPlayingState } = usePlayback(); + const { startDownloadedFilePlayback } = usePlayback(); const handleOpenFile = useCallback(async () => { - setCurrentlyPlayingState({ + startDownloadedFilePlayback({ item, url: `${FileSystem.documentDirectory}/${item.Id}.mp4`, }); - }, [item, setCurrentlyPlayingState]); + }, [item, startDownloadedFilePlayback]); /** * Handles deleting the file with haptic feedback. diff --git a/components/downloads/MovieCard.tsx b/components/downloads/MovieCard.tsx index 7a43631e..94e41cae 100644 --- a/components/downloads/MovieCard.tsx +++ b/components/downloads/MovieCard.tsx @@ -26,14 +26,14 @@ export const MovieCard: React.FC = ({ item }) => { const { deleteFile } = useFiles(); const [settings] = useSettings(); - const { setCurrentlyPlayingState } = usePlayback(); + const { startDownloadedFilePlayback } = usePlayback(); const handleOpenFile = useCallback(() => { - setCurrentlyPlayingState({ + startDownloadedFilePlayback({ item, url: `${FileSystem.documentDirectory}/${item.Id}.mp4`, }); - }, [item, setCurrentlyPlayingState]); + }, [item, startDownloadedFilePlayback]); /** * Handles deleting the file with haptic feedback. diff --git a/providers/PlaybackProvider.tsx b/providers/PlaybackProvider.tsx index 14fcf123..d66b6c2d 100644 --- a/providers/PlaybackProvider.tsx +++ b/providers/PlaybackProvider.tsx @@ -22,7 +22,7 @@ import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; import * as Linking from "expo-linking"; import { useAtom } from "jotai"; import { debounce } from "lodash"; -import { Alert, Platform } from "react-native"; +import { Alert } from "react-native"; import { OnProgressData, type VideoRef } from "react-native-video"; import { apiAtom, userAtom } from "./JellyfinProvider"; @@ -50,6 +50,9 @@ interface PlaybackContextType { setCurrentlyPlayingState: ( currentlyPlaying: CurrentlyPlayingState | null ) => void; + startDownloadedFilePlayback: ( + currentlyPlaying: CurrentlyPlayingState | null + ) => void; } const PlaybackContext = createContext(null); @@ -92,41 +95,85 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({ queryFn: getDeviceId, }); + const startDownloadedFilePlayback = useCallback( + async (state: CurrentlyPlayingState | null) => { + if (!state) { + setCurrentlyPlaying(null); + setIsPlaying(false); + return; + } + + setCurrentlyPlaying(state); + setIsPlaying(true); + if (settings?.openFullScreenVideoPlayerByDefault) { + setTimeout(() => { + presentFullscreenPlayer(); + }, 300); + } + }, + [settings?.openFullScreenVideoPlayerByDefault] + ); + const setCurrentlyPlayingState = useCallback( async (state: CurrentlyPlayingState | null) => { - if (!api) return; + try { + if (state?.item.Id && user?.Id) { + const vlcLink = "vlc://" + state?.url; + if (vlcLink && settings?.openInVLC) { + Linking.openURL("vlc://" + state?.url || ""); + return; + } - if (state && state.item.Id && user?.Id) { - const vlcLink = "vlc://" + state?.url; - if (vlcLink && settings?.openInVLC) { - Linking.openURL("vlc://" + state?.url || ""); - return; + const res = await getMediaInfoApi(api!).getPlaybackInfo({ + itemId: state.item.Id, + userId: user.Id, + }); + + await postCapabilities({ + api, + itemId: state.item.Id, + sessionId: res.data.PlaySessionId, + }); + + setSession(res.data); + setCurrentlyPlaying(state); + setIsPlaying(true); + + if (settings?.openFullScreenVideoPlayerByDefault) { + setTimeout(() => { + presentFullscreenPlayer(); + }, 300); + } + } else { + setCurrentlyPlaying(null); + setIsFullscreen(false); + setIsPlaying(false); } - - const res = await getMediaInfoApi(api).getPlaybackInfo({ - itemId: state.item.Id, - userId: user.Id, - }); - - await postCapabilities({ - api, - itemId: state.item.Id, - sessionId: res.data.PlaySessionId, - }); - - setSession(res.data); - setCurrentlyPlaying(state); - setIsPlaying(true); - - if (settings?.openFullScreenVideoPlayerByDefault) { - setTimeout(() => { - presentFullscreenPlayer(); - }, 300); - } - } else { - setCurrentlyPlaying(null); - setIsFullscreen(false); - setIsPlaying(false); + } catch (e) { + console.error(e); + Alert.alert( + "Something went wrong", + "The item could not be played. Maybe there is no internet connection?", + [ + { + style: "destructive", + text: "Try force play", + onPress: () => { + setCurrentlyPlaying(state); + setIsPlaying(true); + if (settings?.openFullScreenVideoPlayerByDefault) { + setTimeout(() => { + presentFullscreenPlayer(); + }, 300); + } + }, + }, + { + text: "Ok", + style: "default", + }, + ] + ); } }, [settings, user, api] @@ -320,6 +367,7 @@ export const PlaybackProvider: React.FC<{ children: ReactNode }> = ({ stopPlayback, presentFullscreenPlayer, dismissFullscreenPlayer, + startDownloadedFilePlayback, }} > {children}