import React from "react"; import gsap from "gsap"; import { useGSAP } from "@gsap/react"; import { ScrollTrigger } from "gsap/all"; import { useEffect, useRef, useState } from "react"; import { hightlightsSlides } from "../utils"; import { pauseImg, playImg, replayImg } from "../utils"; gsap.registerPlugin(ScrollTrigger); const VideoCarousel = () => { // Using refs to store DOM elements for videos, spans (progress bars), and divs (containers for progress bars). const videoRef = useRef([]); // Ref for video elements to manage playback. const videoSpanRef = useRef([]); // Ref for spans that represent the progress bar of each video. const videoDivRef = useRef([]); // Ref for containers that hold the progress bars. // Defining the state for video-related properties. const [video, setVideo] = useState({ isEnd: false, // Whether the current video has ended. startPlay: false, // Whether the video has started playing. videoId: 0, // The current video ID or index in the carousel. isLastVideo: false, // Whether the last video in the carousel is playing. isPlaying: false, // Whether the video is currently playing or paused. }); // State to store metadata of loaded videos (used to ensure videos are ready for playback). const [loadedData, setLoadedData] = useState([]); // Destructuring the video state for easier access in the component. const { isEnd, isLastVideo, startPlay, videoId, isPlaying } = video; // Setting up GSAP animation for the video slider. useGSAP(() => { // Animating the video slider to move left or right based on the current video ID. gsap.to('#slider', { transform: `translateX(${-100 * videoId}%)`, // Slide the container based on the current video index. duration: 2, // Duration of the animation in seconds. ease: 'power2.inOut', // Easing function for smooth sliding effect. }); // Animating the video element itself. gsap.to('#video', { scrollTrigger: { trigger: '#video', // The animation is triggered when the video element comes into view during scroll. toggleActions: 'restart none none none', // The video restarts when the user scrolls back. }, onComplete: () => { // Once the animation completes, set the video state to playing. setVideo((prevVideo) => ({ ...prevVideo, startPlay: true, // Start the video playback. isPlaying: true, // Set the playing state to true. })); }, }); }, [isEnd, videoId]); // The animation depends on whether the video ended or the video ID changed. // Effect to handle the video play/pause logic based on user interaction or video state. useEffect(() => { if (loadedData.length > 3) { // Ensure the metadata of all videos is loaded before proceeding. if (!isPlaying) { videoRef.current[videoId].pause(); // Pause the video if `isPlaying` is false. } else { startPlay && videoRef.current[videoId].play(); // Play the video if `isPlaying` is true and it is marked to start. } } }, [startPlay, videoId, isPlaying, loadedData]); // This effect depends on the play state, current video, and loaded video data. // Function to store video metadata (such as duration) when a video loads. const handleLoadedMetadata = (i, e) => setLoadedData((prevVideo) => [...prevVideo, e]); // Effect to animate and update the progress bar of the current video. useEffect(() => { let currentProgress = 0; // Keep track of the current progress percentage. let anim; // Animation instance. const animUpdate = () => { if (videoRef.current[videoId]) { // Safeguard to ensure videoRef.current[videoId] exists anim?.progress( videoRef.current[videoId].currentTime / hightlightsSlides[videoId].videoDuration ); } }; if (videoSpanRef.current[videoId]) { anim = gsap.to(videoSpanRef.current[videoId], { onUpdate: () => { if (videoRef.current[videoId]) { const progress = Math.ceil( anim.progress() * 100 ); // Calculate the progress percentage. if (progress !== currentProgress) { currentProgress = progress; // Adjust the width of the progress bar container based on screen size. gsap.to(videoDivRef.current[videoId], { width: window.innerWidth < 760 ? '10vw' : window.innerWidth < 1200 ? '10vw' : '4vw', }); // Update the progress bar width and color dynamically. gsap.to(videoSpanRef.current[videoId], { width: `${currentProgress}%`, backgroundColor: 'white', }); } } }, onComplete: () => { if (isPlaying) { gsap.to(videoDivRef.current[videoId], { width: '12px', }); gsap.to(videoSpanRef.current[videoId], { background: '#afafaf', }); } }, }); if (videoId === 0) { anim.restart(); // Restart the animation if it's the first video. } if (isPlaying) { gsap.ticker.add(animUpdate); // Add the progress update function to GSAP's ticker (runs at every frame). } } return () => { // Cleanup function to remove the ticker and any ongoing animations. gsap.ticker.remove(animUpdate); anim?.kill(); // Kill the animation to prevent memory leaks. }; }, [videoId, startPlay, isPlaying]); // This effect depends on the video ID and whether the video started playing. // Function to handle different events in the video (end, last video, reset, play, pause). const handleProcess = (type, i) => { switch (type) { case 'video-end': setVideo((prevVideo) => ({ ...prevVideo, isEnd: true, videoId: i + 1 })); // Move to the next video when the current video ends. break; case 'video-last': setVideo((prevVideo) => ({ ...prevVideo, isLastVideo: true })); // Set the flag to indicate the last video has finished. break; case 'video-reset': setVideo((prevVideo) => ({ ...prevVideo, isLastVideo: false, videoId: 0 })); // Reset the video carousel to the first video. break; case 'play': setVideo((prevVideo) => ({ ...prevVideo, isPlaying: !prevVideo.isPlaying })); // Toggle play/pause state. break; case 'pause': setVideo((prevVideo) => ({ ...prevVideo, isPlaying: !prevVideo.isPlaying })); // Toggle pause/play state. break; default: return video; // Default case does nothing, just returns the current state. } }; // JSX to render the video carousel and controls. return ( <>
{text}
))}