diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index 4650ae3b..6190bf6f 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -130,6 +130,16 @@ namespace Kyoo.Abstractions.Models /// public Dictionary ExternalId { get; set; } = new(); + /// + /// Links to watch this movie. + /// + public VideoLinks? Links => Kind == ItemKind.Movie ? new() + { + Direct = $"/video/movie/{Slug}/direct", + Hls = $"/video/movie/{Slug}/master.m3u8", + } + : null; + public LibraryItem() { } [JsonConstructor] diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index f9161d96..d5a49da7 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -163,7 +163,7 @@ namespace Kyoo.Abstractions.Models /// /// Links to watch this episode. /// - public object Links => new + public VideoLinks Links => new() { Direct = $"/video/episode/{Slug}/direct", Hls = $"/video/episode/{Slug}/master.m3u8", diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index de9b6243..850bc3fc 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -124,7 +124,7 @@ namespace Kyoo.Abstractions.Models /// /// Links to watch this movie. /// - public object Links => new + public VideoLinks Links => new() { Direct = $"/video/movie/{Slug}/direct", Hls = $"/video/movie/{Slug}/master.m3u8", diff --git a/back/src/Kyoo.Abstractions/Models/VideoLinks.cs b/back/src/Kyoo.Abstractions/Models/VideoLinks.cs new file mode 100644 index 00000000..b0c2cb3b --- /dev/null +++ b/back/src/Kyoo.Abstractions/Models/VideoLinks.cs @@ -0,0 +1,36 @@ +// 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 . + +namespace Kyoo.Abstractions.Models +{ + /// + /// The links to see a movie or an episode. + /// + public class VideoLinks + { + /// + /// The direct link to the unprocessed video (pristine quality). + /// + public string Direct { get; set; } + + /// + /// The link to an HLS master playlist containing all qualities available for this video. + /// + public string Hls { get; set; } + } +} diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index aa9e5ca5..46605074 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -320,6 +320,11 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); + + modelBuilder.Entity() + .Ignore(x => x.Links); + modelBuilder.Entity() + .Ignore(x => x.Links); } /// diff --git a/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs b/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs index 3945f8fd..25781efb 100644 --- a/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs +++ b/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs @@ -67,7 +67,7 @@ namespace Kyoo.Swagger Kind? kind = controller.Type == null ? controller.Kind : cur.Kind; - ICollection permissions = _GetPermissionsList(agg, group!.Value); + ICollection permissions = _GetPermissionsList(agg, group ?? Group.Overall); permissions.Add($"{type}.{kind!.Value.ToString().ToLower()}"); agg[nameof(Kyoo)] = permissions; return agg; diff --git a/front/apps/mobile/app/movie/[slug].tsx b/front/apps/mobile/app/movie/[slug]/index.tsx similarity index 100% rename from front/apps/mobile/app/movie/[slug].tsx rename to front/apps/mobile/app/movie/[slug]/index.tsx diff --git a/front/apps/mobile/app/movie/[slug]/watch.tsx b/front/apps/mobile/app/movie/[slug]/watch.tsx new file mode 100644 index 00000000..25758b2b --- /dev/null +++ b/front/apps/mobile/app/movie/[slug]/watch.tsx @@ -0,0 +1,34 @@ +/* + * 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 { Player } from "@kyoo/ui"; +import { withRoute } from "../../../utils"; + +export default withRoute( + Player, + { + options: { + headerShown: false, + }, + statusBar: { hidden: true }, + fullscreen: true, + }, + { type: "movie" }, +); diff --git a/front/apps/mobile/app/watch/[slug].tsx b/front/apps/mobile/app/watch/[slug].tsx index ca211966..ecfdbdf7 100644 --- a/front/apps/mobile/app/watch/[slug].tsx +++ b/front/apps/mobile/app/watch/[slug].tsx @@ -21,10 +21,14 @@ import { Player } from "@kyoo/ui"; import { withRoute } from "../../utils"; -export default withRoute(Player, { - options: { - headerShown: false, +export default withRoute( + Player, + { + options: { + headerShown: false, + }, + statusBar: { hidden: true }, + fullscreen: true, }, - statusBar: { hidden: true }, - fullscreen: true, -}); + { type: "episode" }, +); diff --git a/front/apps/mobile/utils.tsx b/front/apps/mobile/utils.tsx index 978891fc..fb201bac 100644 --- a/front/apps/mobile/utils.tsx +++ b/front/apps/mobile/utils.tsx @@ -42,6 +42,7 @@ export const withRoute = ( statusBar?: StatusBarProps; fullscreen?: boolean; }, + defaultProps?: Partial, ) => { const { statusBar, fullscreen, ...routeOptions } = options ?? {}; const WithUseRoute = (props: any) => { @@ -51,7 +52,7 @@ export const withRoute = ( {routeOptions && } {statusBar && } {fullscreen && } - + ); }; diff --git a/front/apps/web/src/pages/movie/[slug].tsx b/front/apps/web/src/pages/movie/[slug]/index.tsx similarity index 100% rename from front/apps/web/src/pages/movie/[slug].tsx rename to front/apps/web/src/pages/movie/[slug]/index.tsx diff --git a/front/apps/web/src/pages/movie/[slug]/watch.tsx b/front/apps/web/src/pages/movie/[slug]/watch.tsx new file mode 100644 index 00000000..17349725 --- /dev/null +++ b/front/apps/web/src/pages/movie/[slug]/watch.tsx @@ -0,0 +1,24 @@ +/* + * 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 { Player } from "@kyoo/ui"; +import { withRoute } from "~/router"; + +export default withRoute(Player, { type: "movie" }); diff --git a/front/apps/web/src/pages/watch/[slug].tsx b/front/apps/web/src/pages/watch/[slug].tsx index 006fcb49..a26ad2ca 100644 --- a/front/apps/web/src/pages/watch/[slug].tsx +++ b/front/apps/web/src/pages/watch/[slug].tsx @@ -21,4 +21,4 @@ import { Player } from "@kyoo/ui"; import { withRoute } from "~/router"; -export default withRoute(Player); +export default withRoute(Player, { type: "episode" }); diff --git a/front/apps/web/src/router.tsx b/front/apps/web/src/router.tsx index 3ee341a8..571fa0dd 100644 --- a/front/apps/web/src/router.tsx +++ b/front/apps/web/src/router.tsx @@ -21,12 +21,12 @@ import { useRouter } from "next/router"; import { ComponentType } from "react"; -export const withRoute = (Component: ComponentType) => { +export const withRoute = (Component: ComponentType, defaultProps?: Partial) => { const WithUseRoute = (props: Props) => { const router = useRouter(); // @ts-ignore - return ; + return ; }; const { ...all } = Component; diff --git a/front/packages/models/src/resources/episode.ts b/front/packages/models/src/resources/episode.ts index 10789a19..fff3be27 100644 --- a/front/packages/models/src/resources/episode.ts +++ b/front/packages/models/src/resources/episode.ts @@ -20,8 +20,9 @@ import { z } from "zod"; import { zdate } from "../utils"; -import { ImagesP } from "../traits"; +import { ImagesP, imageFn } from "../traits"; import { ResourceP } from "../traits/resource"; +import { ShowP } from "./show"; const BaseEpisodeP = ResourceP.merge(ImagesP).extend({ /** @@ -54,6 +55,23 @@ const BaseEpisodeP = ResourceP.merge(ImagesP).extend({ * The release date of this episode. It can be null if unknown. */ releaseDate: zdate().nullable(), + + /** + * The links to see a movie or an episode. + */ + links: z.object({ + /** + * The direct link to the unprocessed video (pristine quality). + */ + direct: z.string().transform(imageFn), + + /** + * The link to an HLS master playlist containing all qualities available for this video. + */ + hls: z.string().transform(imageFn), + }), + + show: ShowP.optional() }); export const EpisodeP = BaseEpisodeP.extend({ diff --git a/front/packages/models/src/resources/index.ts b/front/packages/models/src/resources/index.ts index 64aa2385..5f06357f 100644 --- a/front/packages/models/src/resources/index.ts +++ b/front/packages/models/src/resources/index.ts @@ -27,5 +27,5 @@ export * from "./person"; export * from "./studio"; export * from "./episode"; export * from "./season"; -export * from "./watch-item"; +export * from "./watch-info"; export * from "./user"; diff --git a/front/packages/models/src/resources/movie.ts b/front/packages/models/src/resources/movie.ts index 402583c0..9571f042 100644 --- a/front/packages/models/src/resources/movie.ts +++ b/front/packages/models/src/resources/movie.ts @@ -20,7 +20,7 @@ import { z } from "zod"; import { zdate } from "../utils"; -import { ImagesP, ResourceP } from "../traits"; +import { ImagesP, ResourceP, imageFn } from "../traits"; import { Genre } from "./genre"; import { StudioP } from "./studio"; import { Status } from "./show"; @@ -67,6 +67,21 @@ export const MovieP = ResourceP.merge(ImagesP).extend({ * The studio that made this movie. */ studio: StudioP.optional().nullable(), + + /** + * The links to see a movie or an episode. + */ + links: z.object({ + /** + * The direct link to the unprocessed video (pristine quality). + */ + direct: z.string().transform(imageFn), + + /** + * The link to an HLS master playlist containing all qualities available for this video. + */ + hls: z.string().transform(imageFn), + }), }); /** diff --git a/front/packages/models/src/resources/watch-info.ts b/front/packages/models/src/resources/watch-info.ts new file mode 100644 index 00000000..4c2ed400 --- /dev/null +++ b/front/packages/models/src/resources/watch-info.ts @@ -0,0 +1,118 @@ +/* + * 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 { z } from "zod"; +import { imageFn } from "../traits"; + +/** + * A audio or subtitle track. + */ +export const TrackP = z.object({ + /** + * The index of this track on the episode. + */ + index: z.number(), + /** + * The title of the stream. + */ + title: z.string().nullable(), + /** + * The language of this stream (as a ISO-639-2 language code) + */ + language: z.string().nullable(), + /** + * The codec of this stream. + */ + codec: z.string(), + /** + * Is this stream the default one of it's type? + */ + isDefault: z.boolean(), + /** + * Is this stream tagged as forced? + */ + isForced: z.boolean(), +}); +export type Audio = z.infer; + +export const SubtitleP = TrackP.extend({ + /* + * The url of this track (only if this is a subtitle).. + */ + link: z.string().transform(imageFn).nullable(), +}); +export type Subtitle = z.infer; + +export const ChapterP = z.object({ + /** + * The start time of the chapter (in second from the start of the episode). + */ + startTime: z.number(), + /** + * The end time of the chapter (in second from the start of the episode). + */ + endTime: z.number(), + /** + * The name of this chapter. This should be a human-readable name that could be presented to the + * user. There should be well-known chapters name for commonly used chapters. For example, use + * "Opening" for the introduction-song and "Credits" for the end chapter with credits. + */ + name: z.string(), +}); +export type Chapter = z.infer; + +/** + * The transcoder's info for this item. This include subtitles, fonts, chapters... + */ +export const WatchInfoP = z.object({ + /** + * The sha1 of the video file. + */ + sha: z.string(), + /** + * The internal path of the video file. + */ + path: z.string(), + /** + * The container of the video file of this episode. Common containers are mp4, mkv, avi and so on. + */ + container: z.string(), + /** + * The list of audio tracks. + */ + audios: z.array(TrackP), + /** + * The list of subtitles tracks. + */ + subtitles: z.array(SubtitleP), + /** + * The list of fonts that can be used to display subtitles. + */ + fonts: z.array(z.string().transform(imageFn)), + /** + * The list of chapters. See Chapter for more information. + */ + chapters: z.array(ChapterP), +}); + +/** + * A watch info for a video + */ +export type WatchInfo = z.infer; diff --git a/front/packages/models/src/resources/watch-item.ts b/front/packages/models/src/resources/watch-item.ts deleted file mode 100644 index 66901505..00000000 --- a/front/packages/models/src/resources/watch-item.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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 { z } from "zod"; -import { zdate } from "../utils"; -import { ImagesP, imageFn } from "../traits"; -import { EpisodeP } from "./episode"; - -/** - * A audio or subtitle track. - */ -export const TrackP = z.object({ - /** - * The index of this track on the episode. - */ - index: z.number(), - /** - * The title of the stream. - */ - title: z.string().nullable(), - /** - * The language of this stream (as a ISO-639-2 language code) - */ - language: z.string().nullable(), - /** - * The codec of this stream. - */ - codec: z.string(), - /** - * Is this stream the default one of it's type? - */ - isDefault: z.boolean(), - /** - * Is this stream tagged as forced? - */ - isForced: z.boolean(), -}); -export type Audio = z.infer; - -export const SubtitleP = TrackP.extend({ - /* - * The url of this track (only if this is a subtitle).. - */ - link: z.string().transform(imageFn).nullable(), -}); -export type Subtitle = z.infer; - -export const ChapterP = z.object({ - /** - * The start time of the chapter (in second from the start of the episode). - */ - startTime: z.number(), - /** - * The end time of the chapter (in second from the start of the episode). - */ - endTime: z.number(), - /** - * The name of this chapter. This should be a human-readable name that could be presented to the - * user. There should be well-known chapters name for commonly used chapters. For example, use - * "Opening" for the introduction-song and "Credits" for the end chapter with credits. - */ - name: z.string(), -}); -export type Chapter = z.infer; - -const WatchMovieP = z.preprocess( - (x: any) => { - if (!x) return x; - - x.name = x.title; - return x; - }, - ImagesP.extend({ - /** - * The slug of this episode. - */ - slug: z.string(), - /** - * The title of this episode. - */ - name: z.string().nullable(), - /** - * The sumarry of this episode. - */ - overview: z.string().nullable(), - /** - * The release date of this episode. It can be null if unknown. - */ - releaseDate: zdate().nullable(), - - /** - * The transcoder's info for this item. This include subtitles, fonts, chapters... - */ - info: z.object({ - /** - * The sha1 of the video file. - */ - sha: z.string(), - /** - * The internal path of the video file. - */ - path: z.string(), - /** - * The container of the video file of this episode. Common containers are mp4, mkv, avi and so - * on. - */ - container: z.string(), - /** - * The list of audio tracks. - */ - audios: z.array(TrackP), - /** - * The list of subtitles tracks. - */ - subtitles: z.array(SubtitleP), - /** - * The list of fonts that can be used to display subtitles. - */ - fonts: z.array(z.string().transform(imageFn)), - /** - * The list of chapters. See Chapter for more information. - */ - chapters: z.array(ChapterP), - }), - /** - * The links to the videos of this watch item. - */ - link: z.object({ - direct: z.string().transform(imageFn), - hls: z.string().transform(imageFn), - }), - }), -); - -const WatchEpisodeP = WatchMovieP.and( - z.object({ - /** - * The ID of the episode associated with this item. - */ - episodeID: z.number(), - /** - * The title of the show containing this episode. - */ - showTitle: z.string(), - /** - * The slug of the show containing this episode - */ - showSlug: z.string(), - /** - * The season in witch this episode is in. - */ - seasonNumber: z.number().nullable(), - /** - * The number of this episode is it's season. - */ - episodeNumber: z.number().nullable(), - /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a - * new season. - */ - absoluteNumber: z.number().nullable(), - }), -); - -export const WatchItemP = z.union([ - WatchMovieP.and(z.object({ isMovie: z.literal(true) })), - WatchEpisodeP.and(z.object({ isMovie: z.literal(false) })), -]); - -/** - * A watch item for a movie or an episode - */ -export type WatchItem = z.infer; diff --git a/front/packages/primitives/src/icons.tsx b/front/packages/primitives/src/icons.tsx index 2698f1b9..b7cf9bd6 100644 --- a/front/packages/primitives/src/icons.tsx +++ b/front/packages/primitives/src/icons.tsx @@ -113,7 +113,7 @@ export const IconFab = ( bg: (theme) => theme.accent, fover: { self: { - transform: [{ scale: 1.3 }], + transform: "scale(1.3)" as any, bg: (theme: Theme) => theme.accent, }, }, diff --git a/front/packages/ui/src/details/header.tsx b/front/packages/ui/src/details/header.tsx index 92c8bbe7..64af0f61 100644 --- a/front/packages/ui/src/details/header.tsx +++ b/front/packages/ui/src/details/header.tsx @@ -76,6 +76,7 @@ const TitleLine = ({ poster, studio, trailerUrl, + type, ...props }: { isLoading: boolean; @@ -86,6 +87,7 @@ const TitleLine = ({ poster?: KyooImage | null; studio?: Studio | null; trailerUrl?: string | null; + type: "movie" | "show"; } & Stylable) => { const { css, theme } = useYoshiki(); const { t } = useTranslation(); @@ -193,7 +195,7 @@ const TitleLine = ({ ; slug: string }) => { +export const Header = ({ query, type, slug }: { query: QueryIdentifier; type: "movie" | "show", slug: string }) => { const { css } = useYoshiki(); return ( @@ -365,6 +367,7 @@ export const Header = ({ query, slug }: { query: QueryIdentifier; > = ({ slug }) => { }, )} > -
+
{/* */} ); diff --git a/front/packages/ui/src/details/show.tsx b/front/packages/ui/src/details/show.tsx index 8b2f34fa..bc8508d1 100644 --- a/front/packages/ui/src/details/show.tsx +++ b/front/packages/ui/src/details/show.tsx @@ -25,8 +25,8 @@ import { DefaultLayout } from "../layout"; import { EpisodeList } from "./season"; import { Header } from "./header"; import Svg, { Path, SvgProps } from "react-native-svg"; -import { Container, SwitchVariant } from "@kyoo/primitives"; -import { forwardRef, useCallback } from "react"; +import { Container } from "@kyoo/primitives"; +import { forwardRef } from "react"; const SvgWave = (props: SvgProps) => { const { css } = useYoshiki(); @@ -42,7 +42,7 @@ const SvgWave = (props: SvgProps) => { ); }; -const ShowHeader = forwardRef(function _ShowHeader( +const ShowHeader = forwardRef(function ShowHeader( { children, slug, ...props }, ref, ) { @@ -69,7 +69,7 @@ const ShowHeader = forwardRef(function _Show )} > {/* TODO: Remove the slug quickfix for the play button */} -
+
{/* */} void; show: boolean; } & ViewProps) => { - // TODO animate show + // TODO: animate show const opacity = !show && (Platform.OS === "web" ? { opacity: 0 } : { display: "none" as const}); return ( @@ -126,7 +124,6 @@ export const Hover = ({ @@ -210,7 +207,7 @@ export const Back = ({ ); }; -const VideoPoster = ({ poster }: { poster?: string | null }) => { +const VideoPoster = ({ poster }: { poster?: KyooImage | null }) => { const { css } = useYoshiki(); return ( diff --git a/front/packages/ui/src/player/components/right-buttons.tsx b/front/packages/ui/src/player/components/right-buttons.tsx index ae53a273..84ac76b1 100644 --- a/front/packages/ui/src/player/components/right-buttons.tsx +++ b/front/packages/ui/src/player/components/right-buttons.tsx @@ -18,7 +18,7 @@ * along with Kyoo. If not, see . */ -import { Subtitle, WatchItem } from "@kyoo/models"; +import { Subtitle } from "@kyoo/models"; import { IconButton, tooltip, Menu, ts } from "@kyoo/primitives"; import { useAtom } from "jotai"; import { Platform, View } from "react-native"; @@ -46,14 +46,12 @@ export const getDisplayName = (sub: Subtitle) => { export const RightButtons = ({ subtitles, fonts, - qualities, onMenuOpen, onMenuClose, ...props }: { subtitles?: Subtitle[]; fonts?: string[]; - qualities?: WatchItem["link"]; onMenuOpen: () => void; onMenuClose: () => void; } & Stylable) => { diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index 8a96fba9..4cacbf33 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -18,10 +18,20 @@ * along with Kyoo. If not, see . */ -import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models"; +import { + Episode, + EpisodeP, + Movie, + MovieP, + QueryIdentifier, + QueryPage, + WatchInfo, + WatchInfoP, + useFetch, +} from "@kyoo/models"; import { Head } from "@kyoo/primitives"; import { useState, useEffect, ComponentProps } from "react"; -import { Platform, Pressable, PressableProps, StyleSheet, View, PointerEvent as NativePointerEvent } from "react-native"; +import { Platform, StyleSheet, View, PointerEvent as NativePointerEvent } from "react-native"; import { useTranslation } from "react-i18next"; import { useRouter } from "solito/router"; import { useAtom } from "jotai"; @@ -33,43 +43,47 @@ import { useVideoKeyboard } from "./keyboard"; import { MediaSessionManager } from "./media-session"; import { ErrorView } from "../fetch"; -const query = (slug: string): QueryIdentifier => ({ - path: ["watch", slug], - parser: WatchItemP, +type Item = (Movie & { type: "movie" }) | (Episode & { type: "episode" }); + +const query = (type: string, slug: string): QueryIdentifier => + type === "episode" + ? { + path: ["episode", slug], + params: { + fields: ["nextEpisode", "previousEpisode", "show"], + }, + parser: EpisodeP.transform((x) => ({ ...x, type: "episode" })), + } + : { + path: ["movie", slug], + parser: MovieP.transform((x) => ({ ...x, type: "movie" })), + }; +const infoQuery = (type: string, slug: string): QueryIdentifier => ({ + path: ["video", type, slug, "info"], + parser: WatchInfoP, }); const mapData = ( - data: WatchItem | undefined, + data: Item | undefined, + info: WatchInfo | undefined, previousSlug?: string, nextSlug?: string, ): Partial> & { isLoading: boolean } => { - if (!data) return { isLoading: true }; + if (!data || !info) return { isLoading: true }; return { isLoading: false, - name: data.isMovie ? data.name : `${episodeDisplayNumber(data, "")} ${data.name}`, - showName: data.isMovie ? data.name! : data.showTitle, - href: data ? (data.isMovie ? `/movie/${data.slug}` : `/show/${data.showSlug}`) : "#", + name: data.type === "movie" ? data.name : `${episodeDisplayNumber(data, "")} ${data.name}`, + showName: data.type === "movie" ? data.name! : data.show!.name, + href: data ? (data.type === "movie" ? `/movie/${data.slug}` : `/show/${data.show!.slug}`) : "#", poster: data.poster, - qualities: data.link, - subtitles: data.info.subtitles, - chapters: data.info.chapters, - fonts: data.info.fonts, + subtitles: info.subtitles, + chapters: info.chapters, + fonts: info.fonts, previousSlug, nextSlug, }; }; -const PressView = - Platform.OS === "web" - ? View - : ({ - onPointerDown, - onMobilePress, - ...props - }: PressableProps & { onMobilePress: PressableProps["onPress"] }) => ( - onMobilePress?.(e)} {...props} /> - ); - // Callback used to hide the controls when the mouse goes iddle. This is stored globally to clear the old timeout // if the mouse moves again (if this is stored as a state, the whole page is redrawn on mouse move) let mouseCallback: NodeJS.Timeout; @@ -78,21 +92,24 @@ let mouseCallback: NodeJS.Timeout; let touchCount = 0; let touchTimeout: NodeJS.Timeout; -export const Player: QueryPage<{ slug: string }> = ({ slug }) => { +export const Player: QueryPage<{ slug: string; type: "episode" | "movie" }> = ({ slug, type }) => { const { css } = useYoshiki(); const { t } = useTranslation(); const router = useRouter(); const [playbackError, setPlaybackError] = useState(undefined); - const { data, error } = useFetch(query(slug)); + const { data, error } = useFetch(query(type, slug)); + const { data: info, error: infoError } = useFetch(infoQuery(type, slug)); const previous = - data && !data.isMovie && data.previousEpisode + data && data.type === "episode" && data.previousEpisode ? `/watch/${data.previousEpisode.slug}` : undefined; const next = - data && !data.isMovie && data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : undefined; + data && data.type === "episode" && data.nextEpisode + ? `/watch/${data.nextEpisode.slug}` + : undefined; - useVideoKeyboard(data?.info.subtitles, data?.info.fonts, previous, next); + useVideoKeyboard(info?.subtitles, info?.fonts, previous, next); const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom); const [isPlaying, setPlay] = useAtom(playAtom); @@ -139,17 +156,17 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { setFullscreen(!isFullscreen); clearTimeout(touchTimeout); } else - touchTimeout = setTimeout(() => { - touchCount = 0; - }, 400); + touchTimeout = setTimeout(() => { + touchCount = 0; + }, 400); setPlay(!isPlaying); }; - if (error || playbackError) + if (error || infoError || playbackError) return ( <> theme.accent })} /> - + ); @@ -158,22 +175,22 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { {data && ( )} @@ -190,20 +207,20 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { })} >