diff --git a/apps/expo/src/app/videoPlayer/index.tsx b/apps/expo/src/app/videoPlayer/index.tsx index 6ab8900..f2ac9ac 100644 --- a/apps/expo/src/app/videoPlayer/index.tsx +++ b/apps/expo/src/app/videoPlayer/index.tsx @@ -22,6 +22,7 @@ import type { HeaderData } from "~/components/player/Header"; import { ControlsOverlay } from "~/components/player/ControlsOverlay"; import { Text } from "~/components/ui/Text"; import { useBrightness } from "~/hooks/player/useBrightness"; +import { useVolume } from "~/hooks/player/useVolume"; import { usePlayerStore } from "~/stores/player/store"; export default function VideoPlayerWrapper() { @@ -50,13 +51,18 @@ const VideoPlayer: React.FC = ({ data }) => { setShowBrightnessOverlay, handleBrightnessChange, } = useBrightness(); + const { + currentVolume, + debouncedVolume, + showVolumeOverlay, + setShowVolumeOverlay, + handleVolumeChange, + } = useVolume(); const [videoSrc, setVideoSrc] = useState(); const [isLoading, setIsLoading] = useState(true); const [headerData, setHeaderData] = useState(); const [resizeMode, setResizeMode] = useState(ResizeMode.CONTAIN); const [shouldPlay, setShouldPlay] = useState(true); - const [showVolumeOverlay, setShowVolumeOverlay] = useState(false); - const [currentVolume, setCurrentVolume] = useState(0.5); const router = useRouter(); const scale = useSharedValue(1); @@ -94,20 +100,20 @@ const VideoPlayer: React.FC = ({ data }) => { runOnJS(togglePlayback)(); }); - const handleVolumeChange = (newValue: number) => { - setCurrentVolume(newValue); - setShowVolumeOverlay(true); - setTimeout(() => setShowVolumeOverlay(false), 2000); - }; - const screenHalfWidth = Dimensions.get("window").width / 2; const panGesture = Gesture.Pan() .onUpdate((event) => { const divisor = 5000; + const dragIsNotInHeaderOrFooter = event.y < 100 || event.y > 400; + if (dragIsNotInHeaderOrFooter) return; + if (event.x > screenHalfWidth) { const change = -event.translationY / divisor; - const newVolume = Math.max(0, Math.min(1, currentVolume + change)); + const newVolume = Math.max( + 0, + Math.min(1, currentVolume.value + change), + ); runOnJS(handleVolumeChange)(newVolume); } else { const change = -event.translationY / divisor; @@ -120,6 +126,7 @@ const VideoPlayer: React.FC = ({ data }) => { } }) .onEnd(() => { + runOnJS(setShowVolumeOverlay)(false); runOnJS(setShowBrightnessOverlay)(false); }); @@ -211,7 +218,7 @@ const VideoPlayer: React.FC = ({ data }) => { source={videoSrc} shouldPlay={shouldPlay} resizeMode={resizeMode} - volume={currentVolume} + volume={currentVolume.value} onLoadStart={onVideoLoadStart} onReadyForDisplay={onReadyForDisplay} onPlaybackStatusUpdate={setStatus} @@ -224,9 +231,7 @@ const VideoPlayer: React.FC = ({ data }) => { )} {showVolumeOverlay && ( - - Volume: {Math.round(currentVolume * 100)}% - + Volume: {debouncedVolume} )} {showBrightnessOverlay && ( diff --git a/apps/expo/src/components/player/ControlsOverlay.tsx b/apps/expo/src/components/player/ControlsOverlay.tsx index 0350653..70d22d1 100644 --- a/apps/expo/src/components/player/ControlsOverlay.tsx +++ b/apps/expo/src/components/player/ControlsOverlay.tsx @@ -18,12 +18,12 @@ export const ControlsOverlay = ({ headerData }: ControlsOverlayProps) => { setIsIdle(!idle); }; return ( - - -
+ +
+ - - - + + + ); }; diff --git a/apps/expo/src/components/player/MiddleControls.tsx b/apps/expo/src/components/player/MiddleControls.tsx index a1ba53a..1737429 100644 --- a/apps/expo/src/components/player/MiddleControls.tsx +++ b/apps/expo/src/components/player/MiddleControls.tsx @@ -1,4 +1,4 @@ -import { View } from "react-native"; +import { StyleSheet, View } from "react-native"; import { Controls } from "./Controls"; import { PlayButton } from "./PlayButton"; @@ -6,8 +6,8 @@ import { SeekButton } from "./SeekButton"; export const MiddleControls = () => { return ( - - + + @@ -19,3 +19,13 @@ export const MiddleControls = () => { ); }; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 82, + }, +}); diff --git a/apps/expo/src/components/player/ProgressBar.tsx b/apps/expo/src/components/player/ProgressBar.tsx index 1eb24b2..d532bbd 100644 --- a/apps/expo/src/components/player/ProgressBar.tsx +++ b/apps/expo/src/components/player/ProgressBar.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { View } from "react-native"; +import { TouchableOpacity } from "react-native"; import { usePlayerStore } from "~/stores/player/store"; import VideoSlider from "./VideoSlider"; @@ -7,6 +7,7 @@ import VideoSlider from "./VideoSlider"; export const ProgressBar = () => { const status = usePlayerStore((state) => state.status); const videoRef = usePlayerStore((state) => state.videoRef); + const setIsIdle = usePlayerStore((state) => state.setIsIdle); const updateProgress = useCallback( (newProgress: number) => { @@ -19,9 +20,12 @@ export const ProgressBar = () => { if (status?.isLoaded) { return ( - + setIsIdle(false)} + > - + ); } }; diff --git a/apps/expo/src/components/player/VideoSlider.tsx b/apps/expo/src/components/player/VideoSlider.tsx index abd3ccd..3f0becc 100644 --- a/apps/expo/src/components/player/VideoSlider.tsx +++ b/apps/expo/src/components/player/VideoSlider.tsx @@ -31,6 +31,8 @@ interface VideoSliderProps { } const VideoSlider = ({ onSlidingComplete }: VideoSliderProps) => { + const tapRef = useRef(null); + const panRef = useRef(null); const status = usePlayerStore((state) => state.status); const width = Dimensions.get("screen").width - 200; @@ -52,9 +54,6 @@ const VideoSlider = ({ onSlidingComplete }: VideoSliderProps) => { const valueX = valueToX(value); const translateX = useSharedValue(valueToX(value)); - const tapRef = useRef(null); - const panRef = useRef(null); - useEffect(() => { translateX.value = clamp(valueX, 0, width - knobSize_); }, [valueX]); diff --git a/apps/expo/src/hooks/player/useVolume.ts b/apps/expo/src/hooks/player/useVolume.ts new file mode 100644 index 0000000..42e5661 --- /dev/null +++ b/apps/expo/src/hooks/player/useVolume.ts @@ -0,0 +1,24 @@ +import { useCallback, useState } from "react"; +import { useSharedValue } from "react-native-reanimated"; + +import { useDebounce } from "../useDebounce"; + +export const useVolume = () => { + const [showVolumeOverlay, setShowVolumeOverlay] = useState(false); + const debouncedShowVolumeOverlay = useDebounce(showVolumeOverlay, 20); + const volume = useSharedValue(1); + const debouncedVolume = useDebounce(volume.value, 20); + + const handleVolumeChange = useCallback((newValue: number) => { + volume.value = newValue; + setShowVolumeOverlay(true); + }, []); + + return { + showVolumeOverlay: debouncedShowVolumeOverlay, + currentVolume: volume, + debouncedVolume: `${Math.round(debouncedVolume * 100)}%`, + setShowVolumeOverlay, + handleVolumeChange, + } as const; +};