mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 15:48:05 +00:00
fix
This commit is contained in:
17
app.json
17
app.json
@@ -39,11 +39,7 @@
|
||||
"expo-router",
|
||||
"expo-font",
|
||||
"react-native-compressor",
|
||||
[
|
||||
"react-native-google-cast",
|
||||
{
|
||||
}
|
||||
],
|
||||
["react-native-google-cast", {}],
|
||||
[
|
||||
"react-native-video",
|
||||
{
|
||||
@@ -56,6 +52,17 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"react-native-vlc-media-player",
|
||||
{
|
||||
"ios": {
|
||||
"includeVLCKit": false
|
||||
},
|
||||
"android": {
|
||||
"legacyJetifier": false
|
||||
}
|
||||
}
|
||||
],
|
||||
["expo-build-properties", { "ios": { "deploymentTarget": "14.0" } }]
|
||||
],
|
||||
"experiments": {
|
||||
|
||||
@@ -27,6 +27,16 @@ export default function TabLayout() {
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
style={{ marginHorizontal: 17 }}
|
||||
onPress={() => {
|
||||
router.push("/(auth)/downloads");
|
||||
}}
|
||||
>
|
||||
<Feather name="download" color={"white"} size={24} />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
headerRight: () => (
|
||||
<TouchableOpacity
|
||||
style={{ marginHorizontal: 17 }}
|
||||
|
||||
@@ -132,8 +132,6 @@ export default function index() {
|
||||
</View>
|
||||
);
|
||||
|
||||
if (!data || data.length === 0) return <Text>No data...</Text>;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
nestedScrollEnabled
|
||||
|
||||
85
app/(auth)/downloads.tsx
Normal file
85
app/(auth)/downloads.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { EpisodeCard } from "@/components/downloads/EpisodeCard";
|
||||
import { MovieCard } from "@/components/downloads/MovieCard";
|
||||
import { SeriesCard } from "@/components/downloads/SeriesCard";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
|
||||
const downloads: React.FC = () => {
|
||||
const { data: downloadedFiles } = useQuery({
|
||||
queryKey: ["downloaded_files"],
|
||||
queryFn: async () =>
|
||||
JSON.parse(
|
||||
(await AsyncStorage.getItem("downloaded_files")) || "[]"
|
||||
) as BaseItemDto[],
|
||||
});
|
||||
|
||||
const movies = useMemo(
|
||||
() => downloadedFiles?.filter((f) => f.Type === "Movie"),
|
||||
[downloadedFiles]
|
||||
);
|
||||
|
||||
const groupedBySeries = useMemo(() => {
|
||||
const episodes = downloadedFiles?.filter((f) => f.Type === "Episode");
|
||||
const series: { [key: string]: BaseItemDto[] } = {};
|
||||
episodes?.forEach((e) => {
|
||||
if (!series[e.SeriesName!]) series[e.SeriesName!] = [];
|
||||
series[e.SeriesName!].push(e);
|
||||
});
|
||||
return Object.values(series);
|
||||
}, [downloadedFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
downloadedFiles?.map((i) => ({
|
||||
name: i.Name,
|
||||
codec: i.SourceType,
|
||||
media: i.MediaSources?.[0].Container,
|
||||
}))
|
||||
);
|
||||
}, [downloadedFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
// Get all files from FileStorage
|
||||
// const filename = `${itemId}.mp4`;
|
||||
// const fileUri = `${FileSystem.documentDirectory}`;
|
||||
(async () => {
|
||||
if (!FileSystem.documentDirectory) return;
|
||||
const f = await FileSystem.readDirectoryAsync(
|
||||
FileSystem.documentDirectory
|
||||
);
|
||||
console.log("files", FileSystem.documentDirectory, f);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View className="px-4 py-4">
|
||||
<View className="mb-4">
|
||||
<View className="flex flex-row items-center justify-between mb-2">
|
||||
<Text className="text-2xl font-bold">Movies</Text>
|
||||
<View className="bg-purple-600 rounded-full h-6 w-6 flex items-center justify-center">
|
||||
<Text className="text-xs font-bold">{movies?.length}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{movies?.map((item: BaseItemDto) => (
|
||||
<View className="mb-2 last:mb-0" key={item.Id}>
|
||||
<MovieCard item={item} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View>
|
||||
{groupedBySeries?.map((items: BaseItemDto[], index: number) => (
|
||||
<SeriesCard items={items} key={items[0].SeriesId} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default downloads;
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Chromecast } from "@/components/Chromecast";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { DownloadItem } from "@/components/DownloadItem";
|
||||
import { PlayedStatus } from "@/components/PlayedStatus";
|
||||
@@ -9,7 +10,6 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import {
|
||||
getBackdrop,
|
||||
getLogoImageById,
|
||||
getPrimaryImage,
|
||||
getUserItemData,
|
||||
} from "@/utils/jellyfin";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
@@ -24,8 +24,6 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { ParallaxScrollView } from "../../../../components/ParallaxPage";
|
||||
import { Chromecast } from "@/components/Chromecast";
|
||||
import { useRemoteMediaClient } from "react-native-google-cast";
|
||||
|
||||
const page: React.FC = () => {
|
||||
const local = useLocalSearchParams();
|
||||
@@ -114,7 +112,7 @@ const page: React.FC = () => {
|
||||
{item?.SeriesName}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View className="flex flex-row items-center self-center">
|
||||
<View className="flex flex-row items-center self-center px-4">
|
||||
<Text className="text-center font-bold text-2xl mr-2">
|
||||
{item?.Name}
|
||||
</Text>
|
||||
|
||||
@@ -9,7 +9,9 @@ export default function page() {
|
||||
const { itemId, url } = searchParams as { itemId: string; url: string };
|
||||
|
||||
const fileUrl = useMemo(() => {
|
||||
return FileSystem.documentDirectory + url;
|
||||
const u = FileSystem.documentDirectory + url;
|
||||
console.log({ u });
|
||||
return u;
|
||||
}, [url]);
|
||||
|
||||
if (!fileUrl) return null;
|
||||
|
||||
@@ -77,6 +77,19 @@ export default function RootLayout() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="(auth)/downloads"
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: "Downloads",
|
||||
presentation: "modal",
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity onPress={() => router.back()}>
|
||||
<Feather name="x-circle" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="(auth)/player/offline/page"
|
||||
options={{
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
import ContinueWatchingPoster from "./ContinueWatchingPoster";
|
||||
import { ItemCardText } from "./ItemCardText";
|
||||
import { Text } from "./common/Text";
|
||||
import MoviePoster from "./MoviePoster";
|
||||
import { useMemo } from "react";
|
||||
|
||||
type SimilarItemsProps = {
|
||||
itemId: string;
|
||||
@@ -38,6 +40,11 @@ export const SimilarItems: React.FC<SimilarItemsProps> = ({ itemId }) => {
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
const movies = useMemo(
|
||||
() => similarItems?.filter((i) => i.Type === "Movie") || [],
|
||||
[similarItems]
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text className="px-4 text-2xl font-bold mb-2">Similar items</Text>
|
||||
@@ -48,22 +55,20 @@ export const SimilarItems: React.FC<SimilarItemsProps> = ({ itemId }) => {
|
||||
) : (
|
||||
<ScrollView horizontal>
|
||||
<View className="px-4 flex flex-row gap-x-2">
|
||||
{similarItems?.map((item) => (
|
||||
{movies.map((item) => (
|
||||
<TouchableOpacity
|
||||
key={item.Id}
|
||||
onPress={() => router.push(`/items/${item.Id}/page`)}
|
||||
className="flex flex-col w-48"
|
||||
className="flex flex-col w-32"
|
||||
>
|
||||
<ContinueWatchingPoster item={item} />
|
||||
<MoviePoster item={item} />
|
||||
<ItemCardText item={item} />
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
)}
|
||||
{similarItems?.length === 0 && (
|
||||
<Text className="px-4">No similar items</Text>
|
||||
)}
|
||||
{movies.length === 0 && <Text className="px-4">No similar items</Text>}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
23
components/downloads/EpisodeCard.tsx
Normal file
23
components/downloads/EpisodeCard.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { Text } from "../common/Text";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { router } from "expo-router";
|
||||
|
||||
export const EpisodeCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const open = () => {
|
||||
router.back();
|
||||
router.push(
|
||||
`/(auth)/player/offline/page?url=${item.Id}.${item.MediaSources?.[0].Container}&itemId=${item.Id}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={open}
|
||||
className="bg-neutral-800 border border-neutral-900 rounded-2xl p-4"
|
||||
>
|
||||
<Text className=" font-bold">{item.Name}</Text>
|
||||
<Text className=" text-xs opacity-50">Episode {item.IndexNumber}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
18
components/downloads/MovieCard.tsx
Normal file
18
components/downloads/MovieCard.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { View } from "react-native";
|
||||
import { Text } from "../common/Text";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||
|
||||
export const MovieCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
return (
|
||||
<View className="bg-neutral-800 border border-neutral-900 rounded-2xl p-4">
|
||||
<Text className=" font-bold">{item.Name}</Text>
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<Text className=" text-xs opacity-50">{item.ProductionYear}</Text>
|
||||
<Text className=" text-xs opacity-50">
|
||||
{runtimeTicksToMinutes(item.RunTimeTicks)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
49
components/downloads/SeriesCard.tsx
Normal file
49
components/downloads/SeriesCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { View } from "react-native";
|
||||
import { EpisodeCard } from "./EpisodeCard";
|
||||
import { Text } from "../common/Text";
|
||||
import { useMemo } from "react";
|
||||
import { SeasonPicker } from "../series/SeasonPicker";
|
||||
|
||||
export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({ items }) => {
|
||||
const groupBySeason = useMemo(() => {
|
||||
const seasons: Record<string, BaseItemDto[]> = {};
|
||||
|
||||
items.forEach((item) => {
|
||||
if (!seasons[item.SeasonName!]) {
|
||||
seasons[item.SeasonName!] = [];
|
||||
}
|
||||
|
||||
seasons[item.SeasonName!].push(item);
|
||||
});
|
||||
|
||||
return Object.values(seasons).sort(
|
||||
(a, b) => a[0].IndexNumber! - b[0].IndexNumber!
|
||||
);
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<Text className="text-2xl font-bold">{items[0].SeriesName}</Text>
|
||||
<View className="bg-purple-600 rounded-full h-6 w-6 flex items-center justify-center">
|
||||
<Text className="text-xs font-bold">{items.length}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text className="opacity-50 mb-2">TV-Series</Text>
|
||||
{groupBySeason.map((seasonItems, seasonIndex) => (
|
||||
<View key={seasonIndex}>
|
||||
<Text className="mb-2 font-semibold">
|
||||
{seasonItems[0].SeasonName}
|
||||
</Text>
|
||||
{seasonItems.map((item, index) => (
|
||||
<View className="mb-2">
|
||||
<EpisodeCard item={item} key={item.Id} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -329,6 +329,7 @@ PODS:
|
||||
- MMKV (1.3.4):
|
||||
- MMKVCore (~> 1.3.4)
|
||||
- MMKVCore (1.3.9)
|
||||
- MobileVLCKit (3.5.1)
|
||||
- PromisesObjC (2.4.0)
|
||||
- Protobuf (3.27.3)
|
||||
- RCT-Folly (2024.01.01.00):
|
||||
@@ -1291,6 +1292,8 @@ PODS:
|
||||
- React
|
||||
- react-native-menu (1.1.2):
|
||||
- React
|
||||
- react-native-netinfo (11.3.1):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.10.5):
|
||||
- React-Core
|
||||
- react-native-video (6.4.3):
|
||||
@@ -1336,6 +1339,10 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-vlc-media-player (1.0.38):
|
||||
- MobileVLCKit (= 3.5.1)
|
||||
- React
|
||||
- TVVLCKit (= 3.5.1)
|
||||
- React-nativeconfig (0.74.3)
|
||||
- React-NativeModulesApple (0.74.3):
|
||||
- glog
|
||||
@@ -1717,8 +1724,10 @@ DEPENDENCIES:
|
||||
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
|
||||
- react-native-google-cast (from `../node_modules/react-native-google-cast`)
|
||||
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-video (from `../node_modules/react-native-video`)
|
||||
- react-native-vlc-media-player (from `../node_modules/react-native-vlc-media-player`)
|
||||
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
|
||||
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
|
||||
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||
@@ -1763,6 +1772,7 @@ SPEC REPOS:
|
||||
- libwebp
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- MobileVLCKit
|
||||
- PromisesObjC
|
||||
- Protobuf
|
||||
- SDWebImage
|
||||
@@ -1889,10 +1899,14 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-google-cast"
|
||||
react-native-menu:
|
||||
:path: "../node_modules/@react-native-menu/menu"
|
||||
react-native-netinfo:
|
||||
:path: "../node_modules/@react-native-community/netinfo"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-video:
|
||||
:path: "../node_modules/react-native-video"
|
||||
react-native-vlc-media-player:
|
||||
:path: "../node_modules/react-native-vlc-media-player"
|
||||
React-nativeconfig:
|
||||
:path: "../node_modules/react-native/ReactCommon"
|
||||
React-NativeModulesApple:
|
||||
@@ -1996,6 +2010,7 @@ SPEC CHECKSUMS:
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74
|
||||
MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9
|
||||
MobileVLCKit: 144d5f565512d1147d63b0fa1379231b3fd66535
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
Protobuf: c1cbc880ea7c4e9b157e113515c720a22ee7cf50
|
||||
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
|
||||
@@ -2027,8 +2042,10 @@ SPEC CHECKSUMS:
|
||||
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
|
||||
react-native-google-cast: 77c4a3acca9eab527328f8b65257d952be5f220f
|
||||
react-native-menu: d32728a357dfb360cf01cd5979cf7713c5acbb95
|
||||
react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321
|
||||
react-native-safe-area-context: a240ad4b683349e48b1d51fed1611138d1bdad97
|
||||
react-native-video: b3ba8f424c8c3f54dd9289d47bbe60fbc09bc986
|
||||
react-native-vlc-media-player: 0f9860bded57cd6764ac55b5c0e97347069cb40a
|
||||
React-nativeconfig: fa5de9d8f4dbd5917358f8ad3ad1e08762f01dcb
|
||||
React-NativeModulesApple: 585d1b78e0597de364d259cb56007052d0bda5e5
|
||||
React-perflogger: 7bb9ba49435ff66b666e7966ee10082508a203e8
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
303D6C1B994E4AB730BB4D25 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 5677A7148111D7C0235E3185 /* PrivacyInfo.xcprivacy */; };
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
||||
76DE60737AC6B111D4CF6135 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6AFEBF12046598BC1A9FEC11 /* PrivacyInfo.xcprivacy */; };
|
||||
7B8C1837932545C6A7001A29 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46971613083A41BAB9656F14 /* noop-file.swift */; };
|
||||
96905EF65AED1B983A6B3ABC /* libPods-Streamyfin.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Streamyfin.a */; };
|
||||
9B470C0ACF444752B7807218 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BFAF74683646218FA987CE /* noop-file.swift */; };
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -25,14 +25,14 @@
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Streamyfin/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Streamyfin/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Streamyfin/main.m; sourceTree = "<group>"; };
|
||||
2092729FD1D54853BF6A8489 /* Streamyfin-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Streamyfin-Bridging-Header.h"; path = "Streamyfin/Streamyfin-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
46971613083A41BAB9656F14 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Streamyfin/noop-file.swift"; sourceTree = "<group>"; };
|
||||
5677A7148111D7C0235E3185 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Streamyfin/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Streamyfin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Streamyfin.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6AFEBF12046598BC1A9FEC11 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Streamyfin/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
6C2E3173556A471DD304B334 /* Pods-Streamyfin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Streamyfin.debug.xcconfig"; path = "Target Support Files/Pods-Streamyfin/Pods-Streamyfin.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7A4D352CD337FB3A3BF06240 /* Pods-Streamyfin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Streamyfin.release.xcconfig"; path = "Target Support Files/Pods-Streamyfin/Pods-Streamyfin.release.xcconfig"; sourceTree = "<group>"; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Streamyfin/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
B3BFAF74683646218FA987CE /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Streamyfin/noop-file.swift"; sourceTree = "<group>"; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
E9097609D0714599AF91F55B /* Streamyfin-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Streamyfin-Bridging-Header.h"; path = "Streamyfin/Streamyfin-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Streamyfin/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -59,9 +59,9 @@
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
||||
B3BFAF74683646218FA987CE /* noop-file.swift */,
|
||||
2092729FD1D54853BF6A8489 /* Streamyfin-Bridging-Header.h */,
|
||||
6AFEBF12046598BC1A9FEC11 /* PrivacyInfo.xcprivacy */,
|
||||
46971613083A41BAB9656F14 /* noop-file.swift */,
|
||||
E9097609D0714599AF91F55B /* Streamyfin-Bridging-Header.h */,
|
||||
5677A7148111D7C0235E3185 /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
name = Streamyfin;
|
||||
sourceTree = "<group>";
|
||||
@@ -147,13 +147,13 @@
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Streamyfin" */;
|
||||
buildPhases = (
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||
E4ADA958330463261FFFF98E /* [Expo] Configure project */,
|
||||
5AE4F51AB7DD565AD6F401F0 /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||
FE3C21F6FE840DF7275CA666 /* [CP] Embed Pods Frameworks */,
|
||||
E8388EA82119C85BBE90EA67 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -203,7 +203,7 @@
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
||||
76DE60737AC6B111D4CF6135 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
303D6C1B994E4AB730BB4D25 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -247,6 +247,25 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
5AE4F51AB7DD565AD6F401F0 /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[Expo] Configure project";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Streamyfin/expo-configure-project.sh\"\n";
|
||||
};
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -295,32 +314,14 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Streamyfin/Pods-Streamyfin-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E4ADA958330463261FFFF98E /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[Expo] Configure project";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Streamyfin/expo-configure-project.sh\"\n";
|
||||
};
|
||||
FE3C21F6FE840DF7275CA666 /* [CP] Embed Pods Frameworks */ = {
|
||||
E8388EA82119C85BBE90EA67 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Streamyfin/Pods-Streamyfin-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileVLCKit/MobileVLCKit.framework/MobileVLCKit",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/ffmpegkit.framework/ffmpegkit",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavcodec.framework/libavcodec",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavdevice.framework/libavdevice",
|
||||
@@ -333,6 +334,7 @@
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileVLCKit.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ffmpegkit.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavcodec.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavdevice.framework",
|
||||
@@ -358,7 +360,7 @@
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
|
||||
9B470C0ACF444752B7807218 /* noop-file.swift in Sources */,
|
||||
7B8C1837932545C6A7001A29 /* noop-file.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
options.suspendSessionsWhenBackgrounded = true;
|
||||
[GCKCastContext setSharedInstanceWithOptions:options];
|
||||
[GCKCastContext sharedInstance].useDefaultExpandedMediaControls = true;
|
||||
|
||||
#endif
|
||||
// @generated end react-native-google-cast-didFinishLaunchingWithOptions
|
||||
self.moduleName = @"main";
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@jellyfin/sdk": "^0.10.0",
|
||||
"@kesha-antonov/react-native-background-downloader": "^3.2.0",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-native-community/netinfo": "11.3.1",
|
||||
"@react-native-menu/menu": "^1.1.2",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"@tanstack/react-query": "^5.51.16",
|
||||
@@ -57,6 +58,7 @@
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.2",
|
||||
"react-native-video": "^6.4.3",
|
||||
"react-native-vlc-media-player": "^1.0.67",
|
||||
"react-native-web": "~0.19.10",
|
||||
"tailwindcss": "3.3.2",
|
||||
"uuid": "^10.0.0",
|
||||
|
||||
@@ -15,7 +15,35 @@ import { useAtom } from "jotai";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { runningProcesses } from "./atoms/downloads";
|
||||
import { iosProfile } from "./device-profiles";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { FFmpegKit, ReturnCode } from "ffmpeg-kit-react-native";
|
||||
|
||||
const convertAndReplaceVideo = async (inputUri: string) => {
|
||||
const tempOutputUri = inputUri.replace(/\.\w+$/, "_temp.mp4");
|
||||
|
||||
// Strip the file:/// prefix
|
||||
const inputPath = inputUri.replace("file://", "");
|
||||
const tempOutputPath = tempOutputUri.replace("file://", "");
|
||||
|
||||
const command = `-i ${inputPath} -c:v libx264 -profile:v baseline -level 3.0 -pix_fmt yuv420p -c:a aac -b:a 128k -movflags +faststart ${tempOutputPath}`;
|
||||
try {
|
||||
const session = await FFmpegKit.execute(command);
|
||||
const rc: ReturnCode = await session.getReturnCode();
|
||||
if (ReturnCode.isSuccess(rc)) {
|
||||
console.log("Conversion successful, replacing the original file");
|
||||
|
||||
await FileSystem.moveAsync({
|
||||
from: tempOutputUri,
|
||||
to: inputUri,
|
||||
});
|
||||
|
||||
console.log("Replacement successful");
|
||||
} else {
|
||||
console.log("Conversion failed");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during conversion", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const useDownloadMedia = (api: Api | null, userId?: string | null) => {
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
@@ -32,41 +60,24 @@ export const useDownloadMedia = (api: Api | null, userId?: string | null) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("MediaSources: ", JSON.stringify(item.MediaSources));
|
||||
console.log("MediaStreams: ", JSON.stringify(item.MediaStreams));
|
||||
|
||||
setIsDownloading(true);
|
||||
setError(null);
|
||||
setProgress({
|
||||
item,
|
||||
progress: 0,
|
||||
});
|
||||
|
||||
const itemId = item.Id;
|
||||
|
||||
console.info("Downloading media item", item);
|
||||
|
||||
// const playbackData = await getMediaInfoApi(api!).getPlaybackInfo({
|
||||
// itemId,
|
||||
// userId: userId,
|
||||
// });
|
||||
|
||||
// const url = await getStreamUrl({
|
||||
// api,
|
||||
// userId: userId,
|
||||
// item,
|
||||
// startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0,
|
||||
// sessionData: playbackData.data,
|
||||
// });
|
||||
|
||||
// if (!url) {
|
||||
// setError("Could not get stream URL");
|
||||
// setIsDownloading(false);
|
||||
// setProgress(null);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
try {
|
||||
const filename = `${itemId}.mp4`;
|
||||
const filename = `${itemId}`;
|
||||
const fileUri = `${FileSystem.documentDirectory}${filename}`;
|
||||
|
||||
const url = `${api.basePath}/Items/${itemId}/Download`;
|
||||
|
||||
console.info("Starting download of media item from URL", url);
|
||||
|
||||
downloadResumableRef.current = FileSystem.createDownloadResumable(
|
||||
url,
|
||||
fileUri,
|
||||
@@ -106,6 +117,8 @@ export const useDownloadMedia = (api: Api | null, userId?: string | null) => {
|
||||
JSON.stringify(updatedFiles)
|
||||
);
|
||||
|
||||
await convertAndReplaceVideo(fileUri);
|
||||
|
||||
setIsDownloading(false);
|
||||
setProgress(null);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user