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 (