I am trying to create custom video controls for my react native app but it seems like I am missing something. Currently only the pause/play functionality work. The fast forward/ rewinding and slider don't work. Here is the code for the pause and play functionality that works.
const togglePlayPause = async () => {
if (videoRef.current) {
if (isPlaying) {
await videoRef.current.pauseAsync();
} else {
await videoRef.current.playAsync();
}
setIsPlaying(!isPlaying);
}
};
<View style={styles.controls}>
<TouchableOpacity onPress={togglePlayPause} style={styles.controlButton}>
{isPlaying?
<FontAwesome5 name="pause" size={30} color="white" />
:
<FontAwesome5 name="play" size={30} color="white" />
}
</TouchableOpacity>
</View>
I think I did the right thing but it's not working so probably not. Could someone tell me what I am doing wrong? This is the rest of the code including the pause and play functionally I mentioned earlier
import { View, Text,Dimensions, ImageBackground,StyleSheet, StatusBar, FlatList,Image, TouchableOpacity } from 'react-native'
import { TapGestureHandler, State } from 'react-native-gesture-handler';
import { FontAwesome5 } from '@expo/vector-icons';
import Slider from '@react-native-community/slider';
import React,{useState,useEffect,useRef} from 'react'
import EchoIcon from './Echo';
import ThumbsDownIcon from './ThumbsDown';
import ThumbsUpIcon from './ThumbsUp';
import { Video,ResizeMode } from 'expo-av';
import { supabase } from '../../../../supabase1';
import CommentThumbsUp from '../../../../animations/CommentThumbsUp';
import CommentThumbsDown from '../../../../animations/CommentThumbsDown';
export default function ViewPostMedia({navigation,route}) {
const screenWidth = Dimensions.get('window').width;
const SCREEN_HEIGHT = (Dimensions.get('window').height)
const {media} =route.params;
const {postID} = route.params;
const {initialLikes} = route.params
const {initialDislikes} = route.params
const {initialEchoes} = route.params
const {mediaType} = route.params
const [Comments, setComments] = useState([])
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [sliderValue, setSliderValue] = useState(0);
const [videoDuration, setVideoDuration] = useState(0);
const [sliderBeingDragged, setSliderBeingDragged] = useState(false);
const [doubleTapRight, setDoubleTapRight] = useState(false);
const [doubleTapLeft, setDoubleTapLeft] = useState(false);
useEffect(() => {
const getVideoDuration = async () => {
if (videoRef.current) {
const { durationMillis } = await videoRef.current.getStatusAsync();
console.log('Video duration:', durationMillis / 1000);
setVideoDuration(durationMillis / 1000);
}
};
getVideoDuration();
}, [videoRef.current]);
// Update slider position based on video progress
useEffect(() => {
const updateSliderPosition = () => {
if (videoRef.current) {
videoRef.current.getStatusAsync().then((status) => {
const { positionMillis, durationMillis } = status;
const currentPosition = positionMillis / 1000;
const progress = currentPosition / (durationMillis / 1000);
setSliderValue(progress);
});
}
};
const intervalId = setInterval(updateSliderPosition, 1000); // Update every second
return () => clearInterval(intervalId); // Clean up interval
}, [videoRef.current]);
const onSlidingStart = () => {
setSliderBeingDragged(true);
};
const onSlidingComplete = async (value) => {
setSliderBeingDragged(false);
setSliderValue(value);
const newPosition = value * videoDuration;
await videoRef.current.setPositionAsync(newPosition);
};
// Function to handle slider value change
const onSliderValueChange = (value) => {
setSliderValue(value);
};
const togglePlayPause = async () => {
if (videoRef.current) {
if (isPlaying) {
await videoRef.current.pauseAsync();
} else {
await videoRef.current.playAsync();
}
setIsPlaying(!isPlaying);
}
};
useEffect(() => {
const handleDoubleTap = async () => {
if (doubleTapRight) {
// Move video 5 seconds ahead
const newPosition = Math.min(videoDuration, await videoRef.current.getStatusAsync().then((status) => {
const { positionMillis } = status;
return (positionMillis / 1000) + 5; // Convert to seconds
}));
console.log("New position to the right:",newPosition)
await videoRef.current.setPositionAsync(newPosition);
console.log('Position set successfully.',newPosition);
await videoRef.current.playAsync();
console.log('Video playback started.');
} else if (doubleTapLeft) {
// Move video 5 seconds behind
const newPosition = Math.max(0, await videoRef.current.getStatusAsync().then((status) => {
const { positionMillis } = status;
return (positionMillis / 1000) - 5; // Convert to seconds
}));
console.log("New position to the left:",newPosition)
await videoRef.current.setPositionAsync(newPosition);
console.log('Position set successfully.',newPosition);
await videoRef.current.playAsync();
console.log('Video playback started.');
}
};
handleDoubleTap();
}, [doubleTapRight, doubleTapLeft]);
useEffect(() => {
fetchComments();
}, [postID]);
const formatTimestamp = (timestamp) => {
const currentDate = new Date();
const postDate = new Date(timestamp);
const timeDifference = currentDate - postDate;
const secondsDifference = Math.floor(timeDifference / 1000);
const minutesDifference = Math.floor(secondsDifference / 60);
const hoursDifference = Math.floor(minutesDifference / 60);
const daysDifference = Math.floor(hoursDifference / 24);
if (secondsDifference < 60) {
return `${secondsDifference}s ago`;
} else if (minutesDifference < 60) {
return `${minutesDifference}m ago`;
} else if (hoursDifference < 24) {
return `${hoursDifference}h ago`;
} else {
return `${daysDifference}d ago`;
}
};
const fetchComments = async () => {
try {
const { data: commentsData, error: commentsError } = await supabase
.from('comments')
.select('*')
.eq('post_id', postID);
if (commentsError) {
console.error('Error fetching comments:', commentsError);
return;
}
// Fetch user details for each comment
const commentsWithUserDetails = await Promise.all(
commentsData.map(async (comment) => {
const { data: userData, error: userError } = await supabase
.from('profiles')
.select('*')
.eq('id', comment.user_id)
.single();
if (userError) {
console.error(`Error fetching user details for comment ${comment.comment_id}:`, userError);
return comment;
}
return {
...comment,
userDetails: userData || {},
};
})
);
setComments(commentsWithUserDetails || []);
} catch (error) {
console.error('Error in fetchComments function:', error);
}
};
const renderItem = ({ item: comment }) => (
<View style={{borderTopWidth:1, borderTopColor:'#B3B3B3'}}>
<View style={styles.commentcontainer}>
<View style={styles.profileanduser}>
<View style={styles.postprofilepicturecontainer}>
<Image style={styles.postprofilepicture} source={{ uri: comment.userDetails?.profile_picture_url }} />
</View>
<Text style={styles.usernameposttext}>@{comment.userDetails?.username}</Text>
<Text style={styles.timetext}>{formatTimestamp(comment.created_at)}</Text>
</View>
<View style={styles.commentviewtextcontainer}>
<Text style={styles.commentsviewtext}>{comment.text}</Text>
<View style={styles.commentreactioncontainer}>
<CommentThumbsUp postId={comment.comment_id} initialLikes={comment.likes} />
<Text style={styles.userpostreactionstext}>{comment.likes}</Text>
<CommentThumbsDown postID={comment.comment_id} initialDislikes={comment.dislikes} />
<Text style={styles.userpostreactionstext}>{comment.dislikes}</Text>
</View>
</View>
</View>
</View>
);
return (
<View >
<StatusBar backgroundColor={'transparent'}/>
{ mediaType === 'image' ?
<ImageBackground style={{width:screenWidth,height:SCREEN_HEIGHT}} source={{uri:media}}>
<View style={styles.bottomTab}>
<ThumbsUpIcon postID={postID} initialLikes={initialLikes} />
<ThumbsDownIcon postID={postID} initialDislikes={initialDislikes} />
<EchoIcon postID={postID} initialEchoes={initialEchoes}/>
</View>
</ImageBackground>
:
<View>
<View style={styles.mediacontainer}>
<Video
ref={videoRef}
style={styles.media}
source={{uri:media}}
useNativeControls={false}
resizeMode={ResizeMode.COVER}
onPlaybackStatusUpdate={(status) => {
setIsPlaying(status.isPlaying);
}}
isLooping={true}
/>
</View>
<View style={styles.controls}>
<TouchableOpacity onPress={togglePlayPause} style={styles.controlButton}>
{isPlaying?
<FontAwesome5 name="pause" size={30} color="white" />
:
<FontAwesome5 name="play" size={30} color="white" />
}
</TouchableOpacity>
</View>
<View style={styles.sliderContainer}>
<Slider
style={{ width: 300, height: 40 }}s
minimumValue={0}
maximumValue={1}
value={sliderValue}
onValueChange={onSliderValueChange}
onSlidingStart={onSlidingStart}
onSlidingComplete={onSlidingComplete}
minimumTrackTintColor="#784EF8"
maximumTrackTintColor="white"
thumbTintColor="#784EF8"
/>
</View>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
setDoubleTapRight(true);
setTimeout(() => {
setDoubleTapRight(false);
}, 300); // Adjust timeout as needed
}
}}
numberOfTaps={2}
maxDelayMs={300}
>
<View style={styles.rightDoubleTapArea}></View>
</TapGestureHandler>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
setDoubleTapLeft(true);
setTimeout(() => {
setDoubleTapLeft(false);
}, 300); // Adjust timeout as needed
}
}}
numberOfTaps={2}
maxDelayMs={300}
>
<View style={styles.leftDoubleTapArea}></View>
</TapGestureHandler>
<View >
<FlatList
data={Comments}
keyExtractor={(comment) => comment.comment_id.toString()}
renderItem={renderItem}
/>
</View>
</View>
}
</View>
)
}