diff --git a/front/src/models/video.ts b/front/src/models/video.ts index 06004a15..50b81263 100644 --- a/front/src/models/video.ts +++ b/front/src/models/video.ts @@ -27,9 +27,10 @@ export const Video = z.object({ // Name of the tool that made the guess from: z.string(), - get history() { - return z.array(Video.shape.guess.omit({ history: true })).default([]); - }, + // Adding that results in an infinite recursion + // get history() { + // return z.array(Video.shape.guess.omit({ history: true })).default([]); + // }, }), createdAt: zdate(), updatedAt: zdate(), diff --git a/front/src/primitives/image-background.tsx b/front/src/primitives/image-background.tsx index bc1d4ce0..5eaaf0ac 100644 --- a/front/src/primitives/image-background.tsx +++ b/front/src/primitives/image-background.tsx @@ -25,7 +25,7 @@ export const ImageBackground = ({ layout: ImageLayout; children: ReactNode; }) => { - const { css } = useYoshiki(); + const { css, theme } = useYoshiki(); const { apiUrl, authToken } = useToken(); return ( @@ -42,7 +42,10 @@ export const ImageBackground = ({ }} placeholder={{ blurhash: src?.blurhash }} accessibilityLabel={alt} - {...(css([layout, { overflow: "hidden" }], props) as any)} + {...(css( + [layout, { overflow: "hidden", backgroundColor: theme.overlay0 }], + props, + ) as any)} /> ); }; diff --git a/front/src/primitives/image.tsx b/front/src/primitives/image.tsx index 09954bd9..6476d9d5 100644 --- a/front/src/primitives/image.tsx +++ b/front/src/primitives/image.tsx @@ -34,7 +34,7 @@ export const Image = ({ style?: ImageStyle; layout: ImageLayout; }) => { - const { css } = useYoshiki(); + const { css, theme } = useYoshiki(); const { apiUrl, authToken } = useToken(); return ( @@ -51,7 +51,10 @@ export const Image = ({ }} placeholder={{ blurhash: src?.blurhash }} accessibilityLabel={alt} - {...(css([layout, { borderRadius: 6 }], props) as any)} + {...(css( + [layout, { borderRadius: 6, backgroundColor: theme.overlay0 }], + props, + ) as any)} /> ); }; diff --git a/front/src/primitives/skeleton.tsx b/front/src/primitives/skeleton.tsx index fd0c8129..4d798467 100644 --- a/front/src/primitives/skeleton.tsx +++ b/front/src/primitives/skeleton.tsx @@ -108,7 +108,6 @@ export const Skeleton = ({ colors={["transparent", theme.overlay1, "transparent"]} style={[ StyleSheet.absoluteFillObject, - { transform: [{ translateX: -width.value }] }, animated, ]} /> diff --git a/front/src/ui/player/controls/back.tsx b/front/src/ui/player/controls/back.tsx index 43ab4cea..7cef2f03 100644 --- a/front/src/ui/player/controls/back.tsx +++ b/front/src/ui/player/controls/back.tsx @@ -11,7 +11,7 @@ import { tooltip, } from "~/primitives"; -export const Back = ({ name, ...props }: { name: string } & ViewProps) => { +export const Back = ({ name, ...props }: { name?: string } & ViewProps) => { const { css } = useYoshiki(); const { t } = useTranslation(); const router = useRouter(); @@ -35,49 +35,19 @@ export const Back = ({ name, ...props }: { name: string } & ViewProps) => { onPress={router.back} {...tooltip(t("player.back"))} /> -

- {name} -

- - ); -}; - -Back.Loader = (props: ViewProps) => { - const { css } = useYoshiki(); - const { t } = useTranslation(); - const router = useRouter(); - - return ( - theme.darkOverlay, - display: "flex", - flexDirection: "row", - alignItems: "center", - padding: percent(0.33), - color: "white", - }, - props, + {name ? ( +

+ {name} +

+ ) : ( + )} - > - -
); }; diff --git a/front/src/ui/player/controls/bottom-controls.tsx b/front/src/ui/player/controls/bottom-controls.tsx index 9fe79fb3..25d78505 100644 --- a/front/src/ui/player/controls/bottom-controls.tsx +++ b/front/src/ui/player/controls/bottom-controls.tsx @@ -4,7 +4,7 @@ import type { ComponentProps } from "react"; import { useTranslation } from "react-i18next"; import { Platform, View, type ViewProps } from "react-native"; import type { VideoPlayer } from "react-native-video"; -import { percent, useYoshiki } from "yoshiki/native"; +import { percent, rem, useYoshiki } from "yoshiki/native"; import type { Chapter, KImage } from "~/models"; import { H2, @@ -12,6 +12,7 @@ import { Link, type Menu, Poster, + Skeleton, tooltip, ts, useIsTouch, @@ -31,11 +32,11 @@ export const BottomControls = ({ ...props }: { player: VideoPlayer; - poster: KImage; - name: string; + poster?: KImage | null; + name?: string; chapters: Chapter[]; - previous: string | null; - next: string | null; + previous?: string | null; + next?: string | null; setMenu: (isOpen: boolean) => void; } & ViewProps) => { const { css } = useYoshiki(); @@ -57,25 +58,34 @@ export const BottomControls = ({ position: "relative", })} > - + {poster !== undefined ? ( + + ) : ( + + )} -

- name -

+ {name ? ( +

+ {name} +

+ ) : ( + + )} void; }) => { const { css } = useYoshiki(); @@ -116,7 +126,7 @@ const ControlButtons = ({ {...css( { flexDirection: "row", - flexGrow: 1, + flex: 1, justifyContent: "space-between", flexWrap: "wrap", }, @@ -124,7 +134,7 @@ const ControlButtons = ({ )} > - {isTouch && ( + {!isTouch && ( {previous && ( { const { css } = useYoshiki(); + const insets = useSafeAreaInsets(); const isTouch = useIsTouch(); const [hover, setHover] = useState(false); @@ -44,9 +47,13 @@ export const Controls = ({ } satisfies ViewProps; return ( - + theme.darkOverlay, + paddingTop: insets.top, + paddingLeft: insets.left, + paddingRight: insets.right, }, hoverControls, )} @@ -64,7 +74,7 @@ export const Controls = ({ )} theme.darkOverlay, + paddingLeft: insets.left, + paddingRight: insets.right, + paddingBottom: insets.bottom, }, hoverControls, )} diff --git a/front/src/ui/player/controls/progress.tsx b/front/src/ui/player/controls/progress.tsx index 24a5030d..08f744e1 100644 --- a/front/src/ui/player/controls/progress.tsx +++ b/front/src/ui/player/controls/progress.tsx @@ -39,9 +39,9 @@ export const ProgressBar = ({ }} setProgress={setSeek} endSeek={() => { - setProgress(seek!); - setSeek(null); + player.seekTo(seek!); setTimeout(player.play, 10); + setSeek(null); }} // onHover={(progress, layout) => { // setHoverProgress(progress); diff --git a/front/src/ui/player/controls/touch.tsx b/front/src/ui/player/controls/touch.tsx index a330c2a6..9880e4e5 100644 --- a/front/src/ui/player/controls/touch.tsx +++ b/front/src/ui/player/controls/touch.tsx @@ -5,7 +5,7 @@ import { Pressable, type PressableProps, } from "react-native"; -import type { VideoPlayer } from "react-native-video"; +import { useEvent, type VideoPlayer } from "react-native-video"; import { useYoshiki } from "yoshiki/native"; import { useIsTouch } from "~/primitives"; @@ -18,9 +18,14 @@ export const TouchControls = ({ const { css } = useYoshiki(); const isTouch = useIsTouch(); - const [_show, setShow] = useState(true); + const [playing, setPlay] = useState(player.isPlaying); + useEvent(player, "onPlaybackStateChange", (status) => { + setPlay(status.isPlaying); + }); + + const [_show, setShow] = useState(false); const hideTimeout = useRef(null); - const shouldShow = forceShow || _show; + const shouldShow = forceShow || _show || !playing; const show = useCallback((val: boolean = true) => { setShow(val); if (hideTimeout.current) clearTimeout(hideTimeout.current); diff --git a/front/src/ui/player/index.tsx b/front/src/ui/player/index.tsx index c9c9749b..00429b1d 100644 --- a/front/src/ui/player/index.tsx +++ b/front/src/ui/player/index.tsx @@ -10,31 +10,15 @@ import { type QueryIdentifier, useFetch } from "~/query"; import { useQueryState } from "~/utils"; import { Controls, LoadingIndicator } from "./controls"; -const mapMetadata = (item: FullVideo | undefined) => { - if (!item) return null; - - // TODO: map current entry using entries' duration & the current playtime - const currentEntry = 0; - const entry = item.entries[currentEntry] ?? item.entries[0]; - if (!entry) return null; - - return { - currentEntry, - title: `${entry.name} (${entryDisplayNumber(entry)})`, - description: entry.description, - subtitle: item.show!.kind !== "movie" ? item.show!.name : null, - poster: item.show!.poster, - thumbnail: item.show!.thumbnail, - }; -}; - export const Player = () => { const [slug, setSlug] = useQueryState("slug", undefined!); const [start, setStart] = useQueryState("t", undefined); const { data, error } = useFetch(Player.query(slug)); const { data: info, error: infoError } = useFetch(Player.infoQuery(slug)); - const metadata = mapMetadata(data); + // TODO: map current entry using entries' duration & the current playtime + const currentEntry = 0; + const entry = data?.entries[currentEntry] ?? data?.entries[0]; const { apiUrl, authToken } = useToken(); const [playMode] = useLocalSetting<"direct" | "hls">("playMode", "direct"); @@ -44,15 +28,15 @@ export const Player = () => { headers: { Authorization: `Bearer ${authToken}`, }, - externalSubtitles: info?.subtitles - .filter((x) => x.link) - .map((x) => ({ - uri: x.link!, - // TODO: translate this `Unknown` - label: x.title ?? "Unknown", - language: x.language ?? "und", - type: x.codec, - })), + // externalSubtitles: info?.subtitles + // .filter((x) => x.link) + // .map((x) => ({ + // uri: x.link!, + // // TODO: translate this `Unknown` + // label: x.title ?? "Unknown", + // language: x.language ?? "und", + // type: x.codec, + // })), }, (p) => { p.playWhenInactive = true; @@ -111,9 +95,9 @@ export const Player = () => { }} > { pictureInPicture autoEnterPictureInPicture resizeMode={"contain"} - controls style={StyleSheet.absoluteFillObject} /> x) + .join(" - ") + : undefined + } + chapters={info?.chapters ?? []} + previous={data?.previous?.video} + next={data?.next?.video} /> @@ -147,7 +140,7 @@ export const Player = () => { Player.query = (slug: string): QueryIdentifier => ({ path: ["api", "videos", slug], params: { - fields: ["next", "previous", "show"], + with: ["next", "previous", "show"], }, parser: FullVideo, });