forked from Ninjalama/streamyfin_mirror
Compare commits
6 Commits
master
...
feature/se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
343a2cb3b5 | ||
|
|
49d1be6bdf | ||
|
|
32687332b3 | ||
|
|
7c96443532 | ||
|
|
bf8728eeab | ||
|
|
d7ae41907e |
108
app/_layout.tsx
108
app/_layout.tsx
@@ -2,30 +2,26 @@ import "@/augmentations";
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import { DownloadProvider } from "@/providers/DownloadProvider";
|
import { DownloadProvider } from "@/providers/DownloadProvider";
|
||||||
import {
|
import { getOrSetDeviceId, getTokenFromStorage, JellyfinProvider, apiAtom } from "@/providers/JellyfinProvider";
|
||||||
getOrSetDeviceId,
|
|
||||||
getTokenFromStorage,
|
|
||||||
JellyfinProvider,
|
|
||||||
} from "@/providers/JellyfinProvider";
|
|
||||||
import { JobQueueProvider } from "@/providers/JobQueueProvider";
|
import { JobQueueProvider } from "@/providers/JobQueueProvider";
|
||||||
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
|
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
|
||||||
import { WebSocketProvider } from "@/providers/WebSocketProvider";
|
import { WebSocketProvider } from "@/providers/WebSocketProvider";
|
||||||
import { Settings, useSettings } from "@/utils/atoms/settings";
|
import { Settings, useSettings } from "@/utils/atoms/settings";
|
||||||
import { BACKGROUND_FETCH_TASK } from "@/utils/background-tasks";
|
import {
|
||||||
|
BACKGROUND_FETCH_TASK,
|
||||||
|
BACKGROUND_FETCH_TASK_SESSIONS,
|
||||||
|
registerBackgroundFetchAsyncSessions,
|
||||||
|
} from "@/utils/background-tasks";
|
||||||
import { LogProvider, writeToLog } from "@/utils/log";
|
import { LogProvider, writeToLog } from "@/utils/log";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
|
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
|
||||||
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
||||||
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
const BackGroundDownloader = !Platform.isTV
|
const BackGroundDownloader = !Platform.isTV ? require("@kesha-antonov/react-native-background-downloader") : null;
|
||||||
? require("@kesha-antonov/react-native-background-downloader")
|
|
||||||
: null;
|
|
||||||
import { DarkTheme, ThemeProvider } from "@react-navigation/native";
|
import { DarkTheme, ThemeProvider } from "@react-navigation/native";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
const BackgroundFetch = !Platform.isTV
|
const BackgroundFetch = !Platform.isTV ? require("expo-background-fetch") : null;
|
||||||
? require("expo-background-fetch")
|
|
||||||
: null;
|
|
||||||
import * as FileSystem from "expo-file-system";
|
import * as FileSystem from "expo-file-system";
|
||||||
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
|
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
|
||||||
import { router, Stack } from "expo-router";
|
import { router, Stack } from "expo-router";
|
||||||
@@ -41,6 +37,10 @@ import { SystemBars } from "react-native-edge-to-edge";
|
|||||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
import { Toaster } from "sonner-native";
|
import { Toaster } from "sonner-native";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
||||||
|
import { store } from "@/utils/store";
|
||||||
|
|
||||||
if (!Platform.isTV) {
|
if (!Platform.isTV) {
|
||||||
Notifications.setNotificationHandler({
|
Notifications.setNotificationHandler({
|
||||||
@@ -74,20 +74,16 @@ function useNotificationObserver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifications.getLastNotificationResponseAsync().then(
|
Notifications.getLastNotificationResponseAsync().then((response: { notification: any }) => {
|
||||||
(response: { notification: any }) => {
|
if (!isMounted || !response?.notification) {
|
||||||
if (!isMounted || !response?.notification) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
redirect(response?.notification);
|
|
||||||
}
|
}
|
||||||
);
|
redirect(response?.notification);
|
||||||
|
});
|
||||||
|
|
||||||
const subscription = Notifications.addNotificationResponseReceivedListener(
|
const subscription = Notifications.addNotificationResponseReceivedListener((response: { notification: any }) => {
|
||||||
(response: { notification: any }) => {
|
redirect(response.notification);
|
||||||
redirect(response.notification);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
@@ -97,6 +93,22 @@ function useNotificationObserver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Platform.isTV) {
|
if (!Platform.isTV) {
|
||||||
|
TaskManager.defineTask(BACKGROUND_FETCH_TASK_SESSIONS, async () => {
|
||||||
|
console.log("TaskManager ~ sessions trigger");
|
||||||
|
|
||||||
|
const api = store.get(apiAtom);
|
||||||
|
if (api === null || api === undefined) return;
|
||||||
|
|
||||||
|
const response = await getSessionApi(api).getSessions({
|
||||||
|
activeWithinSeconds: 360,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = response.data.filter((s) => s.NowPlayingItem);
|
||||||
|
Notifications.setBadgeCountAsync(result.length);
|
||||||
|
|
||||||
|
return BackgroundFetch.BackgroundFetchResult.NewData;
|
||||||
|
});
|
||||||
|
|
||||||
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
||||||
console.log("TaskManager ~ trigger");
|
console.log("TaskManager ~ trigger");
|
||||||
|
|
||||||
@@ -109,15 +121,13 @@ if (!Platform.isTV) {
|
|||||||
const settings: Partial<Settings> = JSON.parse(settingsData);
|
const settings: Partial<Settings> = JSON.parse(settingsData);
|
||||||
const url = settings?.optimizedVersionsServerUrl;
|
const url = settings?.optimizedVersionsServerUrl;
|
||||||
|
|
||||||
if (!settings?.autoDownload || !url)
|
if (!settings?.autoDownload || !url) return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
|
||||||
|
|
||||||
const token = getTokenFromStorage();
|
const token = getTokenFromStorage();
|
||||||
const deviceId = getOrSetDeviceId();
|
const deviceId = getOrSetDeviceId();
|
||||||
const baseDirectory = FileSystem.documentDirectory;
|
const baseDirectory = FileSystem.documentDirectory;
|
||||||
|
|
||||||
if (!token || !deviceId || !baseDirectory)
|
if (!token || !deviceId || !baseDirectory) return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
|
||||||
|
|
||||||
const jobs = await getAllJobsByDeviceId({
|
const jobs = await getAllJobsByDeviceId({
|
||||||
deviceId,
|
deviceId,
|
||||||
@@ -194,9 +204,7 @@ if (!Platform.isTV) {
|
|||||||
|
|
||||||
const checkAndRequestPermissions = async () => {
|
const checkAndRequestPermissions = async () => {
|
||||||
try {
|
try {
|
||||||
const hasAskedBefore = storage.getString(
|
const hasAskedBefore = storage.getString("hasAskedForNotificationPermission");
|
||||||
"hasAskedForNotificationPermission"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasAskedBefore !== "true") {
|
if (hasAskedBefore !== "true") {
|
||||||
const { status } = await Notifications.requestPermissionsAsync();
|
const { status } = await Notifications.requestPermissionsAsync();
|
||||||
@@ -214,11 +222,7 @@ const checkAndRequestPermissions = async () => {
|
|||||||
console.log("Already asked for notification permissions before.");
|
console.log("Already asked for notification permissions before.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeToLog(
|
writeToLog("ERROR", "Error checking/requesting notification permissions:", error);
|
||||||
"ERROR",
|
|
||||||
"Error checking/requesting notification permissions:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
console.error("Error checking/requesting notification permissions:", error);
|
console.error("Error checking/requesting notification permissions:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -253,12 +257,11 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
function Layout() {
|
function Layout() {
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
|
const [user] = useAtom(userAtom);
|
||||||
const appState = useRef(AppState.currentState);
|
const appState = useRef(AppState.currentState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
i18n.changeLanguage(
|
i18n.changeLanguage(settings?.preferedLanguage ?? getLocales()[0].languageCode ?? "en");
|
||||||
settings?.preferedLanguage ?? getLocales()[0].languageCode ?? "en"
|
|
||||||
);
|
|
||||||
}, [settings?.preferedLanguage, i18n]);
|
}, [settings?.preferedLanguage, i18n]);
|
||||||
|
|
||||||
if (!Platform.isTV) {
|
if (!Platform.isTV) {
|
||||||
@@ -266,6 +269,11 @@ function Layout() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkAndRequestPermissions();
|
checkAndRequestPermissions();
|
||||||
|
(async () => {
|
||||||
|
if (!Platform.isTV && user && user.Policy?.IsAdministrator) {
|
||||||
|
registerBackgroundFetchAsyncSessions();
|
||||||
|
}
|
||||||
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -275,24 +283,16 @@ function Layout() {
|
|||||||
ScreenOrientation.unlockAsync();
|
ScreenOrientation.unlockAsync();
|
||||||
} else {
|
} else {
|
||||||
// If the user has auto rotate disabled, lock the orientation to portrait
|
// If the user has auto rotate disabled, lock the orientation to portrait
|
||||||
ScreenOrientation.lockAsync(
|
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
|
||||||
ScreenOrientation.OrientationLock.PORTRAIT_UP
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = AppState.addEventListener(
|
const subscription = AppState.addEventListener("change", (nextAppState) => {
|
||||||
"change",
|
if (appState.current.match(/inactive|background/) && nextAppState === "active") {
|
||||||
(nextAppState) => {
|
BackGroundDownloader.checkForExistingDownloads();
|
||||||
if (
|
|
||||||
appState.current.match(/inactive|background/) &&
|
|
||||||
nextAppState === "active"
|
|
||||||
) {
|
|
||||||
BackGroundDownloader.checkForExistingDownloads();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
BackGroundDownloader.checkForExistingDownloads();
|
BackGroundDownloader.checkForExistingDownloads();
|
||||||
|
|
||||||
@@ -369,9 +369,7 @@ function Layout() {
|
|||||||
function saveDownloadedItemInfo(item: BaseItemDto) {
|
function saveDownloadedItemInfo(item: BaseItemDto) {
|
||||||
try {
|
try {
|
||||||
const downloadedItems = storage.getString("downloadedItems");
|
const downloadedItems = storage.getString("downloadedItems");
|
||||||
let items: BaseItemDto[] = downloadedItems
|
let items: BaseItemDto[] = downloadedItems ? JSON.parse(downloadedItems) : [];
|
||||||
? JSON.parse(downloadedItems)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const existingItemIndex = items.findIndex((i) => i.Id === item.Id);
|
const existingItemIndex = items.findIndex((i) => i.Id === item.Id);
|
||||||
if (existingItemIndex !== -1) {
|
if (existingItemIndex !== -1) {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { apiAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
||||||
import { userAtom } from "@/providers/JellyfinProvider";
|
import { userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
|
||||||
|
|
||||||
export interface useSessionsProps {
|
export interface useSessionsProps {
|
||||||
refetchInterval: number;
|
refetchInterval: number;
|
||||||
@@ -22,9 +24,13 @@ export const useSessions = ({ refetchInterval = 5 * 1000, activeWithinSeconds =
|
|||||||
const response = await getSessionApi(api).getSessions({
|
const response = await getSessionApi(api).getSessions({
|
||||||
activeWithinSeconds: activeWithinSeconds,
|
activeWithinSeconds: activeWithinSeconds,
|
||||||
});
|
});
|
||||||
return response.data
|
|
||||||
|
const result = response.data
|
||||||
.filter((s) => s.NowPlayingItem)
|
.filter((s) => s.NowPlayingItem)
|
||||||
.sort((a, b) => (b.NowPlayingItem?.Name ?? "").localeCompare(a.NowPlayingItem?.Name ?? ""));
|
.sort((a, b) => (b.NowPlayingItem?.Name ?? "").localeCompare(a.NowPlayingItem?.Name ?? ""));
|
||||||
|
|
||||||
|
Notifications.setBadgeCountAsync(result.length);
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
refetchInterval: refetchInterval,
|
refetchInterval: refetchInterval,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useInterval } from "@/hooks/useInterval";
|
|||||||
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
|
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
|
import { store } from "@/utils/store";
|
||||||
import { Api, Jellyfin } from "@jellyfin/sdk";
|
import { Api, Jellyfin } from "@jellyfin/sdk";
|
||||||
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
@@ -165,6 +166,10 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
await refreshStreamyfinPluginSettings();
|
await refreshStreamyfinPluginSettings();
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
store.set(apiAtom, api);
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
useInterval(pollQuickConnect, isPolling ? 1000 : null);
|
useInterval(pollQuickConnect, isPolling ? 1000 : null);
|
||||||
useInterval(refreshStreamyfinPluginSettings, 60 * 5 * 1000); // 5 min
|
useInterval(refreshStreamyfinPluginSettings, 60 * 5 * 1000); // 5 min
|
||||||
|
|||||||
@@ -24,3 +24,26 @@ export async function unregisterBackgroundFetchAsync() {
|
|||||||
console.log("Error unregistering background fetch task", error);
|
console.log("Error unregistering background fetch task", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BACKGROUND_FETCH_TASK_SESSIONS =
|
||||||
|
"background-fetch-sessions";
|
||||||
|
|
||||||
|
export async function registerBackgroundFetchAsyncSessions() {
|
||||||
|
try {
|
||||||
|
BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK_SESSIONS, {
|
||||||
|
minimumInterval: 1 * 60, // 1 minutes
|
||||||
|
stopOnTerminate: false, // android only,
|
||||||
|
startOnBoot: true, // android only
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error registering background fetch task", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unregisterBackgroundFetchAsyncSessions() {
|
||||||
|
try {
|
||||||
|
BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK_SESSIONS);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error unregistering background fetch task", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
utils/store.ts
Normal file
3
utils/store.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createStore } from 'jotai';
|
||||||
|
|
||||||
|
export const store = createStore();
|
||||||
Reference in New Issue
Block a user