mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-07 09:31:53 +01:00
chore: update dependencies and refactor config plugin imports (#993)
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { DownloadedItem } from "@/providers/Downloads/types";
|
||||
import { useSegments } from "@/utils/segments";
|
||||
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||
import { useHaptic } from "./useHaptic";
|
||||
@@ -10,6 +12,8 @@ export const useCreditSkipper = (
|
||||
play: () => void,
|
||||
isVlc = false,
|
||||
isOffline = false,
|
||||
api: Api | null = null,
|
||||
downloadedFiles: DownloadedItem[] | undefined = undefined,
|
||||
) => {
|
||||
const [showSkipCreditButton, setShowSkipCreditButton] = useState(false);
|
||||
const lightHapticFeedback = useHaptic("light");
|
||||
@@ -26,7 +30,12 @@ export const useCreditSkipper = (
|
||||
seek(seconds);
|
||||
};
|
||||
|
||||
const { data: segments } = useSegments(itemId, isOffline);
|
||||
const { data: segments } = useSegments(
|
||||
itemId,
|
||||
isOffline,
|
||||
downloadedFiles,
|
||||
api,
|
||||
);
|
||||
const creditTimestamps = segments?.creditSegments?.[0];
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { DownloadedItem } from "@/providers/Downloads/types";
|
||||
import { useSegments } from "@/utils/segments";
|
||||
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||
import { useHaptic } from "./useHaptic";
|
||||
@@ -15,6 +17,8 @@ export const useIntroSkipper = (
|
||||
play: () => void,
|
||||
isVlc = false,
|
||||
isOffline = false,
|
||||
api: Api | null = null,
|
||||
downloadedFiles: DownloadedItem[] | undefined = undefined,
|
||||
) => {
|
||||
const [showSkipButton, setShowSkipButton] = useState(false);
|
||||
if (isVlc) {
|
||||
@@ -30,7 +34,12 @@ export const useIntroSkipper = (
|
||||
seek(seconds);
|
||||
};
|
||||
|
||||
const { data: segments } = useSegments(itemId, isOffline);
|
||||
const { data: segments } = useSegments(
|
||||
itemId,
|
||||
isOffline,
|
||||
downloadedFiles,
|
||||
api,
|
||||
);
|
||||
const introTimestamps = segments?.introSegments?.[0];
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { toast } from "sonner-native";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import type { Settings } from "@/utils/atoms/settings";
|
||||
import type { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes";
|
||||
import {
|
||||
IssueStatus,
|
||||
@@ -416,9 +416,11 @@ export class JellyseerrApi {
|
||||
|
||||
const jellyseerrUserAtom = atom(storage.get<JellyseerrUser>(JELLYSEERR_USER));
|
||||
|
||||
export const useJellyseerr = () => {
|
||||
export const useJellyseerr = (
|
||||
settings: Settings,
|
||||
updateSettings: (update: Partial<Settings>) => void,
|
||||
) => {
|
||||
const [jellyseerrUser, setJellyseerrUser] = useAtom(jellyseerrUserAtom);
|
||||
const [settings, updateSettings] = useSettings();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const jellyseerrApi = useMemo(() => {
|
||||
@@ -472,7 +474,7 @@ export const useJellyseerr = () => {
|
||||
return (
|
||||
items &&
|
||||
Object.hasOwn(items, "mediaType") &&
|
||||
Object.values(MediaType).includes(items.mediaType)
|
||||
Object.values(MediaType).includes(items.mediaType as MediaType)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ import { Image } from "expo-image";
|
||||
import { useGlobalSearchParams } from "expo-router";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { store } from "@/utils/store";
|
||||
import { ticksToMs } from "@/utils/time";
|
||||
import {
|
||||
generateTrickplayUrl,
|
||||
getTrickplayInfo,
|
||||
type TrickplayInfo,
|
||||
} from "@/utils/trickplay";
|
||||
|
||||
interface TrickplayUrl {
|
||||
x: number;
|
||||
@@ -15,8 +18,8 @@ interface TrickplayUrl {
|
||||
|
||||
/** Hook to handle trickplay logic for a given item. */
|
||||
export const useTrickplay = (item: BaseItemDto) => {
|
||||
const [trickPlayUrl, setTrickPlayUrl] = useState<TrickplayUrl | null>(null);
|
||||
const { getDownloadedItemById } = useDownload();
|
||||
const [trickPlayUrl, setTrickPlayUrl] = useState<TrickplayUrl | null>(null);
|
||||
const lastCalculationTime = useRef(0);
|
||||
const throttleDelay = 200;
|
||||
const isOffline = useGlobalSearchParams().offline === "true";
|
||||
@@ -33,7 +36,7 @@ export const useTrickplay = (item: BaseItemDto) => {
|
||||
}
|
||||
return generateTrickplayUrl(item, sheetIndex);
|
||||
},
|
||||
[trickplayInfo],
|
||||
[trickplayInfo, isOffline, getDownloadedItemById],
|
||||
);
|
||||
|
||||
/** Calculates the trickplay URL for the current progress. */
|
||||
@@ -57,12 +60,25 @@ export const useTrickplay = (item: BaseItemDto) => {
|
||||
[trickplayInfo, item, throttleDelay, getTrickplayUrl],
|
||||
);
|
||||
|
||||
/** Prefetches all the trickplay images for the item. */
|
||||
const prefetchAllTrickplayImages = useCallback(() => {
|
||||
/** Prefetches all the trickplay images for the item, limiting concurrency to avoid I/O spikes. */
|
||||
const prefetchAllTrickplayImages = useCallback(async () => {
|
||||
if (!trickplayInfo || !item.Id) return;
|
||||
for (let index = 0; index < trickplayInfo.totalImageSheets; index++) {
|
||||
const maxConcurrent = 4;
|
||||
const total = trickplayInfo.totalImageSheets;
|
||||
const urls: string[] = [];
|
||||
for (let index = 0; index < total; index++) {
|
||||
const url = getTrickplayUrl(item, index);
|
||||
if (url) Image.prefetch(url);
|
||||
if (url) urls.push(url);
|
||||
}
|
||||
for (let i = 0; i < urls.length; i += maxConcurrent) {
|
||||
const batch = urls.slice(i, i + maxConcurrent);
|
||||
await Promise.all(
|
||||
batch.map(
|
||||
(url) => Image.prefetch(url).catch(() => {}), // Ignore errors
|
||||
),
|
||||
);
|
||||
// Yield to the event loop between batches to avoid blocking
|
||||
await Promise.resolve();
|
||||
}
|
||||
}, [trickplayInfo, item, getTrickplayUrl]);
|
||||
|
||||
@@ -83,67 +99,6 @@ export interface TrickplayData {
|
||||
ThumbnailCount?: number;
|
||||
}
|
||||
|
||||
export interface TrickplayInfo {
|
||||
resolution: string;
|
||||
aspectRatio: number;
|
||||
data: TrickplayData;
|
||||
totalImageSheets: number;
|
||||
}
|
||||
|
||||
/** Generates a trickplay URL based on the item, resolution, and sheet index. */
|
||||
export const generateTrickplayUrl = (item: BaseItemDto, sheetIndex: number) => {
|
||||
const api = store.get(apiAtom);
|
||||
const resolution = getTrickplayInfo(item)?.resolution;
|
||||
if (!resolution || !api) return null;
|
||||
return `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${sheetIndex}.jpg?api_key=${api.accessToken}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the trickplay metadata from a BaseItemDto.
|
||||
* @param item The Jellyfin media item.
|
||||
* @returns Parsed trickplay information or null if not available.
|
||||
*/
|
||||
export const getTrickplayInfo = (item: BaseItemDto): TrickplayInfo | null => {
|
||||
if (!item.Id || !item.Trickplay) return null;
|
||||
|
||||
const mediaSourceId = item.Id;
|
||||
const trickplayDataForSource = item.Trickplay[mediaSourceId];
|
||||
|
||||
if (!trickplayDataForSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstResolution = Object.keys(trickplayDataForSource)[0];
|
||||
if (!firstResolution) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = trickplayDataForSource[firstResolution];
|
||||
const { Interval, TileWidth, TileHeight, Width, Height } = data;
|
||||
|
||||
if (
|
||||
!Interval ||
|
||||
!TileWidth ||
|
||||
!TileHeight ||
|
||||
!Width ||
|
||||
!Height ||
|
||||
!item.RunTimeTicks
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tilesPerSheet = TileWidth * TileHeight;
|
||||
const totalTiles = Math.ceil(ticksToMs(item.RunTimeTicks) / Interval);
|
||||
const totalImageSheets = Math.ceil(totalTiles / tilesPerSheet);
|
||||
|
||||
return {
|
||||
resolution: firstResolution,
|
||||
aspectRatio: Width / Height,
|
||||
data,
|
||||
totalImageSheets,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the specific image sheet and tile offset for a given time.
|
||||
* @param progressTicks The current playback time in ticks.
|
||||
|
||||
Reference in New Issue
Block a user