add buggy videoslider

This commit is contained in:
Jorrin
2024-02-14 14:36:56 +01:00
parent a4f4f6822d
commit c140fa885b
3 changed files with 174 additions and 61 deletions

View File

@@ -8,13 +8,12 @@ import { mapMillisecondsToTime } from "./utils";
export const BottomControls = () => { export const BottomControls = () => {
const status = usePlayerStore((state) => state.status); const status = usePlayerStore((state) => state.status);
status?.isLoaded;
if (status?.isLoaded) { if (status?.isLoaded) {
return ( return (
<Controls> <Controls>
<View className="flex h-16 w-full flex-row items-center justify-center"> <View className="flex h-16 w-full flex-row items-center justify-center">
<View className="flex flex-row items-center justify-center gap-5 px-4 py-2"> <View className="flex flex-row items-center justify-center px-4 py-2">
<Text className="font-bold"> <Text className="font-bold">
{mapMillisecondsToTime(status.positionMillis ?? 0)} {mapMillisecondsToTime(status.positionMillis ?? 0)}
</Text> </Text>

View File

@@ -1,79 +1,26 @@
import { useCallback, useRef } from "react"; import { useCallback } from "react";
import { Dimensions, PanResponder, TouchableOpacity, View } from "react-native"; import { View } from "react-native";
import { usePlayerStore } from "~/stores/player/store"; import { usePlayerStore } from "~/stores/player/store";
import VideoSlider from "./VideoSlider";
export const ProgressBar = () => { export const ProgressBar = () => {
const status = usePlayerStore((state) => state.status); const status = usePlayerStore((state) => state.status);
const videoRef = usePlayerStore((state) => state.videoRef); const videoRef = usePlayerStore((state) => state.videoRef);
const screenWidth = Dimensions.get("window").width;
const progressBarWidth = screenWidth - 40; // Adjust the padding as needed
const updateProgress = useCallback( const updateProgress = useCallback(
(newProgress: number) => { (newProgress: number) => {
videoRef?.setStatusAsync({ positionMillis: newProgress }).catch(() => { videoRef?.setStatusAsync({ positionMillis: newProgress }).catch(() => {
console.log("Error updating progress"); console.error("Error updating progress");
}); });
}, },
[videoRef], [videoRef],
); );
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: (e, gestureState) => {
console.log(gestureState.moveX, gestureState.x0, gestureState.dx);
},
onPanResponderRelease: (e, gestureState) => {
console.log("onPanResponderRelease");
const { moveX, x0 } = gestureState;
const newProgress = (moveX - x0) / progressBarWidth;
updateProgress(newProgress);
},
}),
).current;
if (status?.isLoaded) { if (status?.isLoaded) {
const progressRatio =
status.durationMillis && status.durationMillis !== 0
? status.positionMillis / status.durationMillis
: 0;
return ( return (
<View className="flex h-8 flex-1 items-center justify-center"> <View className="flex h-10 flex-1 items-center justify-center p-8">
{/* Progress Dot */} <VideoSlider onSlidingComplete={updateProgress} />
<View className="absolute inset-x-0 top-0">
<View
className="z-10 h-4 w-4 rounded-full bg-primary-100"
style={{
left: `${progressRatio * 100}%`,
transform: [
{ translateY: 7 },
{
translateX: -4,
},
],
}}
/>
</View>
{/* Full bar */}
<TouchableOpacity
className="relative h-1 w-full rounded-full bg-secondary-300 bg-opacity-25 transition-[height] duration-100"
{...panResponder.panHandlers}
>
{/* TODO: Preloaded */}
<View className="absolute left-0 top-0 h-full rounded-full bg-secondary-300" />
{/* Progress */}
<View
className="dir-neutral:left-0 absolute top-0 flex h-full items-center justify-end rounded-full bg-primary-100"
style={{
width: `${progressRatio * 100}%`,
}}
/>
</TouchableOpacity>
</View> </View>
); );
} }

View File

@@ -0,0 +1,167 @@
import type {
HandlerStateChangeEvent,
PanGestureHandlerGestureEvent,
TapGestureHandlerEventPayload,
} from "react-native-gesture-handler";
import React, { useEffect, useRef } from "react";
import { Dimensions, StyleSheet, View } from "react-native";
import {
PanGestureHandler,
State,
TapGestureHandler,
} from "react-native-gesture-handler";
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated";
import colors from "@movie-web/tailwind-config/colors";
import { usePlayerStore } from "~/stores/player/store";
const clamp = (value: number, lowerBound: number, upperBound: number) => {
"worklet";
return Math.min(Math.max(lowerBound, value), upperBound);
};
interface VideoSliderProps {
onSlidingComplete?: (value: number) => void;
}
const VideoSlider = ({ onSlidingComplete }: VideoSliderProps) => {
const status = usePlayerStore((state) => state.status);
const width = Dimensions.get("screen").width - 140;
const knobSize_ = 20;
const trackSize_ = 8;
const minimumValue = 0;
const maximumValue = status?.isLoaded ? status.durationMillis! : 0;
const value = status?.isLoaded ? status.positionMillis : 0;
const valueToX = (v: number) => {
if (maximumValue === minimumValue) return 0;
return (width * (v - minimumValue)) / (maximumValue - minimumValue);
};
const xToValue = (x: number) => {
"worklet";
if (maximumValue === minimumValue) return minimumValue;
return (x / width) * (maximumValue - minimumValue) + minimumValue;
};
const valueX = valueToX(value);
const translateX = useSharedValue(valueToX(value));
const tapRef = useRef<TapGestureHandler>(null);
const panRef = useRef<PanGestureHandler>(null);
useEffect(() => {
translateX.value = clamp(valueX, 0, width - knobSize_);
}, [valueX]);
const _onSlidingComplete = (xValue: number) => {
"worklet";
if (onSlidingComplete) runOnJS(onSlidingComplete)(xToValue(xValue));
};
const _onActive = (value: number) => {
"worklet";
translateX.value = clamp(value, 0, width - knobSize_);
};
const onGestureEvent = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
{ offsetX: number }
>({
onStart: (_, ctx) => (ctx.offsetX = translateX.value),
onActive: (event, ctx) => _onActive(event.translationX + ctx.offsetX),
});
const onTapEvent = (
event: HandlerStateChangeEvent<TapGestureHandlerEventPayload>,
) => {
if (event.nativeEvent.state === State.ACTIVE) {
_onActive(event.nativeEvent.x);
_onSlidingComplete(event.nativeEvent.x);
}
};
const scrollTranslationStyle = useAnimatedStyle(() => {
return { transform: [{ translateX: translateX.value }] };
});
const progressStyle = useAnimatedStyle(() => {
return {
width: translateX.value + knobSize_,
};
});
return (
<TapGestureHandler
ref={tapRef}
onHandlerStateChange={onTapEvent}
simultaneousHandlers={panRef}
>
<View
style={[
{
alignItems: "center",
justifyContent: "center",
height: knobSize_,
width,
},
]}
>
<View
className="justify-center"
style={[
{
height: trackSize_,
borderRadius: trackSize_,
backgroundColor: colors.secondary[700],
width,
},
]}
>
<Animated.View
className="absolute bottom-0 left-0 right-0 top-0"
style={[
{
backgroundColor: colors.primary[300],
borderRadius: trackSize_ / 2,
},
progressStyle,
]}
/>
<PanGestureHandler
ref={panRef}
onGestureEvent={onGestureEvent}
simultaneousHandlers={tapRef}
>
<Animated.View
style={[
styles.knob,
{
height: knobSize_,
width: knobSize_,
borderRadius: knobSize_ / 2,
backgroundColor: colors.primary[300],
},
scrollTranslationStyle,
]}
/>
</PanGestureHandler>
</View>
</View>
</TapGestureHandler>
);
};
const styles = StyleSheet.create({
knob: {
justifyContent: "center",
alignItems: "center",
},
});
export default VideoSlider;