mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-10-16 19:40:43 -04:00
Init player rework
This commit is contained in:
parent
6b82053aec
commit
c1ee6b9821
@ -21,7 +21,7 @@
|
|||||||
import { type WatchInfo, getCurrentApiUrl, queryFn, toQueryKey } from "@kyoo/models";
|
import { type WatchInfo, getCurrentApiUrl, queryFn, toQueryKey } from "@kyoo/models";
|
||||||
import { getCurrentAccount } from "@kyoo/models/src/account-internal";
|
import { getCurrentAccount } from "@kyoo/models/src/account-internal";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { Player } from "../player";
|
import { Player } from "../../../../src/ui/player../src/ui/player";
|
||||||
|
|
||||||
export const useDownloader = () => {
|
export const useDownloader = () => {
|
||||||
return async (type: "episode" | "movie", slug: string) => {
|
return async (type: "episode" | "movie", slug: string) => {
|
||||||
|
@ -41,7 +41,7 @@ import { type PrimitiveAtom, atom, useSetAtom, useStore } from "jotai";
|
|||||||
import { type ReactNode, useEffect } from "react";
|
import { type ReactNode, useEffect } from "react";
|
||||||
import { ToastAndroid } from "react-native";
|
import { ToastAndroid } from "react-native";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Player } from "../player";
|
import { Player } from "../../../../src/ui/player";
|
||||||
|
|
||||||
type Router = ReturnType<typeof useRouter>;
|
type Router = ReturnType<typeof useRouter>;
|
||||||
|
|
||||||
|
3
front/src/app/(app)/watch/[slug].tsx
Normal file
3
front/src/app/(app)/watch/[slug].tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { Player } from "~/ui/player";
|
||||||
|
|
||||||
|
export default Player;
|
@ -35,6 +35,7 @@ import {
|
|||||||
A,
|
A,
|
||||||
Chip,
|
Chip,
|
||||||
Container,
|
Container,
|
||||||
|
ContrastArea,
|
||||||
capitalize,
|
capitalize,
|
||||||
DottedSeparator,
|
DottedSeparator,
|
||||||
GradientImageBackground,
|
GradientImageBackground,
|
||||||
@ -714,30 +715,32 @@ export const Header = ({
|
|||||||
},
|
},
|
||||||
}) as any)}
|
}) as any)}
|
||||||
/>
|
/>
|
||||||
<TitleLine
|
<ContrastArea>
|
||||||
kind={kind}
|
<TitleLine
|
||||||
slug={slug}
|
kind={kind}
|
||||||
name={data.name}
|
slug={slug}
|
||||||
tagline={data.tagline}
|
name={data.name}
|
||||||
date={getDisplayDate(data)}
|
tagline={data.tagline}
|
||||||
rating={data.rating}
|
date={getDisplayDate(data)}
|
||||||
runtime={data.kind === "movie" ? data.runtime : null}
|
rating={data.rating}
|
||||||
poster={data.poster}
|
runtime={data.kind === "movie" ? data.runtime : null}
|
||||||
studios={data.kind !== "collection" ? data.studios! : null}
|
poster={data.poster}
|
||||||
playHref={data.kind !== "collection" ? data.playHref : null}
|
studios={data.kind !== "collection" ? data.studios! : null}
|
||||||
trailerUrl={data.kind !== "collection" ? data.trailerUrl : null}
|
playHref={data.kind !== "collection" ? data.playHref : null}
|
||||||
watchStatus={
|
trailerUrl={data.kind !== "collection" ? data.trailerUrl : null}
|
||||||
data.kind !== "collection" ? data.watchStatus?.status! : null
|
watchStatus={
|
||||||
}
|
data.kind !== "collection" ? data.watchStatus?.status! : null
|
||||||
{...css({
|
}
|
||||||
marginTop: {
|
{...css({
|
||||||
xs: max(vh(20), px(200)),
|
marginTop: {
|
||||||
sm: vh(45),
|
xs: max(vh(20), px(200)),
|
||||||
md: max(vh(30), px(150)),
|
sm: vh(45),
|
||||||
lg: max(vh(35), px(200)),
|
md: max(vh(30), px(150)),
|
||||||
},
|
lg: max(vh(35), px(200)),
|
||||||
})}
|
},
|
||||||
/>
|
})}
|
||||||
|
/>
|
||||||
|
</ContrastArea>
|
||||||
<Description
|
<Description
|
||||||
description={data?.description}
|
description={data?.description}
|
||||||
genres={data?.genres}
|
genres={data?.genres}
|
||||||
|
@ -29,7 +29,7 @@ import { useAtom } from "jotai";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { type Stylable, useYoshiki } from "yoshiki/native";
|
import { type Stylable, useYoshiki } from "yoshiki/native";
|
||||||
import { useSubtitleName } from "../../utils";
|
import { useSubtitleName } from "../../../../packages/ui/src/utils";
|
||||||
import { fullscreenAtom, subtitleAtom } from "../state";
|
import { fullscreenAtom, subtitleAtom } from "../state";
|
||||||
import { AudiosMenu, QualitiesMenu } from "../video";
|
import { AudiosMenu, QualitiesMenu } from "../video";
|
||||||
|
|
@ -24,7 +24,7 @@ import { useAtomValue } from "jotai";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { type Theme, percent, px, useForceRerender, useYoshiki } from "yoshiki/native";
|
import { type Theme, percent, px, useForceRerender, useYoshiki } from "yoshiki/native";
|
||||||
import { ErrorView } from "../../../../../src/ui/errors";
|
import { ErrorView } from "../../errors";
|
||||||
import { durationAtom } from "../state";
|
import { durationAtom } from "../state";
|
||||||
import { seekProgressAtom } from "./hover";
|
import { seekProgressAtom } from "./hover";
|
||||||
import { toTimerString } from "./left-buttons";
|
import { toTimerString } from "./left-buttons";
|
@ -1,45 +1,20 @@
|
|||||||
/*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
type Episode,
|
|
||||||
EpisodeP,
|
|
||||||
type Movie,
|
|
||||||
MovieP,
|
|
||||||
type QueryIdentifier,
|
|
||||||
type WatchInfo,
|
|
||||||
WatchInfoP,
|
|
||||||
useFetch,
|
|
||||||
} from "@kyoo/models";
|
|
||||||
import { Head } from "@kyoo/primitives";
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { type ComponentProps, useEffect, useState } from "react";
|
import { type ComponentProps, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, StyleSheet, View } from "react-native";
|
import { Platform, StyleSheet, View } from "react-native";
|
||||||
import { useRouter } from "solito/router";
|
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { episodeDisplayNumber } from "../../../../src/ui/details/episode";
|
import {
|
||||||
import { ErrorView } from "../../../../src/ui/errors";
|
Episode,
|
||||||
|
Movie,
|
||||||
|
type QueryIdentifier,
|
||||||
|
useFetch,
|
||||||
|
type WatchInfo,
|
||||||
|
WatchInfoP,
|
||||||
|
} from "~/models";
|
||||||
|
import { Head } from "~/primitives";
|
||||||
import { Back, Hover, LoadingIndicator } from "./components/hover";
|
import { Back, Hover, LoadingIndicator } from "./components/hover";
|
||||||
import { useVideoKeyboard } from "./keyboard";
|
import { useVideoKeyboard } from "./keyboard";
|
||||||
import { Video, durationAtom, fullscreenAtom } from "./state";
|
import { durationAtom, fullscreenAtom, Video } from "./state";
|
||||||
import { WatchStatusObserver } from "./watch-status-observer";
|
import { WatchStatusObserver } from "./watch-status-observer";
|
||||||
|
|
||||||
type Item = (Movie & { type: "movie" }) | (Episode & { type: "episode" });
|
type Item = (Movie & { type: "movie" }) | (Episode & { type: "episode" });
|
||||||
@ -53,7 +28,10 @@ const mapData = (
|
|||||||
if (!data) return { isLoading: true };
|
if (!data) return { isLoading: true };
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
name: data.type === "movie" ? data.name : `${episodeDisplayNumber(data)} ${data.name}`,
|
name:
|
||||||
|
data.type === "movie"
|
||||||
|
? data.name
|
||||||
|
: `${episodeDisplayNumber(data)} ${data.name}`,
|
||||||
showName: data.type === "movie" ? data.name! : data.show!.name,
|
showName: data.type === "movie" ? data.name! : data.show!.name,
|
||||||
poster: data.type === "movie" ? data.poster : data.show!.poster,
|
poster: data.type === "movie" ? data.poster : data.show!.poster,
|
||||||
subtitles: info?.subtitles,
|
subtitles: info?.subtitles,
|
||||||
@ -89,11 +67,17 @@ export const Player = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [playbackError, setPlaybackError] = useState<string | undefined>(undefined);
|
const [playbackError, setPlaybackError] = useState<string | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
const { data, error } = useFetch(Player.query(type, slug));
|
const { data, error } = useFetch(Player.query(type, slug));
|
||||||
const { data: info, error: infoError } = useFetch(Player.infoQuery(type, slug));
|
const { data: info, error: infoError } = useFetch(
|
||||||
|
Player.infoQuery(type, slug),
|
||||||
|
);
|
||||||
const image =
|
const image =
|
||||||
data && data.type === "episode" ? (data.show?.poster ?? data?.poster) : data?.poster;
|
data && data.type === "episode"
|
||||||
|
? (data.show?.poster ?? data?.poster)
|
||||||
|
: data?.poster;
|
||||||
const previous =
|
const previous =
|
||||||
data && data.type === "episode" && data.previousEpisode
|
data && data.type === "episode" && data.previousEpisode
|
||||||
? `/watch/${data.previousEpisode.slug}?t=0`
|
? `/watch/${data.previousEpisode.slug}?t=0`
|
||||||
@ -103,7 +87,8 @@ export const Player = ({
|
|||||||
? `/watch/${data.nextEpisode.slug}?t=0`
|
? `/watch/${data.nextEpisode.slug}?t=0`
|
||||||
: undefined;
|
: undefined;
|
||||||
const title = data && formatTitleMetadata(data);
|
const title = data && formatTitleMetadata(data);
|
||||||
const subtitle = data && data.type === "episode" ? data.show?.name : undefined;
|
const subtitle =
|
||||||
|
data && data.type === "episode" ? data.show?.name : undefined;
|
||||||
|
|
||||||
useVideoKeyboard(info?.subtitles, info?.fonts, previous, next);
|
useVideoKeyboard(info?.subtitles, info?.fonts, previous, next);
|
||||||
|
|
||||||
@ -126,7 +111,10 @@ export const Player = ({
|
|||||||
if (error || infoError || playbackError)
|
if (error || infoError || playbackError)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Back isLoading={false} {...css({ position: "relative", bg: (theme) => theme.accent })} />
|
<Back
|
||||||
|
isLoading={false}
|
||||||
|
{...css({ position: "relative", bg: (theme) => theme.accent })}
|
||||||
|
/>
|
||||||
<ErrorView error={error ?? infoError ?? { errors: [playbackError!] }} />
|
<ErrorView error={error ?? infoError ?? { errors: [playbackError!] }} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -135,7 +123,11 @@ export const Player = ({
|
|||||||
<>
|
<>
|
||||||
<Head title={title} description={data?.overview} />
|
<Head title={title} description={data?.overview} />
|
||||||
{data && info && (
|
{data && info && (
|
||||||
<WatchStatusObserver type={type} slug={data.slug} duration={info.durationSeconds} />
|
<WatchStatusObserver
|
||||||
|
type={type}
|
||||||
|
slug={data.slug}
|
||||||
|
duration={info.durationSeconds}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<View
|
<View
|
||||||
{...css({
|
{...css({
|
||||||
@ -164,23 +156,35 @@ export const Player = ({
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
if (data.type === "movie")
|
if (data.type === "movie")
|
||||||
router.replace(`/movie/${data.slug}`, undefined, {
|
router.replace(`/movie/${data.slug}`, undefined, {
|
||||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
experimental: {
|
||||||
|
nativeBehavior: "stack-replace",
|
||||||
|
isNestedNavigator: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
router.replace(next ?? `/show/${data.show!.slug}`, undefined, {
|
router.replace(next ?? `/show/${data.show!.slug}`, undefined, {
|
||||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
experimental: {
|
||||||
|
nativeBehavior: "stack-replace",
|
||||||
|
isNestedNavigator: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
{...css(StyleSheet.absoluteFillObject)}
|
{...css(StyleSheet.absoluteFillObject)}
|
||||||
/>
|
/>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
<Hover {...mapData(data, info, previous, next)} url={`${type}/${slug}`} />
|
<Hover
|
||||||
|
{...mapData(data, info, previous, next)}
|
||||||
|
url={`${type}/${slug}`}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Player.query = (type: "episode" | "movie", slug: string): QueryIdentifier<Item> =>
|
Player.query = (
|
||||||
|
type: "episode" | "movie",
|
||||||
|
slug: string,
|
||||||
|
): QueryIdentifier<Item> =>
|
||||||
type === "episode"
|
type === "episode"
|
||||||
? {
|
? {
|
||||||
path: ["episode", slug],
|
path: ["episode", slug],
|
||||||
@ -197,15 +201,10 @@ Player.query = (type: "episode" | "movie", slug: string): QueryIdentifier<Item>
|
|||||||
parser: MovieP.transform((x) => ({ ...x, type: "movie" })),
|
parser: MovieP.transform((x) => ({ ...x, type: "movie" })),
|
||||||
};
|
};
|
||||||
|
|
||||||
Player.infoQuery = (type: "episode" | "movie", slug: string): QueryIdentifier<WatchInfo> => ({
|
Player.infoQuery = (
|
||||||
|
type: "episode" | "movie",
|
||||||
|
slug: string,
|
||||||
|
): QueryIdentifier<WatchInfo> => ({
|
||||||
path: [type, slug, "info"],
|
path: [type, slug, "info"],
|
||||||
parser: WatchInfoP,
|
parser: WatchInfoP,
|
||||||
});
|
});
|
||||||
|
|
||||||
// if more queries are needed, dont forget to update download.tsx to cache those.
|
|
||||||
Player.getFetchUrls = ({ slug, type }: { slug: string; type: "episode" | "movie" }) => [
|
|
||||||
Player.query(type, slug),
|
|
||||||
Player.infoQuery(type, slug),
|
|
||||||
];
|
|
||||||
|
|
||||||
Player.requiredPermissions = ["overall.play"];
|
|
@ -50,7 +50,7 @@ import NativeVideo, {
|
|||||||
SelectedVideoTrackType,
|
SelectedVideoTrackType,
|
||||||
} from "react-native-video";
|
} from "react-native-video";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { useDisplayName } from "../utils";
|
import { useDisplayName } from "../../../packages/ui/src/utils";
|
||||||
import { PlayMode, audioAtom, playModeAtom, subtitleAtom } from "./state";
|
import { PlayMode, audioAtom, playModeAtom, subtitleAtom } from "./state";
|
||||||
|
|
||||||
const MimeTypes: Map<string, string> = new Map([
|
const MimeTypes: Map<string, string> = new Map([
|
@ -36,7 +36,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import type { VideoProps } from "react-native-video";
|
import type { VideoProps } from "react-native-video";
|
||||||
import toVttBlob from "srt-webvtt";
|
import toVttBlob from "srt-webvtt";
|
||||||
import { useForceRerender, useYoshiki } from "yoshiki";
|
import { useForceRerender, useYoshiki } from "yoshiki";
|
||||||
import { useDisplayName } from "../utils";
|
import { useDisplayName } from "../../../packages/ui/src/utils";
|
||||||
import { MediaSessionManager } from "./media-session";
|
import { MediaSessionManager } from "./media-session";
|
||||||
import { PlayMode, audioAtom, playAtom, playModeAtom, progressAtom, subtitleAtom } from "./state";
|
import { PlayMode, audioAtom, playAtom, playModeAtom, progressAtom, subtitleAtom } from "./state";
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user