mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
fix: can not play offline content
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user