mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Player cleanups
This commit is contained in:
parent
67da1563be
commit
b88cd583d3
@ -80,6 +80,11 @@ const GlobalCssTheme = () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::cue {
|
||||||
|
background-color: transparent;
|
||||||
|
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
|
||||||
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<WebTooltip theme={theme} />
|
<WebTooltip theme={theme} />
|
||||||
<SkeletonCss />
|
<SkeletonCss />
|
||||||
|
@ -20,17 +20,18 @@
|
|||||||
|
|
||||||
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, ComponentProps } from "react";
|
||||||
import { Platform, Pressable, StyleSheet, View } from "react-native";
|
import { Platform, Pressable, StyleSheet } from "react-native";
|
||||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
import { useTranslation } from "react-i18next";
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
import { useRouter } from "solito/router";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { Back, Hover, LoadingIndicator } from "./components/hover";
|
import { Back, Hover, LoadingIndicator } from "./components/hover";
|
||||||
import { fullscreenAtom, playAtom, Video } 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";
|
||||||
import { ErrorView } from "../fetch";
|
import { ErrorView } from "../fetch";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const query = (slug: string): QueryIdentifier<WatchItem> => ({
|
const query = (slug: string): QueryIdentifier<WatchItem> => ({
|
||||||
path: ["watch", slug],
|
path: ["watch", slug],
|
||||||
@ -68,6 +69,7 @@ let touchTimeout: NodeJS.Timeout;
|
|||||||
export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [playbackError, setPlaybackError] = useState<string | undefined>(undefined);
|
const [playbackError, setPlaybackError] = useState<string | undefined>(undefined);
|
||||||
const { data, error } = useFetch(query(slug));
|
const { data, error } = useFetch(query(slug));
|
||||||
@ -78,8 +80,6 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
const next =
|
const next =
|
||||||
data && !data.isMovie && data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : undefined;
|
data && !data.isMovie && data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : undefined;
|
||||||
|
|
||||||
// const { playerRef, videoProps, onVideoClick } = useVideoController(data?.link);
|
|
||||||
// useSubtitleController(playerRef, data?.subtitles, data?.fonts);
|
|
||||||
useVideoKeyboard(data?.subtitles, data?.fonts, previous, next);
|
useVideoKeyboard(data?.subtitles, data?.fonts, previous, next);
|
||||||
|
|
||||||
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
||||||
@ -107,9 +107,6 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
return () => document.removeEventListener("pointermove", handler);
|
return () => document.removeEventListener("pointermove", handler);
|
||||||
});
|
});
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// setPlay(true);
|
|
||||||
// }, [slug, setPlay]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Platform.OS !== "web" || !/Mobi/i.test(window.navigator.userAgent)) return;
|
if (Platform.OS !== "web" || !/Mobi/i.test(window.navigator.userAgent)) return;
|
||||||
setFullscreen(true);
|
setFullscreen(true);
|
||||||
@ -148,12 +145,6 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
next={next}
|
next={next}
|
||||||
previous={previous}
|
previous={previous}
|
||||||
/>
|
/>
|
||||||
{/* <style jsx global>{` */}
|
|
||||||
{/* ::cue { */}
|
|
||||||
{/* background-color: transparent; */}
|
|
||||||
{/* text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; */}
|
|
||||||
{/* } */}
|
|
||||||
{/* `}</style> */}
|
|
||||||
<Pressable
|
<Pressable
|
||||||
focusable={false}
|
focusable={false}
|
||||||
onHoverOut={() => setMouseMoved(false)}
|
onHoverOut={() => setMouseMoved(false)}
|
||||||
@ -181,7 +172,7 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
}, 400);
|
}, 400);
|
||||||
setPlay(!isPlaying);
|
setPlay(!isPlaying);
|
||||||
}
|
}
|
||||||
: () => displayControls ? setMouseMoved(false) : show()
|
: () => (displayControls ? setMouseMoved(false) : show())
|
||||||
}
|
}
|
||||||
{...css(StyleSheet.absoluteFillObject)}
|
{...css(StyleSheet.absoluteFillObject)}
|
||||||
>
|
>
|
||||||
@ -189,15 +180,15 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
links={data?.link}
|
links={data?.link}
|
||||||
setError={setPlaybackError}
|
setError={setPlaybackError}
|
||||||
fonts={data?.fonts}
|
fonts={data?.fonts}
|
||||||
|
onEnd={() => {
|
||||||
|
if (!data) return;
|
||||||
|
if (data.isMovie) router.push(`/movie/${data.slug}`);
|
||||||
|
else
|
||||||
|
router.push(
|
||||||
|
data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : `/show/${data.showSlug}`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
{...css(StyleSheet.absoluteFillObject)}
|
{...css(StyleSheet.absoluteFillObject)}
|
||||||
// onEnded={() => {
|
|
||||||
// if (!data) return;
|
|
||||||
// if (data.isMovie) router.push(`/movie/${data.slug}`);
|
|
||||||
// else
|
|
||||||
// router.push(
|
|
||||||
// data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : `/show/${data.showSlug}`,
|
|
||||||
// );
|
|
||||||
// }}
|
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Track, WatchItem } from "@kyoo/models";
|
import { Track, WatchItem } from "@kyoo/models";
|
||||||
import { atom, useAtomValue, useSetAtom } from "jotai";
|
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import { memo, useEffect, useLayoutEffect, useRef } from "react";
|
import { memo, useEffect, useLayoutEffect, useRef } from "react";
|
||||||
import NativeVideo, { VideoProperties as VideoProps } from "./video";
|
import NativeVideo, { VideoProperties as VideoProps } from "./video";
|
||||||
import { bakedAtom } from "../jotai-utils";
|
import { bakedAtom } from "../jotai-utils";
|
||||||
@ -75,13 +75,9 @@ export const Video = memo(function _Video({
|
|||||||
setError: (error: string | undefined) => void;
|
setError: (error: string | undefined) => void;
|
||||||
} & Partial<VideoProps>) {
|
} & Partial<VideoProps>) {
|
||||||
const ref = useRef<NativeVideo | null>(null);
|
const ref = useRef<NativeVideo | null>(null);
|
||||||
const isPlaying = useAtomValue(playAtom);
|
const [isPlaying, setPlay] = useAtom(playAtom);
|
||||||
const setLoad = useSetAtom(loadAtom);
|
const setLoad = useSetAtom(loadAtom);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
setLoad(true);
|
|
||||||
}, [setLoad]);
|
|
||||||
|
|
||||||
const publicProgress = useAtomValue(publicProgressAtom);
|
const publicProgress = useAtomValue(publicProgressAtom);
|
||||||
const setPrivateProgress = useSetAtom(privateProgressAtom);
|
const setPrivateProgress = useSetAtom(privateProgressAtom);
|
||||||
const setBuffered = useSetAtom(bufferedAtom);
|
const setBuffered = useSetAtom(bufferedAtom);
|
||||||
@ -90,6 +86,13 @@ export const Video = memo(function _Video({
|
|||||||
ref.current?.seek(publicProgress);
|
ref.current?.seek(publicProgress);
|
||||||
}, [publicProgress]);
|
}, [publicProgress]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
// Reset the state when a new video is loaded.
|
||||||
|
setLoad(true);
|
||||||
|
setPrivateProgress(0);
|
||||||
|
setPlay(true);
|
||||||
|
}, [links, setLoad, setPrivateProgress, setPlay]);
|
||||||
|
|
||||||
const volume = useAtomValue(volumeAtom);
|
const volume = useAtomValue(volumeAtom);
|
||||||
const isMuted = useAtomValue(mutedAtom);
|
const isMuted = useAtomValue(mutedAtom);
|
||||||
|
|
||||||
@ -125,6 +128,7 @@ export const Video = memo(function _Video({
|
|||||||
onLoad={(info) => {
|
onLoad={(info) => {
|
||||||
setDuration(info.duration);
|
setDuration(info.duration);
|
||||||
}}
|
}}
|
||||||
|
onPlayPause={setPlay}
|
||||||
selectedTextTrack={
|
selectedTextTrack={
|
||||||
subtitle
|
subtitle
|
||||||
? {
|
? {
|
||||||
@ -135,10 +139,6 @@ export const Video = memo(function _Video({
|
|||||||
}
|
}
|
||||||
fonts={fonts}
|
fonts={fonts}
|
||||||
// TODO: textTracks: external subtitles
|
// TODO: textTracks: external subtitles
|
||||||
// onError: () => {
|
|
||||||
// if (player?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED)
|
|
||||||
// setPlayMode(PlayMode.Transmux);
|
|
||||||
// },
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -28,15 +28,16 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { VideoProps } from "react-native-video";
|
import { VideoProps } from "react-native-video";
|
||||||
import { atom, useAtom, useAtomValue } from "jotai";
|
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import { useYoshiki } from "yoshiki";
|
import { useYoshiki } from "yoshiki";
|
||||||
import SubtitleOctopus from "libass-wasm";
|
import SubtitleOctopus from "libass-wasm";
|
||||||
import { subtitleAtom } from "./state";
|
import { playAtom, subtitleAtom } from "./state";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
|
|
||||||
declare module "react-native-video" {
|
declare module "react-native-video" {
|
||||||
interface VideoProperties {
|
interface VideoProperties {
|
||||||
fonts?: Font[];
|
fonts?: Font[];
|
||||||
|
onPlayPause: (isPlaying: boolean) => void;
|
||||||
}
|
}
|
||||||
export type VideoProps = Omit<VideoProperties, "source"> & {
|
export type VideoProps = Omit<VideoProperties, "source"> & {
|
||||||
source: { uri?: string; transmux?: string };
|
source: { uri?: string; transmux?: string };
|
||||||
@ -52,7 +53,19 @@ const playModeAtom = atom<PlayMode>(PlayMode.Direct);
|
|||||||
let hls: Hls | null = null;
|
let hls: Hls | null = null;
|
||||||
|
|
||||||
const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video(
|
const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video(
|
||||||
{ source, paused, muted, volume, onBuffer, onLoad, onProgress, onError, fonts },
|
{
|
||||||
|
source,
|
||||||
|
paused,
|
||||||
|
muted,
|
||||||
|
volume,
|
||||||
|
onBuffer,
|
||||||
|
onLoad,
|
||||||
|
onProgress,
|
||||||
|
onError,
|
||||||
|
onEnd,
|
||||||
|
onPlayPause,
|
||||||
|
fonts,
|
||||||
|
},
|
||||||
forwaredRef,
|
forwaredRef,
|
||||||
) {
|
) {
|
||||||
const ref = useRef<HTMLVideoElement>(null);
|
const ref = useRef<HTMLVideoElement>(null);
|
||||||
@ -87,7 +100,6 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
}, [source.uri, setPlayMode]);
|
}, [source.uri, setPlayMode]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
console.log("toto");
|
|
||||||
const src = playMode === PlayMode.Direct ? source?.uri : source?.transmux;
|
const src = playMode === PlayMode.Direct ? source?.uri : source?.transmux;
|
||||||
|
|
||||||
if (!ref?.current || !src) return;
|
if (!ref?.current || !src) return;
|
||||||
@ -105,9 +117,17 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
}
|
}
|
||||||
}, [playMode, source?.uri, source?.transmux]);
|
}, [playMode, source?.uri, source?.transmux]);
|
||||||
|
|
||||||
|
const setPlay = useSetAtom(playAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
// Set play state to the player's value (if autoplay is denied)
|
||||||
|
setPlay(!ref.current.paused);
|
||||||
|
}, [setPlay]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
src={source.uri}
|
||||||
muted={muted}
|
muted={muted}
|
||||||
autoPlay={!paused}
|
autoPlay={!paused}
|
||||||
onCanPlay={() => onBuffer?.call(null, { isBuffering: false })}
|
onCanPlay={() => onBuffer?.call(null, { isBuffering: false })}
|
||||||
@ -138,6 +158,9 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onPlay={() => onPlayPause?.call(null, true)}
|
||||||
|
onPause={() => onPlayPause?.call(null, false)}
|
||||||
|
onEnded={onEnd}
|
||||||
{...css({ width: "100%", height: "100%" })}
|
{...css({ width: "100%", height: "100%" })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user