diff --git a/front/packages/models/src/query.tsx b/front/packages/models/src/query.tsx index 24ceeab9..4eb064f3 100644 --- a/front/packages/models/src/query.tsx +++ b/front/packages/models/src/query.tsx @@ -24,7 +24,6 @@ import { QueryClient, QueryFunctionContext, useInfiniteQuery, - useMutation, useQuery, } from "@tanstack/react-query"; import { z } from "zod"; @@ -49,6 +48,7 @@ export const queryFn = async ( authenticated?: boolean; apiUrl?: string; timeout?: number; + plainText?: boolean; }, type?: z.ZodType, token?: string | null, @@ -112,12 +112,14 @@ export const queryFn = async ( // @ts-expect-error Assume Data is nullable. if (resp.status === 204) return null; + if ("plainText" in context && context.plainText) return (await resp.text()) as unknown as Data; + let data; try { data = await resp.json(); } catch (e) { - console.error("Invald json from kyoo", e); - throw { errors: ["Invalid repsonse from kyoo"] }; + console.error("Invalid json from kyoo", e); + throw { errors: ["Invalid response from kyoo"] }; } if (!type) return data; const parsed = await type.safeParseAsync(data); @@ -170,6 +172,7 @@ export type QueryIdentifier = { placeholderData?: T | (() => T); enabled?: boolean; timeout?: number; + options?: Partial[0]>; }; export type QueryPage = ComponentType< @@ -203,7 +206,7 @@ export const toQueryKey = (query: { export const useFetch = (query: QueryIdentifier) => { return useQuery({ queryKey: toQueryKey(query), - queryFn: (ctx) => queryFn({ ...ctx, timeout: query.timeout }, query.parser), + queryFn: (ctx) => queryFn({ ...ctx, timeout: query.timeout, ...query.options }, query.parser), placeholderData: query.placeholderData as any, enabled: query.enabled, }); diff --git a/front/packages/primitives/src/image/fast-image.tsx b/front/packages/primitives/src/image/fast-image.tsx new file mode 100644 index 00000000..37d532c8 --- /dev/null +++ b/front/packages/primitives/src/image/fast-image.tsx @@ -0,0 +1,49 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import FImage from "react-native-fast-image"; + +export const FastImage = ({ + src, + alt, + width, + height, + style, + ...props +}: { + src: string; + alt: string; + width: number | string; + height: number | string; + style?: object; +}) => { + return ( + + ); +}; diff --git a/front/packages/primitives/src/image/fast-image.web.tsx b/front/packages/primitives/src/image/fast-image.web.tsx new file mode 100644 index 00000000..8b2106e0 --- /dev/null +++ b/front/packages/primitives/src/image/fast-image.web.tsx @@ -0,0 +1,44 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import NextImage from "next/image"; + +export const FastImage = ({ + src, + alt, + style, + ...props +}: { + src: string; + alt: string; + style?: object; +}) => { + return ( + + ); +}; diff --git a/front/packages/primitives/src/image/index.tsx b/front/packages/primitives/src/image/index.tsx index a7dfdf48..686645b9 100644 --- a/front/packages/primitives/src/image/index.tsx +++ b/front/packages/primitives/src/image/index.tsx @@ -27,6 +27,7 @@ import { ContrastArea } from "../themes"; import { percent } from "yoshiki/native"; import { imageBorderRadius } from "../constants"; +export { FastImage } from "./fast-image"; export { BlurhashContainer } from "./blurhash"; export { type Props as ImageProps, Image }; diff --git a/front/packages/ui/src/player/components/hover.tsx b/front/packages/ui/src/player/components/hover.tsx index 4ee75262..f7f78ed9 100644 --- a/front/packages/ui/src/player/components/hover.tsx +++ b/front/packages/ui/src/player/components/hover.tsx @@ -51,6 +51,7 @@ import { } from "../state"; import { ReactNode, useCallback, useEffect, useRef } from "react"; import { atom } from "jotai"; +import { BottomScrubber } from "./scrubber"; const hoverReasonAtom = atom({ mouseMoved: false, @@ -63,6 +64,7 @@ export const hoverAtom = atom((get) => export const Hover = ({ isLoading, + url, name, showName, poster, @@ -75,6 +77,7 @@ export const Hover = ({ qualitiesAvailables = true, }: { isLoading: boolean; + url: string; name?: string | null; showName?: string; poster?: KyooImage | null; @@ -170,6 +173,7 @@ export const Hover = ({ }} /> + diff --git a/front/packages/ui/src/player/components/scrubber.tsx b/front/packages/ui/src/player/components/scrubber.tsx new file mode 100644 index 00000000..80c8f82d --- /dev/null +++ b/front/packages/ui/src/player/components/scrubber.tsx @@ -0,0 +1,98 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { useFetch, QueryIdentifier, imageFn } from "@kyoo/models"; +import { FastImage } from "@kyoo/primitives"; +import { Platform, View } from "react-native"; +import { percent, useYoshiki, vh } from "yoshiki/native"; +import { ErrorView } from "../../fetch"; +import { useMemo } from "react"; +import { CssObject } from "yoshiki/src/web/generator"; + +type Thumb = { to: number; url: string; x: number; y: number; width: number; height: number }; + +export const BottomScrubber = ({ url }: { url: string }) => { + const { css } = useYoshiki(); + const { data, error } = useFetch(BottomScrubber.query(url)); + const info = useMemo(() => { + if (!data) return []; + + const lines = data.split("\n").filter((x) => x); + lines.shift(); + /* lines now contains something like + * + * 00:00:00.000 --> 00:00:01.000 + * image1.png#xywh=0,0,190,120 + * 00:00:01.000 --> 00:00:02.000 + * image1.png#xywh=190,0,190,120 + */ + + const ret = new Array(lines.length / 2); + for (let i = 0; i < ret.length; i++) { + const times = lines[i * 2].split(" --> "); + const timesV = times[1].split(":"); + const ts = + (parseInt(timesV[0]) * 3600 + parseInt(timesV[1]) * 60 + parseFloat(timesV[2])) * 1000; + const url = lines[i * 2 + 1].split("#xywh="); + const xywh = url[1].split(",").map((x) => parseInt(x)); + ret[i] = { + to: ts, + url: imageFn("/video/" + url[0]), + x: xywh[0], + y: xywh[1], + width: xywh[2], + height: xywh[3], + }; + } + return ret; + }, [data]); + + if (error) return ; + + return ( + + {info.map((thumb) => ( + + ))} + + ); +}; + +BottomScrubber.query = (url: string): QueryIdentifier => ({ + path: ["video", url, "thumbnails.vtt"], + parser: null!, + options: { + plainText: true, + }, +}); diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index 342a5b69..96a73315 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -157,7 +157,7 @@ export const Player = ({ slug, type }: { slug: string; type: "episode" | "movie" {...css(StyleSheet.absoluteFillObject)} /> - + );