diff --git a/front/apps/web/src/pages/_app.tsx b/front/apps/web/src/pages/_app.tsx index 58780407..8793b4f5 100755 --- a/front/apps/web/src/pages/_app.tsx +++ b/front/apps/web/src/pages/_app.tsx @@ -80,6 +80,11 @@ const GlobalCssTheme = () => { width: 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; + } `} diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index a3ab71fe..ccade134 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -20,17 +20,18 @@ import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models"; import { Head } from "@kyoo/primitives"; -import { useState, useEffect, PointerEvent as ReactPointerEvent, ComponentProps } from "react"; -import { Platform, Pressable, StyleSheet, View } from "react-native"; -import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { percent, useYoshiki } from "yoshiki/native"; +import { useState, useEffect, ComponentProps } from "react"; +import { Platform, Pressable, StyleSheet } from "react-native"; +import { useTranslation } from "react-i18next"; +import { useRouter } from "solito/router"; +import { useAtom } from "jotai"; +import { useYoshiki } from "yoshiki/native"; import { Back, Hover, LoadingIndicator } from "./components/hover"; import { fullscreenAtom, playAtom, Video } from "./state"; import { episodeDisplayNumber } from "../details/episode"; import { useVideoKeyboard } from "./keyboard"; import { MediaSessionManager } from "./media-session"; import { ErrorView } from "../fetch"; -import { useTranslation } from "react-i18next"; const query = (slug: string): QueryIdentifier => ({ path: ["watch", slug], @@ -68,6 +69,7 @@ let touchTimeout: NodeJS.Timeout; export const Player: QueryPage<{ slug: string }> = ({ slug }) => { const { css } = useYoshiki(); const { t } = useTranslation(); + const router = useRouter(); const [playbackError, setPlaybackError] = useState(undefined); const { data, error } = useFetch(query(slug)); @@ -78,8 +80,6 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { const next = 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); const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom); @@ -107,9 +107,6 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { return () => document.removeEventListener("pointermove", handler); }); - // useEffect(() => { - // setPlay(true); - // }, [slug, setPlay]); useEffect(() => { if (Platform.OS !== "web" || !/Mobi/i.test(window.navigator.userAgent)) return; setFullscreen(true); @@ -148,12 +145,6 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { next={next} previous={previous} /> - {/* */} setMouseMoved(false)} @@ -181,7 +172,7 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { }, 400); setPlay(!isPlaying); } - : () => displayControls ? setMouseMoved(false) : show() + : () => (displayControls ? setMouseMoved(false) : show()) } {...css(StyleSheet.absoluteFillObject)} > @@ -189,15 +180,15 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => { links={data?.link} setError={setPlaybackError} 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)} - // onEnded={() => { - // if (!data) return; - // if (data.isMovie) router.push(`/movie/${data.slug}`); - // else - // router.push( - // data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : `/show/${data.showSlug}`, - // ); - // }} /> diff --git a/front/packages/ui/src/player/state.tsx b/front/packages/ui/src/player/state.tsx index 64de53b7..34cf3d32 100644 --- a/front/packages/ui/src/player/state.tsx +++ b/front/packages/ui/src/player/state.tsx @@ -19,7 +19,7 @@ */ 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 NativeVideo, { VideoProperties as VideoProps } from "./video"; import { bakedAtom } from "../jotai-utils"; @@ -75,13 +75,9 @@ export const Video = memo(function _Video({ setError: (error: string | undefined) => void; } & Partial) { const ref = useRef(null); - const isPlaying = useAtomValue(playAtom); + const [isPlaying, setPlay] = useAtom(playAtom); const setLoad = useSetAtom(loadAtom); - useLayoutEffect(() => { - setLoad(true); - }, [setLoad]); - const publicProgress = useAtomValue(publicProgressAtom); const setPrivateProgress = useSetAtom(privateProgressAtom); const setBuffered = useSetAtom(bufferedAtom); @@ -90,6 +86,13 @@ export const Video = memo(function _Video({ ref.current?.seek(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 isMuted = useAtomValue(mutedAtom); @@ -125,6 +128,7 @@ export const Video = memo(function _Video({ onLoad={(info) => { setDuration(info.duration); }} + onPlayPause={setPlay} selectedTextTrack={ subtitle ? { @@ -135,10 +139,6 @@ export const Video = memo(function _Video({ } fonts={fonts} // TODO: textTracks: external subtitles - // onError: () => { - // if (player?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) - // setPlayMode(PlayMode.Transmux); - // }, /> ); }); diff --git a/front/packages/ui/src/player/video.web.tsx b/front/packages/ui/src/player/video.web.tsx index c1516433..a9b966ee 100644 --- a/front/packages/ui/src/player/video.web.tsx +++ b/front/packages/ui/src/player/video.web.tsx @@ -28,15 +28,16 @@ import { useRef, } from "react"; import { VideoProps } from "react-native-video"; -import { atom, useAtom, useAtomValue } from "jotai"; +import { atom, useAtom, useAtomValue, useSetAtom } from "jotai"; import { useYoshiki } from "yoshiki"; import SubtitleOctopus from "libass-wasm"; -import { subtitleAtom } from "./state"; +import { playAtom, subtitleAtom } from "./state"; import Hls from "hls.js"; declare module "react-native-video" { interface VideoProperties { fonts?: Font[]; + onPlayPause: (isPlaying: boolean) => void; } export type VideoProps = Omit & { source: { uri?: string; transmux?: string }; @@ -52,7 +53,19 @@ const playModeAtom = atom(PlayMode.Direct); let hls: Hls | null = null; 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, ) { const ref = useRef(null); @@ -87,7 +100,6 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function }, [source.uri, setPlayMode]); useLayoutEffect(() => { - console.log("toto"); const src = playMode === PlayMode.Direct ? source?.uri : source?.transmux; if (!ref?.current || !src) return; @@ -105,9 +117,17 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function } }, [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 (