mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-04 03:27:14 -05:00 
			
		
		
		
	Init player rework
This commit is contained in:
		
							parent
							
								
									da79d235be
								
							
						
					
					
						commit
						a0739e57f2
					
				@ -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,6 +715,7 @@ export const Header = ({
 | 
				
			|||||||
								},
 | 
													},
 | 
				
			||||||
							}) as any)}
 | 
												}) as any)}
 | 
				
			||||||
						/>
 | 
											/>
 | 
				
			||||||
 | 
											<ContrastArea>
 | 
				
			||||||
							<TitleLine
 | 
												<TitleLine
 | 
				
			||||||
								kind={kind}
 | 
													kind={kind}
 | 
				
			||||||
								slug={slug}
 | 
													slug={slug}
 | 
				
			||||||
@ -738,6 +740,7 @@ export const Header = ({
 | 
				
			|||||||
									},
 | 
														},
 | 
				
			||||||
								})}
 | 
													})}
 | 
				
			||||||
							/>
 | 
												/>
 | 
				
			||||||
 | 
											</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