fix: trailers for movies

This commit is contained in:
Fredrik Burmester
2024-12-31 21:47:43 +01:00
parent 479a1f037e
commit f8d22bb7d6
8 changed files with 56 additions and 56 deletions

View File

@@ -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";

View File

@@ -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">

View File

@@ -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: "",
}} }}

View File

@@ -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;

View File

@@ -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} />

View File

@@ -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]);

View File

@@ -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>

View File

@@ -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,
}; };
} }