From 378b16b3e49164beef037c8635fa36f424d806d5 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Tue, 13 Feb 2024 21:13:48 +0100 Subject: [PATCH] Improve layout, add current time and duration --- apps/expo/src/app/videoPlayer/index.tsx | 23 +++++++------ .../src/components/player/BottomControls.tsx | 28 +++++++++++++++ apps/expo/src/components/player/Controls.tsx | 11 ++++-- .../src/components/player/ControlsOverlay.tsx | 20 +++++++++++ apps/expo/src/components/player/Header.tsx | 33 +++++++++++------- .../src/components/player/MiddleControls.tsx | 34 +++++++------------ apps/expo/src/components/player/utils.ts | 18 ++++++++++ 7 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 apps/expo/src/components/player/BottomControls.tsx create mode 100644 apps/expo/src/components/player/ControlsOverlay.tsx create mode 100644 apps/expo/src/components/player/utils.ts diff --git a/apps/expo/src/app/videoPlayer/index.tsx b/apps/expo/src/app/videoPlayer/index.tsx index 23e607a..8d316eb 100644 --- a/apps/expo/src/app/videoPlayer/index.tsx +++ b/apps/expo/src/app/videoPlayer/index.tsx @@ -13,8 +13,7 @@ import { findHighestQuality } from "@movie-web/provider-utils"; import type { ItemData } from "~/components/item/item"; import type { HeaderData } from "~/components/player/Header"; -import { Header } from "~/components/player/Header"; -import { MiddleControls } from "~/components/player/MiddleControls"; +import { ControlsOverlay } from "~/components/player/ControlsOverlay"; import { usePlayerStore } from "~/stores/player/store"; export default function VideoPlayerWrapper() { @@ -44,6 +43,7 @@ const VideoPlayer: React.FC = ({ data }) => { const scale = useSharedValue(1); const setVideoRef = usePlayerStore((state) => state.setVideoRef); const setStatus = usePlayerStore((state) => state.setStatus); + const isIdle = usePlayerStore((state) => state.interface.isIdle); const setIsIdle = usePlayerStore((state) => state.setIsIdle); const presentFullscreenPlayer = usePlayerStore( (state) => state.presentFullscreenPlayer, @@ -67,6 +67,11 @@ const VideoPlayer: React.FC = ({ data }) => { useEffect(() => { const initializePlayer = async () => { + if (!data) { + await dismissFullscreenPlayer(); + return router.push("/(tabs)"); + } + StatusBar.setStatusBarHidden(true); if (Platform.OS === "android") { @@ -74,11 +79,6 @@ const VideoPlayer: React.FC = ({ data }) => { } setIsLoading(true); - if (!data) { - await dismissFullscreenPlayer(); - return router.push("/(tabs)"); - } - const { item, stream, media } = data; setHeaderData({ @@ -145,16 +145,17 @@ const VideoPlayer: React.FC = ({ data }) => { ref={setVideoRef} source={videoSrc} shouldPlay - resizeMode={resizeMode} + resizeMode={ResizeMode.CONTAIN} onLoadStart={onVideoLoadStart} onReadyForDisplay={onReadyForDisplay} onPlaybackStatusUpdate={setStatus} style={styles.video} - onTouchStart={() => setIsIdle(false)} + onTouchStart={() => setIsIdle(!isIdle)} /> {isLoading && } - {!isLoading && data &&
} - {!isLoading && } + {!isLoading && headerData && ( + + )} ); diff --git a/apps/expo/src/components/player/BottomControls.tsx b/apps/expo/src/components/player/BottomControls.tsx new file mode 100644 index 0000000..b6ced21 --- /dev/null +++ b/apps/expo/src/components/player/BottomControls.tsx @@ -0,0 +1,28 @@ +import { View } from "react-native"; + +import { usePlayerStore } from "~/stores/player/store"; +import { Text } from "../ui/Text"; +import { Controls } from "./Controls"; +import { mapMillisecondsToTime } from "./utils"; + +export const BottomControls = () => { + const status = usePlayerStore((state) => state.status); + status?.isLoaded; + + if (status?.isLoaded) { + return ( + + + + + {mapMillisecondsToTime(status.positionMillis ?? 0)} + + + {mapMillisecondsToTime(status.durationMillis ?? 0)} + + + + + ); + } +}; diff --git a/apps/expo/src/components/player/Controls.tsx b/apps/expo/src/components/player/Controls.tsx index 05a93d2..37fb81a 100644 --- a/apps/expo/src/components/player/Controls.tsx +++ b/apps/expo/src/components/player/Controls.tsx @@ -1,5 +1,6 @@ +import type { TouchableOpacity } from "react-native"; import React from "react"; -import { TouchableOpacity } from "react-native"; +import { TouchableWithoutFeedback } from "react-native-gesture-handler"; import { usePlayerStore } from "~/stores/player/store"; @@ -9,10 +10,14 @@ interface ControlsProps extends React.ComponentProps { export const Controls = ({ children, className }: ControlsProps) => { const idle = usePlayerStore((state) => state.interface.isIdle); + const setIsIdle = usePlayerStore((state) => state.setIsIdle); return ( - + setIsIdle(false)} + > {!idle && children} - + ); }; diff --git a/apps/expo/src/components/player/ControlsOverlay.tsx b/apps/expo/src/components/player/ControlsOverlay.tsx new file mode 100644 index 0000000..2d44c7a --- /dev/null +++ b/apps/expo/src/components/player/ControlsOverlay.tsx @@ -0,0 +1,20 @@ +import { View } from "react-native"; + +import type { HeaderData } from "./Header"; +import { BottomControls } from "./BottomControls"; +import { Header } from "./Header"; +import { MiddleControls } from "./MiddleControls"; + +interface ControlsOverlayProps { + headerData: HeaderData; +} + +export const ControlsOverlay = ({ headerData }: ControlsOverlayProps) => { + return ( + +
+ + + + ); +}; diff --git a/apps/expo/src/components/player/Header.tsx b/apps/expo/src/components/player/Header.tsx index 62ffa05..b007a58 100644 --- a/apps/expo/src/components/player/Header.tsx +++ b/apps/expo/src/components/player/Header.tsx @@ -1,5 +1,6 @@ import { Image, View } from "react-native"; +import { usePlayerStore } from "~/stores/player/store"; import Icon from "../../../assets/images/icon-transparent.png"; import { Text } from "../ui/Text"; import { BackButton } from "./BackButton"; @@ -17,18 +18,24 @@ interface HeaderProps { } export const Header = ({ data }: HeaderProps) => { - return ( - - - - {data.season !== undefined && data.episode !== undefined - ? `${data.title} (${data.year}) S${data.season.toString().padStart(2, "0")}E${data.episode.toString().padStart(2, "0")}` - : `${data.title} (${data.year})`} - - - - movie-web + const isIdle = usePlayerStore((state) => state.interface.isIdle); + + if (!isIdle) { + return ( + + + + + + {data.season !== undefined && data.episode !== undefined + ? `${data.title} (${data.year}) S${data.season.toString().padStart(2, "0")}E${data.episode.toString().padStart(2, "0")}` + : `${data.title} (${data.year})`} + + + + movie-web + - - ); + ); + } }; diff --git a/apps/expo/src/components/player/MiddleControls.tsx b/apps/expo/src/components/player/MiddleControls.tsx index 62ebe1e..a1ba53a 100644 --- a/apps/expo/src/components/player/MiddleControls.tsx +++ b/apps/expo/src/components/player/MiddleControls.tsx @@ -1,31 +1,21 @@ -import { TouchableWithoutFeedback, View } from "react-native"; +import { View } from "react-native"; -import { usePlayerStore } from "~/stores/player/store"; import { Controls } from "./Controls"; import { PlayButton } from "./PlayButton"; import { SeekButton } from "./SeekButton"; export const MiddleControls = () => { - const idle = usePlayerStore((state) => state.interface.isIdle); - const setIsIdle = usePlayerStore((state) => state.setIsIdle); - - const handleTouch = () => { - setIsIdle(!idle); - }; - return ( - - - - - - - - - - - - - + + + + + + + + + + + ); }; diff --git a/apps/expo/src/components/player/utils.ts b/apps/expo/src/components/player/utils.ts new file mode 100644 index 0000000..ab8e2ef --- /dev/null +++ b/apps/expo/src/components/player/utils.ts @@ -0,0 +1,18 @@ +export const mapMillisecondsToTime = (milliseconds: number): string => { + const hours = Math.floor(milliseconds / (1000 * 60 * 60)); + const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); + + const components: string[] = []; + + if (hours > 0) { + components.push(hours.toString().padStart(2, "0")); + } + + components.push(minutes.toString().padStart(2, "0")); + components.push(seconds.toString().padStart(2, "0")); + + const formattedTime = components.join(":"); + + return formattedTime; +};