fix: can not play offline content

This commit is contained in:
Fredrik Burmester
2024-09-03 08:55:03 +03:00
parent 2509a8d6e2
commit 67214a81c4
4 changed files with 147 additions and 81 deletions

View File

@@ -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<typeof Button> {
item?: BaseItemDto | null;
@@ -55,6 +56,10 @@ export const PlayButton: React.FC<Props> = ({ 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<Props> = ({ item, url, ...props }) => {
*/
return (
<TouchableOpacity
accessibilityLabel="Play button"
accessibilityHint="Tap to play the media"
onPress={onPress}
className="relative"
{...props}
>
<View className="absolute w-full h-full top-0 left-0 rounded-xl z-10 overflow-hidden">
<Animated.View
style={[
animatedPrimaryStyle,
animatedWidthStyle,
{
height: "100%",
},
]}
/>
</View>
<Animated.View
style={[animatedAverageStyle]}
className="absolute w-full h-full top-0 left-0 rounded-xl"
/>
<View
style={{
borderWidth: 1,
borderColor: colorAtom.primary,
borderStyle: "solid",
}}
className="flex flex-row items-center justify-center bg-transparent rounded-xl z-20 h-12 w-full "
<View>
<TouchableOpacity
accessibilityLabel="Play button"
accessibilityHint="Tap to play the media"
onPress={onPress}
className="relative"
{...props}
>
<View className="flex flex-row items-center space-x-2">
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
{runtimeTicksToMinutes(item?.RunTimeTicks)}
</Animated.Text>
<Animated.Text style={animatedTextStyle}>
<Ionicons name="play-circle" size={24} />
</Animated.Text>
{client && (
<Animated.Text style={animatedTextStyle}>
<Feather name="cast" size={22} />
</Animated.Text>
)}
<View className="absolute w-full h-full top-0 left-0 rounded-xl z-10 overflow-hidden">
<Animated.View
style={[
animatedPrimaryStyle,
animatedWidthStyle,
{
height: "100%",
},
]}
/>
</View>
<Animated.View
style={[animatedAverageStyle]}
className="absolute w-full h-full top-0 left-0 rounded-xl"
/>
<View
style={{
borderWidth: 1,
borderColor: colorAtom.primary,
borderStyle: "solid",
}}
className="flex flex-row items-center justify-center bg-transparent rounded-xl z-20 h-12 w-full "
>
<View className="flex flex-row items-center space-x-2">
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
{runtimeTicksToMinutes(item?.RunTimeTicks)}
</Animated.Text>
<Animated.Text style={animatedTextStyle}>
<Ionicons name="play-circle" size={24} />
</Animated.Text>
{client && (
<Animated.Text style={animatedTextStyle}>
<Feather name="cast" size={22} />
</Animated.Text>
)}
</View>
</View>
</TouchableOpacity>
<View className="mt-2 flex flex-row items-center">
<Ionicons
name="information-circle"
size={12}
className=""
color={"#9BA1A6"}
/>
<Text className="text-neutral-500 ml-1">
{directStream ? "Direct stream" : "Transcoded stream"}
</Text>
</View>
</TouchableOpacity>
</View>
);
};

View File

@@ -23,14 +23,14 @@ interface EpisodeCardProps {
export const EpisodeCard: React.FC<EpisodeCardProps> = ({ 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.

View File

@@ -26,14 +26,14 @@ export const MovieCard: React.FC<MovieCardProps> = ({ 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.

View File

@@ -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<PlaybackContextType | null>(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}