mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Rewrite progress slider (part 1)
This commit is contained in:
		
							parent
							
								
									7b8d916685
								
							
						
					
					
						commit
						c79a991024
					
				@ -15,7 +15,7 @@
 | 
				
			|||||||
		"@tanstack/react-query": "^4.19.1",
 | 
							"@tanstack/react-query": "^4.19.1",
 | 
				
			||||||
		"babel-plugin-transform-inline-environment-variables": "^0.4.4",
 | 
							"babel-plugin-transform-inline-environment-variables": "^0.4.4",
 | 
				
			||||||
		"expo": "^47.0.0",
 | 
							"expo": "^47.0.0",
 | 
				
			||||||
		"expo-av": "~13.0.2",
 | 
							"expo-av": "file:///home/anonymus-raccoon/projects/expo/packages/expo-av/",
 | 
				
			||||||
		"expo-constants": "~14.0.2",
 | 
							"expo-constants": "~14.0.2",
 | 
				
			||||||
		"expo-linear-gradient": "~12.0.1",
 | 
							"expo-linear-gradient": "~12.0.1",
 | 
				
			||||||
		"expo-linking": "~3.2.3",
 | 
							"expo-linking": "~3.2.3",
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@
 | 
				
			|||||||
		"@tanstack/react-query": "^4.19.1",
 | 
							"@tanstack/react-query": "^4.19.1",
 | 
				
			||||||
		"clsx": "^1.2.1",
 | 
							"clsx": "^1.2.1",
 | 
				
			||||||
		"csstype": "^3.1.1",
 | 
							"csstype": "^3.1.1",
 | 
				
			||||||
		"expo-av": "^13.0.2",
 | 
							"expo-av": "file:///home/anonymus-raccoon/projects/expo/packages/expo-av/",
 | 
				
			||||||
		"expo-linear-gradient": "^12.0.1",
 | 
							"expo-linear-gradient": "^12.0.1",
 | 
				
			||||||
		"hls.js": "^1.2.8",
 | 
							"hls.js": "^1.2.8",
 | 
				
			||||||
		"i18next": "^22.0.6",
 | 
							"i18next": "^22.0.6",
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,7 @@ export * from "./tooltip";
 | 
				
			|||||||
export * from "./container";
 | 
					export * from "./container";
 | 
				
			||||||
export * from "./divider";
 | 
					export * from "./divider";
 | 
				
			||||||
export * from "./progress";
 | 
					export * from "./progress";
 | 
				
			||||||
 | 
					export * from "./slider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from "./animated";
 | 
					export * from "./animated";
 | 
				
			||||||
export * from "./utils";
 | 
					export * from "./utils";
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										127
									
								
								front/packages/primitives/src/slider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								front/packages/primitives/src/slider.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 { useState } from "react";
 | 
				
			||||||
 | 
					import { Platform, Pressable, View } from "react-native";
 | 
				
			||||||
 | 
					import { percent, Stylable, useYoshiki } from "yoshiki/native";
 | 
				
			||||||
 | 
					import { ts } from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const calc =
 | 
				
			||||||
 | 
						Platform.OS === "web"
 | 
				
			||||||
 | 
							? (first: number, operator: "+" | "-" | "*" | "/", second: number): number =>
 | 
				
			||||||
 | 
									`calc(${first} ${operator} ${second})` as unknown as number
 | 
				
			||||||
 | 
							: (first: number, operator: "+" | "-" | "*" | "/", second: number): number => {
 | 
				
			||||||
 | 
									switch (operator) {
 | 
				
			||||||
 | 
										case "+":
 | 
				
			||||||
 | 
											return first + second;
 | 
				
			||||||
 | 
										case "-":
 | 
				
			||||||
 | 
											return first - second;
 | 
				
			||||||
 | 
										case "*":
 | 
				
			||||||
 | 
											return first * second;
 | 
				
			||||||
 | 
										case "/":
 | 
				
			||||||
 | 
											return first / second;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
							  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Slider = ({
 | 
				
			||||||
 | 
						progress,
 | 
				
			||||||
 | 
						subtleProgress,
 | 
				
			||||||
 | 
						max = 100,
 | 
				
			||||||
 | 
						markers,
 | 
				
			||||||
 | 
						...props
 | 
				
			||||||
 | 
					}: { progress: number; max?: number; subtleProgress?: number; markers?: number[] } & Stylable) => {
 | 
				
			||||||
 | 
						const { css } = useYoshiki();
 | 
				
			||||||
 | 
						const [isSeeking, setSeek] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return (
 | 
				
			||||||
 | 
							<Pressable
 | 
				
			||||||
 | 
								onTouchStart={(event) => {
 | 
				
			||||||
 | 
									// // prevent drag and drop of the UI.
 | 
				
			||||||
 | 
									// event.preventDefault();
 | 
				
			||||||
 | 
									setSeek(true);
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
								{...css(
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										paddingVertical: ts(1),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									props,
 | 
				
			||||||
 | 
								)}
 | 
				
			||||||
 | 
							>
 | 
				
			||||||
 | 
								<View
 | 
				
			||||||
 | 
									{...css({
 | 
				
			||||||
 | 
										width: percent(100),
 | 
				
			||||||
 | 
										height: ts(1),
 | 
				
			||||||
 | 
										bg: (theme) => theme.overlay0,
 | 
				
			||||||
 | 
									})}
 | 
				
			||||||
 | 
								>
 | 
				
			||||||
 | 
									{subtleProgress && (
 | 
				
			||||||
 | 
										<View
 | 
				
			||||||
 | 
											{...css({
 | 
				
			||||||
 | 
												bg: (theme) => theme.overlay1,
 | 
				
			||||||
 | 
												position: "absolute",
 | 
				
			||||||
 | 
												top: 0,
 | 
				
			||||||
 | 
												bottom: 0,
 | 
				
			||||||
 | 
												left: 0,
 | 
				
			||||||
 | 
												width: percent((subtleProgress / max) * 100),
 | 
				
			||||||
 | 
											})}
 | 
				
			||||||
 | 
										/>
 | 
				
			||||||
 | 
									)}
 | 
				
			||||||
 | 
									<View
 | 
				
			||||||
 | 
										{...css({
 | 
				
			||||||
 | 
											bg: (theme) => theme.accent,
 | 
				
			||||||
 | 
											position: "absolute",
 | 
				
			||||||
 | 
											top: 0,
 | 
				
			||||||
 | 
											bottom: 0,
 | 
				
			||||||
 | 
											left: 0,
 | 
				
			||||||
 | 
											width: percent((progress / max) * 100),
 | 
				
			||||||
 | 
										})}
 | 
				
			||||||
 | 
									/>
 | 
				
			||||||
 | 
									{markers?.map((x) => (
 | 
				
			||||||
 | 
										<View
 | 
				
			||||||
 | 
											key={x}
 | 
				
			||||||
 | 
											{...css({
 | 
				
			||||||
 | 
												position: "absolute",
 | 
				
			||||||
 | 
												top: 0,
 | 
				
			||||||
 | 
												bottom: 0,
 | 
				
			||||||
 | 
												left: percent(Math.min(100, (x / max) * 100)),
 | 
				
			||||||
 | 
												bg: (theme) => theme.accent,
 | 
				
			||||||
 | 
												width: ts(1),
 | 
				
			||||||
 | 
												height: ts(1),
 | 
				
			||||||
 | 
												borderRadius: ts(0.5),
 | 
				
			||||||
 | 
											})}
 | 
				
			||||||
 | 
										/>
 | 
				
			||||||
 | 
									))}
 | 
				
			||||||
 | 
								</View>
 | 
				
			||||||
 | 
								<View
 | 
				
			||||||
 | 
									{...css({
 | 
				
			||||||
 | 
										position: "absolute",
 | 
				
			||||||
 | 
										top: 0,
 | 
				
			||||||
 | 
										bottom: 0,
 | 
				
			||||||
 | 
										margin: "auto",
 | 
				
			||||||
 | 
										left: calc(percent((progress / max) * 100), "-", ts(1)),
 | 
				
			||||||
 | 
										bg: (theme) => theme.accent,
 | 
				
			||||||
 | 
										width: ts(2),
 | 
				
			||||||
 | 
										height: ts(2),
 | 
				
			||||||
 | 
										borderRadius: ts(1),
 | 
				
			||||||
 | 
									})}
 | 
				
			||||||
 | 
								/>
 | 
				
			||||||
 | 
							</Pressable>
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -59,7 +59,7 @@ export const LeftButtons = ({
 | 
				
			|||||||
			)}
 | 
								)}
 | 
				
			||||||
			<IconButton
 | 
								<IconButton
 | 
				
			||||||
				icon={isPlaying ? Pause : PlayArrow}
 | 
									icon={isPlaying ? Pause : PlayArrow}
 | 
				
			||||||
				onClick={() => setPlay(!isPlaying)}
 | 
									onPress={() => setPlay(!isPlaying)}
 | 
				
			||||||
				{...tooltip(isPlaying ? t("player.pause") : t("player.play"))}
 | 
									{...tooltip(isPlaying ? t("player.pause") : t("player.play"))}
 | 
				
			||||||
				{...spacing}
 | 
									{...spacing}
 | 
				
			||||||
			/>
 | 
								/>
 | 
				
			||||||
@ -139,8 +139,9 @@ const ProgressText = () => {
 | 
				
			|||||||
	);
 | 
						);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toTimerString = (timer: number, duration?: number) => {
 | 
					const toTimerString = (timer?: number, duration?: number) => {
 | 
				
			||||||
 | 
						if (timer === undefined) return "??:??";
 | 
				
			||||||
	if (!duration) duration = timer;
 | 
						if (!duration) duration = timer;
 | 
				
			||||||
	if (duration >= 3600) return new Date(timer * 1000).toISOString().substring(11, 19);
 | 
						if (duration >= 3600) return new Date(timer).toISOString().substring(11, 19);
 | 
				
			||||||
	return new Date(timer * 1000).toISOString().substring(14, 19);
 | 
						return new Date(timer).toISOString().substring(14, 19);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Chapter } from "@kyoo/models";
 | 
					import { Chapter } from "@kyoo/models";
 | 
				
			||||||
import { ts } from "@kyoo/primitives";
 | 
					import { ts, Slider } from "@kyoo/primitives";
 | 
				
			||||||
import { useAtom, useAtomValue } from "jotai";
 | 
					import { useAtom, useAtomValue } from "jotai";
 | 
				
			||||||
import { useEffect, useRef, useState } from "react";
 | 
					import { useEffect, useRef, useState } from "react";
 | 
				
			||||||
import { NativeTouchEvent, Pressable, Touchable, View } from "react-native";
 | 
					import { NativeTouchEvent, Pressable, Touchable, View } from "react-native";
 | 
				
			||||||
@ -27,14 +27,22 @@ import { useYoshiki, px, percent } from "yoshiki/native";
 | 
				
			|||||||
import { bufferedAtom, durationAtom, progressAtom } from "../state";
 | 
					import { bufferedAtom, durationAtom, progressAtom } from "../state";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ProgressBar = ({ chapters }: { chapters?: Chapter[] }) => {
 | 
					export const ProgressBar = ({ chapters }: { chapters?: Chapter[] }) => {
 | 
				
			||||||
	return null;
 | 
					 | 
				
			||||||
	const { css } = useYoshiki();
 | 
					 | 
				
			||||||
	const ref = useRef<View>(null);
 | 
					 | 
				
			||||||
	const [isSeeking, setSeek] = useState(false);
 | 
					 | 
				
			||||||
	const [progress, setProgress] = useAtom(progressAtom);
 | 
						const [progress, setProgress] = useAtom(progressAtom);
 | 
				
			||||||
	const buffered = useAtomValue(bufferedAtom);
 | 
						const buffered = useAtomValue(bufferedAtom);
 | 
				
			||||||
	const duration = useAtomValue(durationAtom);
 | 
						const duration = useAtomValue(durationAtom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return (
 | 
				
			||||||
 | 
							<Slider
 | 
				
			||||||
 | 
								progress={progress}
 | 
				
			||||||
 | 
								subtleProgress={buffered}
 | 
				
			||||||
 | 
								max={duration}
 | 
				
			||||||
 | 
								markers={chapters?.map((x) => x.startTime)}
 | 
				
			||||||
 | 
							/>
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						const { css } = useYoshiki();
 | 
				
			||||||
 | 
						const ref = useRef<View>(null);
 | 
				
			||||||
 | 
						const [isSeeking, setSeek] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const updateProgress = (event: NativeTouchEvent, skipSeek?: boolean) => {
 | 
						const updateProgress = (event: NativeTouchEvent, skipSeek?: boolean) => {
 | 
				
			||||||
		if (!(isSeeking || skipSeek) || !ref?.current) return;
 | 
							if (!(isSeeking || skipSeek) || !ref?.current) return;
 | 
				
			||||||
		const pageX: number = "pageX" in event ? event.pageX : event.changedTouches[0].pageX;
 | 
							const pageX: number = "pageX" in event ? event.pageX : event.changedTouches[0].pageX;
 | 
				
			||||||
 | 
				
			|||||||
@ -21,13 +21,12 @@
 | 
				
			|||||||
import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models";
 | 
					import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models";
 | 
				
			||||||
import { Head } from "@kyoo/primitives";
 | 
					import { Head } from "@kyoo/primitives";
 | 
				
			||||||
import { useState, useEffect, PointerEvent as ReactPointerEvent, ComponentProps } from "react";
 | 
					import { useState, useEffect, PointerEvent as ReactPointerEvent, ComponentProps } from "react";
 | 
				
			||||||
import { StyleSheet, View } from "react-native";
 | 
					import { PointerEvent, StyleSheet, View } from "react-native";
 | 
				
			||||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
 | 
					import { useAtom, useAtomValue, useSetAtom } from "jotai";
 | 
				
			||||||
import { useRouter } from "solito/router";
 | 
					import { useRouter } from "solito/router";
 | 
				
			||||||
import { Video } from "expo-av";
 | 
					 | 
				
			||||||
import { percent, useYoshiki } from "yoshiki/native";
 | 
					import { percent, useYoshiki } from "yoshiki/native";
 | 
				
			||||||
import { Hover, LoadingIndicator } from "./components/hover";
 | 
					import { Hover, LoadingIndicator } from "./components/hover";
 | 
				
			||||||
import { fullscreenAtom, playAtom, useSubtitleController, useVideoController } from "./state";
 | 
					import { fullscreenAtom, playAtom, Video } from "./state";
 | 
				
			||||||
import { episodeDisplayNumber } from "../details/episode";
 | 
					import { episodeDisplayNumber } from "../details/episode";
 | 
				
			||||||
import { useVideoKeyboard } from "./keyboard";
 | 
					import { useVideoKeyboard } from "./keyboard";
 | 
				
			||||||
import { MediaSessionManager } from "./media-session";
 | 
					import { MediaSessionManager } from "./media-session";
 | 
				
			||||||
@ -44,7 +43,7 @@ const query = (slug: string): QueryIdentifier<WatchItem> => ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const mapData = (
 | 
					const mapData = (
 | 
				
			||||||
	data: WatchItem | undefined,
 | 
						data: WatchItem | undefined,
 | 
				
			||||||
	previousSlug: string,
 | 
						previousSlug?: string,
 | 
				
			||||||
	nextSlug?: string,
 | 
						nextSlug?: string,
 | 
				
			||||||
): Partial<ComponentProps<typeof Hover>> => {
 | 
					): Partial<ComponentProps<typeof Hover>> => {
 | 
				
			||||||
	if (!data) return {};
 | 
						if (!data) return {};
 | 
				
			||||||
@ -61,6 +60,7 @@ const mapData = (
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
 | 
					export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
 | 
				
			||||||
	const { css } = useYoshiki();
 | 
						const { css } = useYoshiki();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -152,17 +152,17 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
 | 
				
			|||||||
				})}
 | 
									})}
 | 
				
			||||||
			>
 | 
								>
 | 
				
			||||||
				<Video
 | 
									<Video
 | 
				
			||||||
					source={{ uri: data?.link.direct }}
 | 
										links={data?.link}
 | 
				
			||||||
					videoStyle={{ margin: "auto" }}
 | 
										videoStyle={{ width: percent(100), height: percent(100) }}
 | 
				
			||||||
					{...css(StyleSheet.absoluteFillObject)}
 | 
										{...css(StyleSheet.absoluteFillObject)}
 | 
				
			||||||
					/* {...videoProps} */
 | 
										// onClick={onVideoClick}
 | 
				
			||||||
					// onPointerDown={(e: ReactPointerEvent<HTMLVideoElement>) => {
 | 
										// onPointerDown={(e: PointerEvent) => {
 | 
				
			||||||
					// 	if (e.pointerType === "mouse") {
 | 
										// 	if (e.type === "mouse") {
 | 
				
			||||||
					// 		onVideoClick();
 | 
										// 		onVideoClick();
 | 
				
			||||||
					// 	} else if (mouseMoved) {
 | 
										// 	} else if (mouseMoved) {
 | 
				
			||||||
					// 		setMouseMoved(false);
 | 
										// 		setMouseMoved(false);
 | 
				
			||||||
					// 	} else {
 | 
										// 	} else {
 | 
				
			||||||
					// 		mouseHasMoved();
 | 
										// 		// mouseHasMoved();
 | 
				
			||||||
					// 	}
 | 
										// 	}
 | 
				
			||||||
					// }}
 | 
										// }}
 | 
				
			||||||
					// onEnded={() => {
 | 
										// onEnded={() => {
 | 
				
			||||||
 | 
				
			|||||||
@ -84,7 +84,7 @@ export const MediaSessionManager = ({
 | 
				
			|||||||
		navigator.mediaSession.playbackState = isPlaying ? "playing" : "paused";
 | 
							navigator.mediaSession.playbackState = isPlaying ? "playing" : "paused";
 | 
				
			||||||
	}, [isPlaying]);
 | 
						}, [isPlaying]);
 | 
				
			||||||
	useEffect(() => {
 | 
						useEffect(() => {
 | 
				
			||||||
		if (!("mediaSession" in navigator)) return;
 | 
							if (!("mediaSession" in navigator) || !duration) return;
 | 
				
			||||||
		navigator.mediaSession.setPositionState({ position: progress, duration, playbackRate: 1 });
 | 
							navigator.mediaSession.setPositionState({ position: progress, duration, playbackRate: 1 });
 | 
				
			||||||
	}, [progress, duration]);
 | 
						}, [progress, duration]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -18,11 +18,11 @@
 | 
				
			|||||||
 * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
 | 
					 * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Font, Track } from "@kyoo/models";
 | 
					import { Font, Track, WatchItem } from "@kyoo/models";
 | 
				
			||||||
import { atom, useAtom, useSetAtom } from "jotai";
 | 
					import { atom, useAtom, useSetAtom } from "jotai";
 | 
				
			||||||
import { RefObject, useEffect, useRef } from "react";
 | 
					import { RefObject, useEffect, useRef, useState } from "react";
 | 
				
			||||||
import { createParam } from "solito";
 | 
					import { createParam } from "solito";
 | 
				
			||||||
import { ResizeMode, VideoProps } from "expo-av";
 | 
					import { ResizeMode, Video as NativeVideo, VideoProps } from "expo-av";
 | 
				
			||||||
import SubtitleOctopus from "libass-wasm";
 | 
					import SubtitleOctopus from "libass-wasm";
 | 
				
			||||||
import Hls from "hls.js";
 | 
					import Hls from "hls.js";
 | 
				
			||||||
import { bakedAtom } from "../jotai-utils";
 | 
					import { bakedAtom } from "../jotai-utils";
 | 
				
			||||||
@ -34,31 +34,13 @@ enum PlayMode {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const playModeAtom = atom<PlayMode>(PlayMode.Direct);
 | 
					const playModeAtom = atom<PlayMode>(PlayMode.Direct);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const playerAtom = atom<RefObject<HTMLVideoElement> | null>(null);
 | 
					export const playAtom = atom<boolean>(true);
 | 
				
			||||||
export const [_playAtom, playAtom] = bakedAtom(true, async (get, set, value) => {
 | 
					
 | 
				
			||||||
	const player = get(playerAtom);
 | 
					 | 
				
			||||||
	if (!player?.current) return;
 | 
					 | 
				
			||||||
	if (value) {
 | 
					 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
			await player.current.play();
 | 
					 | 
				
			||||||
		} catch (e) {
 | 
					 | 
				
			||||||
			if (e instanceof DOMException && e.name === "NotSupportedError")
 | 
					 | 
				
			||||||
				set(playModeAtom, PlayMode.Transmux);
 | 
					 | 
				
			||||||
			else if (!(e instanceof DOMException && e.name === "NotAllowedError")) console.log(e);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		player.current.pause();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
export const loadAtom = atom(false);
 | 
					export const loadAtom = atom(false);
 | 
				
			||||||
export const [_progressAtom, progressAtom] = bakedAtom(0, (get, set, value, baker) => {
 | 
					export const progressAtom = atom(0);
 | 
				
			||||||
	const player = get(playerAtom);
 | 
					 | 
				
			||||||
	if (!player?.current) return;
 | 
					 | 
				
			||||||
	set(baker, value);
 | 
					 | 
				
			||||||
	player.current.currentTime = value;
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
export const bufferedAtom = atom(0);
 | 
					export const bufferedAtom = atom(0);
 | 
				
			||||||
export const durationAtom = atom(1);
 | 
					export const durationAtom = atom<number | undefined>(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const [_volumeAtom, volumeAtom] = bakedAtom(100, (get, set, value, baker) => {
 | 
					export const [_volumeAtom, volumeAtom] = bakedAtom(100, (get, set, value, baker) => {
 | 
				
			||||||
	const player = get(playerAtom);
 | 
						const player = get(playerAtom);
 | 
				
			||||||
	if (!player?.current) return;
 | 
						if (!player?.current) return;
 | 
				
			||||||
@ -87,21 +69,27 @@ export const [_, fullscreenAtom] = bakedAtom(false, async (_, set, value, baker)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let hls: Hls | null = null;
 | 
					let hls: Hls | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useVideoController = (links?: { direct: string; transmux: string }) => {
 | 
					export const Video = ({ links, ...props }: { links?: WatchItem["link"] } & VideoProps) => {
 | 
				
			||||||
	const player = useRef<HTMLVideoElement>(null);
 | 
						// const player = useRef<HTMLVideoElement>(null);
 | 
				
			||||||
	const setPlayer = useSetAtom(playerAtom);
 | 
						// const setPlayer = useSetAtom(playerAtom);
 | 
				
			||||||
	const setPlay = useSetAtom(_playAtom);
 | 
						// const setLoad = useSetAtom(loadAtom);
 | 
				
			||||||
	const setPPlay = useSetAtom(playAtom);
 | 
						// const setVolume = useSetAtom(_volumeAtom);
 | 
				
			||||||
	const setLoad = useSetAtom(loadAtom);
 | 
						// const setMuted = useSetAtom(_mutedAtom);
 | 
				
			||||||
	const setProgress = useSetAtom(_progressAtom);
 | 
						// const setFullscreen = useSetAtom(fullscreenAtom);
 | 
				
			||||||
	const setBuffered = useSetAtom(bufferedAtom);
 | 
						// const [playMode, setPlayMode] = useAtom(playModeAtom);
 | 
				
			||||||
	const setDuration = useSetAtom(durationAtom);
 | 
					 | 
				
			||||||
	const setVolume = useSetAtom(_volumeAtom);
 | 
					 | 
				
			||||||
	const setMuted = useSetAtom(_mutedAtom);
 | 
					 | 
				
			||||||
	const setFullscreen = useSetAtom(fullscreenAtom);
 | 
					 | 
				
			||||||
	const [playMode, setPlayMode] = useAtom(playModeAtom);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	setPlayer(player);
 | 
						const ref = useRef<NativeVideo | null>(null);
 | 
				
			||||||
 | 
						const [isPlaying, setPlay] = useAtom(playAtom);
 | 
				
			||||||
 | 
						const [progress, setProgress] = useAtom(progressAtom);
 | 
				
			||||||
 | 
						const [buffered, setBuffered] = useAtom(bufferedAtom);
 | 
				
			||||||
 | 
						const [duration, setDuration] = useAtom(durationAtom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						useEffect(() => {
 | 
				
			||||||
 | 
							// I think this will trigger an infinite refresh loop
 | 
				
			||||||
 | 
							// ref.current?.setStatusAsync({ positionMillis: progress });
 | 
				
			||||||
 | 
						}, [progress]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// setPlayer(player);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// useEffect(() => {
 | 
						// useEffect(() => {
 | 
				
			||||||
	// 	if (!player.current) return;
 | 
						// 	if (!player.current) return;
 | 
				
			||||||
@ -137,45 +125,53 @@ export const useVideoController = (links?: { direct: string; transmux: string })
 | 
				
			|||||||
	// 	if (!player?.current?.duration) return;
 | 
						// 	if (!player?.current?.duration) return;
 | 
				
			||||||
	// 	setDuration(player.current.duration);
 | 
						// 	setDuration(player.current.duration);
 | 
				
			||||||
	// }, [player, setDuration]);
 | 
						// }, [player, setDuration]);
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const videoProps: VideoProps = {
 | 
						return (
 | 
				
			||||||
		// ref: player,
 | 
							<NativeVideo
 | 
				
			||||||
		// shouldPlay: isPlaying,
 | 
								ref={ref}
 | 
				
			||||||
		// onDoubleClick: () => {
 | 
								{...props}
 | 
				
			||||||
		// 	setFullscreen(!document.fullscreenElement);
 | 
								source={links ? { uri: links.direct } : undefined}
 | 
				
			||||||
		// },
 | 
								shouldPlay={isPlaying}
 | 
				
			||||||
		// onPlay: () => setPlay(true),
 | 
								onPlaybackStatusUpdate={(status) => {
 | 
				
			||||||
		// onPause: () => setPlay(false),
 | 
									// TODO: Handle error state
 | 
				
			||||||
		// onWaiting: () => setLoad(true),
 | 
									if (!status.isLoaded) return;
 | 
				
			||||||
		// onCanPlay: () => setLoad(false),
 | 
					
 | 
				
			||||||
		onError: () => {
 | 
									setPlay(status.shouldPlay);
 | 
				
			||||||
			if (player?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED)
 | 
									setProgress(status.positionMillis);
 | 
				
			||||||
				setPlayMode(PlayMode.Transmux);
 | 
									setBuffered(status.playableDurationMillis ?? 0);
 | 
				
			||||||
		},
 | 
									setDuration(status.durationMillis);
 | 
				
			||||||
		// onTimeUpdate: () => setProgress(player?.current?.currentTime ?? 0),
 | 
								}}
 | 
				
			||||||
		// onDurationChange: () => setDuration(player?.current?.duration ?? 0),
 | 
								// ref: player,
 | 
				
			||||||
		// onProgress: () =>
 | 
								// shouldPlay: isPlaying,
 | 
				
			||||||
		// 	setBuffered(
 | 
								// onDoubleClick: () => {
 | 
				
			||||||
		// 		player?.current?.buffered.length
 | 
								// 	setFullscreen(!document.fullscreenElement);
 | 
				
			||||||
		// 			? player.current.buffered.end(player.current.buffered.length - 1)
 | 
								// },
 | 
				
			||||||
		// 			: 0,
 | 
								// onPlay: () => setPlay(true),
 | 
				
			||||||
		// 	),
 | 
								// onPause: () => setPlay(false),
 | 
				
			||||||
		// onVolumeChange: () => {
 | 
								// onWaiting: () => setLoad(true),
 | 
				
			||||||
		// 	if (!player.current) return;
 | 
								// onCanPlay: () => setLoad(false),
 | 
				
			||||||
		// 	setVolume(player.current.volume * 100);
 | 
								// onError: () => {
 | 
				
			||||||
		// 	setMuted(player?.current.muted);
 | 
								// 	if (player?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED)
 | 
				
			||||||
		// },
 | 
								// 		setPlayMode(PlayMode.Transmux);
 | 
				
			||||||
		resizeMode: ResizeMode.CONTAIN,
 | 
								// },
 | 
				
			||||||
		useNativeControls: false,
 | 
								// onTimeUpdate: () => setProgress(player?.current?.currentTime ?? 0),
 | 
				
			||||||
	};
 | 
								// onDurationChange: () => setDuration(player?.current?.duration ?? 0),
 | 
				
			||||||
	return {
 | 
								// onProgress: () =>
 | 
				
			||||||
		playerRef: player,
 | 
								// 	setBuffered(
 | 
				
			||||||
		videoProps,
 | 
								// 		player?.current?.buffered.length
 | 
				
			||||||
		onVideoClick: () => {
 | 
								// 			? player.current.buffered.end(player.current.buffered.length - 1)
 | 
				
			||||||
			if (!player.current) return;
 | 
								// 			: 0,
 | 
				
			||||||
			setPPlay(player.current.paused);
 | 
								// 	),
 | 
				
			||||||
		},
 | 
								// onVolumeChange: () => {
 | 
				
			||||||
	};
 | 
								// 	if (!player.current) return;
 | 
				
			||||||
 | 
								// 	setVolume(player.current.volume * 100);
 | 
				
			||||||
 | 
								// 	setMuted(player?.current.muted);
 | 
				
			||||||
 | 
								// },
 | 
				
			||||||
 | 
								resizeMode={ResizeMode.CONTAIN}
 | 
				
			||||||
 | 
								useNativeControls={false}
 | 
				
			||||||
 | 
							/>
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const htmlTrackAtom = atom<HTMLTrackElement | null>(null);
 | 
					const htmlTrackAtom = atom<HTMLTrackElement | null>(null);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										618
									
								
								front/yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										618
									
								
								front/yarn.lock
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user