mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-22 00:34:43 +01:00
fix: trailers for movies
This commit is contained in:
@@ -4,7 +4,7 @@ import { ParallaxScrollView } from "@/components/ParallaxPage";
|
|||||||
import { Ratings } from "@/components/Ratings";
|
import { Ratings } from "@/components/Ratings";
|
||||||
import { NextUp } from "@/components/series/NextUp";
|
import { NextUp } from "@/components/series/NextUp";
|
||||||
import { SeasonPicker } from "@/components/series/SeasonPicker";
|
import { SeasonPicker } from "@/components/series/SeasonPicker";
|
||||||
import { SeriesActions } from "@/components/series/SeriesActions";
|
import { ItemActions } from "@/components/series/SeriesActions";
|
||||||
import { SeriesHeader } from "@/components/series/SeriesHeader";
|
import { SeriesHeader } from "@/components/series/SeriesHeader";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { useGlobalSearchParams, useNavigation } from "expo-router";
|
import { useGlobalSearchParams } from "expo-router";
|
||||||
import { useState, useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Button, Dimensions } from "react-native";
|
import { Alert, Dimensions, View } from "react-native";
|
||||||
import { Alert, View } from "react-native";
|
|
||||||
import YoutubePlayer, { PLAYER_STATES } from "react-native-youtube-iframe";
|
import YoutubePlayer, { PLAYER_STATES } from "react-native-youtube-iframe";
|
||||||
|
|
||||||
export default function page() {
|
export default function page() {
|
||||||
const searchParams = useGlobalSearchParams();
|
const searchParams = useGlobalSearchParams();
|
||||||
const navigation = useNavigation();
|
|
||||||
console.log(searchParams);
|
console.log(searchParams);
|
||||||
|
|
||||||
const { url } = searchParams as { url: string };
|
const { url } = searchParams as { url: string };
|
||||||
@@ -29,15 +27,10 @@ export default function page() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.setOptions({
|
|
||||||
headerShown: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
togglePlaying();
|
togglePlaying();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const screenWidth = Dimensions.get("screen").width;
|
const screenWidth = Dimensions.get("screen").width;
|
||||||
const screenHeight = Dimensions.get("screen").height;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex flex-col bg-black items-center justify-center h-full">
|
<View className="flex flex-col bg-black items-center justify-center h-full">
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ function Layout() {
|
|||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="(auth)/trailer/page"
|
name="(auth)/trailer/page"
|
||||||
options={{
|
options={{
|
||||||
|
headerShown: false,
|
||||||
presentation: "modal",
|
presentation: "modal",
|
||||||
title: "",
|
title: "",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings";
|
|||||||
import { useImageColors } from "@/hooks/useImageColors";
|
import { useImageColors } from "@/hooks/useImageColors";
|
||||||
import { useOrientation } from "@/hooks/useOrientation";
|
import { useOrientation } from "@/hooks/useOrientation";
|
||||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { SubtitleHelper } from "@/utils/SubtitleHelper";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||||
import {
|
import {
|
||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
MediaStream,
|
|
||||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { useNavigation } from "expo-router";
|
import { useNavigation } from "expo-router";
|
||||||
@@ -31,10 +31,9 @@ import { View } from "react-native";
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Chromecast } from "./Chromecast";
|
import { Chromecast } from "./Chromecast";
|
||||||
import { ItemHeader } from "./ItemHeader";
|
import { ItemHeader } from "./ItemHeader";
|
||||||
|
import { ItemTechnicalDetails } from "./ItemTechnicalDetails";
|
||||||
import { MediaSourceSelector } from "./MediaSourceSelector";
|
import { MediaSourceSelector } from "./MediaSourceSelector";
|
||||||
import { MoreMoviesWithActor } from "./MoreMoviesWithActor";
|
import { MoreMoviesWithActor } from "./MoreMoviesWithActor";
|
||||||
import { SubtitleHelper } from "@/utils/SubtitleHelper";
|
|
||||||
import { ItemTechnicalDetails } from "./ItemTechnicalDetails";
|
|
||||||
|
|
||||||
export type SelectedOptions = {
|
export type SelectedOptions = {
|
||||||
bitrate: Bitrate;
|
bitrate: Bitrate;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
|
import React from "react";
|
||||||
import { View, ViewProps } from "react-native";
|
import { View, ViewProps } from "react-native";
|
||||||
|
import { GenreTags } from "./GenreTags";
|
||||||
import { MoviesTitleHeader } from "./movies/MoviesTitleHeader";
|
import { MoviesTitleHeader } from "./movies/MoviesTitleHeader";
|
||||||
import { Ratings } from "./Ratings";
|
import { Ratings } from "./Ratings";
|
||||||
import { EpisodeTitleHeader } from "./series/EpisodeTitleHeader";
|
import { EpisodeTitleHeader } from "./series/EpisodeTitleHeader";
|
||||||
import { GenreTags } from "./GenreTags";
|
import { ItemActions } from "./series/SeriesActions";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface Props extends ViewProps {
|
interface Props extends ViewProps {
|
||||||
item?: BaseItemDto | null;
|
item?: BaseItemDto | null;
|
||||||
@@ -27,7 +28,10 @@ export const ItemHeader: React.FC<Props> = ({ item, ...props }) => {
|
|||||||
return (
|
return (
|
||||||
<View className="flex flex-col" {...props}>
|
<View className="flex flex-col" {...props}>
|
||||||
<View className="flex flex-col" {...props}>
|
<View className="flex flex-col" {...props}>
|
||||||
<Ratings item={item} className="mb-2" />
|
<View className="flex flex-row items-center justify-between">
|
||||||
|
<Ratings item={item} className="mb-2" />
|
||||||
|
<ItemActions item={item} />
|
||||||
|
</View>
|
||||||
{item.Type === "Episode" && (
|
{item.Type === "Episode" && (
|
||||||
<>
|
<>
|
||||||
<EpisodeTitleHeader item={item} />
|
<EpisodeTitleHeader item={item} />
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { View, TouchableOpacity, ViewProps } from "react-native";
|
import { TouchableOpacity, View, ViewProps } from "react-native";
|
||||||
|
|
||||||
interface Props extends ViewProps {
|
interface Props extends ViewProps {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SeriesActions = ({ item, ...props }: Props) => {
|
export const ItemActions = ({ item, ...props }: Props) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const trailerLink = useMemo(() => item.RemoteTrailers?.[0]?.Url, [item]);
|
const trailerLink = useMemo(() => item.RemoteTrailers?.[0]?.Url, [item]);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Text } from "../common/Text";
|
|||||||
import { Ratings } from "../Ratings";
|
import { Ratings } from "../Ratings";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { SeriesActions } from "./SeriesActions";
|
import { ItemActions } from "./SeriesActions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
@@ -56,7 +56,7 @@ export const SeriesHeader = ({ item }: Props) => {
|
|||||||
<Text className="">{yearString}</Text>
|
<Text className="">{yearString}</Text>
|
||||||
<View className="flex flex-row items-center justify-between">
|
<View className="flex flex-row items-center justify-between">
|
||||||
<Ratings item={item} className="mb-2" />
|
<Ratings item={item} className="mb-2" />
|
||||||
<SeriesActions item={item} />
|
<ItemActions item={item} />
|
||||||
</View>
|
</View>
|
||||||
<Text className="">{item?.Overview}</Text>
|
<Text className="">{item?.Overview}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import {AppState, AppStateStatus, Platform} from "react-native";
|
import { AppState, AppStateStatus, Platform } from "react-native";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
import { apiAtom } from "./JellyfinProvider";
|
import { apiAtom } from "./JellyfinProvider";
|
||||||
import * as Notifications from "expo-notifications";
|
import * as Notifications from "expo-notifications";
|
||||||
@@ -195,7 +195,7 @@ function useDownloadProvider() {
|
|||||||
[settings?.optimizedVersionsServerUrl, authHeader]
|
[settings?.optimizedVersionsServerUrl, authHeader]
|
||||||
);
|
);
|
||||||
|
|
||||||
const APP_CACHE_DOWNLOAD_DIRECTORY = `${FileSystem.cacheDirectory}${Application.applicationId}/Downloads/`
|
const APP_CACHE_DOWNLOAD_DIRECTORY = `${FileSystem.cacheDirectory}${Application.applicationId}/Downloads/`;
|
||||||
|
|
||||||
const startDownload = useCallback(
|
const startDownload = useCallback(
|
||||||
async (process: JobStatus) => {
|
async (process: JobStatus) => {
|
||||||
@@ -423,32 +423,25 @@ function useDownloadProvider() {
|
|||||||
throw new Error("Base directory not found");
|
throw new Error("Base directory not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`ignoreList length: ${ignoreList?.length}`);
|
|
||||||
|
|
||||||
const dirContents = await FileSystem.readDirectoryAsync(baseDirectory);
|
const dirContents = await FileSystem.readDirectoryAsync(baseDirectory);
|
||||||
for (const item of dirContents) {
|
for (const item of dirContents) {
|
||||||
// Exclude mmkv directory.
|
// Exclude mmkv directory.
|
||||||
// Deleting this deletes all user information as well. Logout should handle this.
|
// Deleting this deletes all user information as well. Logout should handle this.
|
||||||
if (
|
if (
|
||||||
(item == "mmkv" && !includeMMKV) ||
|
(item == "mmkv" && !includeMMKV) ||
|
||||||
ignoreList.some(i => item.includes(i))
|
ignoreList.some((i) => item.includes(i))
|
||||||
) {
|
) {
|
||||||
console.log("Skipping read for item", item)
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await FileSystem.getInfoAsync(`${baseDirectory}${item}`)
|
await FileSystem.getInfoAsync(`${baseDirectory}${item}`)
|
||||||
.then((itemInfo) => {
|
.then((itemInfo) => {
|
||||||
console.log("Loading itemInfo", itemInfo);
|
|
||||||
|
|
||||||
if (itemInfo.exists && !itemInfo.isDirectory) {
|
if (itemInfo.exists && !itemInfo.isDirectory) {
|
||||||
callback(itemInfo);
|
callback(itemInfo);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e =>
|
.catch((e) => console.error(e));
|
||||||
console.error(e)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const deleteLocalFiles = async (): Promise<void> => {
|
const deleteLocalFiles = async (): Promise<void> => {
|
||||||
await forEveryDocumentDirFile(false, [], (file) => {
|
await forEveryDocumentDirFile(false, [], (file) => {
|
||||||
@@ -545,28 +538,36 @@ function useDownloadProvider() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cleanCacheDirectory = async () => {
|
const cleanCacheDirectory = async () => {
|
||||||
const cacheDir = await FileSystem.getInfoAsync(APP_CACHE_DOWNLOAD_DIRECTORY);
|
const cacheDir = await FileSystem.getInfoAsync(
|
||||||
|
APP_CACHE_DOWNLOAD_DIRECTORY
|
||||||
|
);
|
||||||
if (cacheDir.exists) {
|
if (cacheDir.exists) {
|
||||||
const cachedFiles = await FileSystem.readDirectoryAsync(APP_CACHE_DOWNLOAD_DIRECTORY)
|
const cachedFiles = await FileSystem.readDirectoryAsync(
|
||||||
let position = 0
|
APP_CACHE_DOWNLOAD_DIRECTORY
|
||||||
const batchSize = 3
|
);
|
||||||
|
let position = 0;
|
||||||
|
const batchSize = 3;
|
||||||
|
|
||||||
// batching promise.all to avoid OOM
|
// batching promise.all to avoid OOM
|
||||||
while (position < cachedFiles.length) {
|
while (position < cachedFiles.length) {
|
||||||
const itemsForBatch = cachedFiles.slice(position, position + batchSize)
|
const itemsForBatch = cachedFiles.slice(position, position + batchSize);
|
||||||
await Promise.all(itemsForBatch.map(async file => {
|
await Promise.all(
|
||||||
const info = await FileSystem.getInfoAsync(`${APP_CACHE_DOWNLOAD_DIRECTORY}${file}`)
|
itemsForBatch.map(async (file) => {
|
||||||
if (info.exists) {
|
const info = await FileSystem.getInfoAsync(
|
||||||
await FileSystem.deleteAsync(info.uri, { idempotent: true })
|
`${APP_CACHE_DOWNLOAD_DIRECTORY}${file}`
|
||||||
return Promise.resolve(file)
|
);
|
||||||
}
|
if (info.exists) {
|
||||||
return Promise.reject()
|
await FileSystem.deleteAsync(info.uri, { idempotent: true });
|
||||||
}))
|
return Promise.resolve(file);
|
||||||
|
}
|
||||||
|
return Promise.reject();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
position += batchSize
|
position += batchSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const deleteFileByType = async (type: BaseItemDto["Type"]) => {
|
const deleteFileByType = async (type: BaseItemDto["Type"]) => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -583,20 +584,22 @@ function useDownloadProvider() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const appSizeUsage = useMemo(async () => {
|
const appSizeUsage = useMemo(async () => {
|
||||||
const sizes: number[] = downloadedFiles?.map(d => {
|
const sizes: number[] =
|
||||||
return getDownloadedItemSize(d.item.Id!!)
|
downloadedFiles?.map((d) => {
|
||||||
}) || [];
|
return getDownloadedItemSize(d.item.Id!!);
|
||||||
|
}) || [];
|
||||||
|
|
||||||
await forEveryDocumentDirFile(
|
await forEveryDocumentDirFile(
|
||||||
true,
|
true,
|
||||||
getAllDownloadedItems().map(d => d.item.Id!!),
|
getAllDownloadedItems().map((d) => d.item.Id!!),
|
||||||
(file) => {
|
(file) => {
|
||||||
if (file.exists) {
|
if (file.exists) {
|
||||||
sizes.push(file.size);
|
sizes.push(file.size);
|
||||||
}
|
}
|
||||||
}).catch(e => {
|
}
|
||||||
console.error(e)
|
).catch((e) => {
|
||||||
})
|
console.error(e);
|
||||||
|
});
|
||||||
|
|
||||||
return sizes.reduce((sum, size) => sum + size, 0);
|
return sizes.reduce((sum, size) => sum + size, 0);
|
||||||
}, [logs, downloadedFiles, forEveryDocumentDirFile]);
|
}, [logs, downloadedFiles, forEveryDocumentDirFile]);
|
||||||
@@ -690,7 +693,7 @@ function useDownloadProvider() {
|
|||||||
appSizeUsage,
|
appSizeUsage,
|
||||||
getDownloadedItemSize,
|
getDownloadedItemSize,
|
||||||
APP_CACHE_DOWNLOAD_DIRECTORY,
|
APP_CACHE_DOWNLOAD_DIRECTORY,
|
||||||
cleanCacheDirectory
|
cleanCacheDirectory,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user