skip to Main Content

I got a React project where I’m using a slider to move between the points, which are basically steps, and depending on which step is chosen it plays the part of the video.

Functionality is there, but I’m having a problem when I go to the previous step (like if you go from 8 to 7), and video not play as I want.

What I want to have is that, let say you are in Display step and you click LED, it should play the video till the idle point of LED and stop, and if you click Display again, it should continue playing LED frame till end frame of it and then go back to Display.

I’m using video-react for this.

So,

I’m calling VideoPlayer component in main component as

  <div className={styles.content}>
                <VideoPlayer currentComponent={selectedComponent} prevComponent={prevComponent} startTime={frames.find((item) => item.id === selectedComponent).start} endTime={frames.find((item) => item.id === selectedComponent).end} idleTime={frames.find((item) => item.id === selectedComponent).idle} />
            </div>

and frames array is like this:

[
...

            {
            id: 2,
            title: 'Display',
            start: 221 / 30,
            idle: 266 / 30,
            end: 312 / 30,
        },
        {
            id: 3,
            title: 'Status LED',
            start: 313 / 30,
            idle: 358 / 30,
            end: 403 / 30,
        },
...
]

selectedComponent is just a value between 1 and 8 depending on which step is chosen in slider.

and my VideoPlayer component is like following:

Updated: made the reverse functionality, but still having problem to stop the video if currentTime>=idleTime when I go to step 2 from step 3.

import React, { useState, useRef, useEffect } from 'react';
import { Player, ControlBar } from 'video-react';

const VideoPlayer = ({ frames, currentComponent, prevComponent, startTime, endTime, idleTime }) => {
    const [currentTime, setCurrentTime] = useState(startTime);
    const playerRef = useRef(null);
    const [isPlaying, setIsPlaying] = useState(true);
    useEffect(() => {
        play();
        seek(startTime);
    }, [startTime]);

    useEffect(() => {
        if (currentTime >= idleTime) {
            pause();
            setIsPlaying(false);
        }
    }, [currentTime]);

    const play = () => {
        playerRef.current.play();
    };

    useEffect(() => {
        if (currentComponent < prevComponent) {
            setIsPlaying(true);
            playUntil(frames.find(item => item.id === prevComponent).end);
        }
    }, [currentTime]);

    const playUntil = (time) => { //endTime of previous component
        console.log(time, currentTime);
        console.log(idleTime);

        if (time > currentTime && currentTime <= idleTime && isPlaying) {
            playerRef.current.play();
        } else {
            //pause();
            playerRef.current.play();
            if (currentTime >= idleTime) {
                // pause();
                setIsPlaying(false);
            }
        }
        if (time <= currentTime) {
            pause();
            playerRef.current.play();
            playerRef.current.seek(startTime);
        }
    };


    const pause = () => {
        playerRef.current.pause();
    };

    const seek = (seconds) => () => {
        playerRef.current.seek(seconds);
    };
    return (
        <Player
            ref={playerRef}
            autoPlay
            startTime={0}
            muted={true}
            onTimeUpdate={(e) => setCurrentTime(parseFloat(e.target.currentTime))}
        >
            <source src="./video.mp4" style={{ height: '100vh' }} />
            <ControlBar autoHide={false} disableCompletely={true} disableDefaultControls={true} />
        </Player>
    );
};

export default VideoPlayer;

So, to conclude, I’m trying to have a functionality that depending on which selectedComponent is selected, it should run video till idle and if user clicks next one, it should continue playing (that functionality is there), if user clicks previous one, it should play till end frame of the current component and then use seek() function to go to the start point of previous frame.

I’m also sharing CodeSandbox link for the reference.

2

Answers


  1. Implementing this functionality requires managing the video playback and seeking behavior in response to the user’s interaction with the slider. Here’s an approach you can take:

    1. Store references to your Player component using useRef.
    2. Use useEffect to subscribe to state changes of the Player and to seek the video when the selected step changes.
    3. Control the video playback based on the current and previous steps, playing until the end of the current step when going backwards, or until the idle point when moving forward.

    Here’s an updated version of your VideoPlayer component that should meet your requirements:

    import React, { useState, useRef, useEffect } from 'react';
    import { Player } from 'video-react';
    
    const VideoPlayer = ({
      currentComponent,
      prevComponent,
      startTime,
      endTime,
      idleTime
    }) => {
      const playerRef = useRef(null);
      const [playerState, setPlayerState] = useState({
        isPlaying: false,
        hasPlayed: false,
      });
    
      useEffect(() => {
        const handleStateChange = (state) => {
          // Update local state based on the player's state
          setPlayerState({
            isPlaying: !state.paused,
            hasPlayed: state.hasStarted,
          });
        };
        // Subscribe to state changes
        const unsubscribe = playerRef.current.subscribeToStateChange(handleStateChange);
        return () => {
          unsubscribe();
        };
      }, []);
    
      useEffect(() => {
        if (currentComponent !== prevComponent) {
          if (currentComponent < prevComponent) {
            // If moving backwards, play until the end of the current component
            playerRef.current.play();
          } else {
            // If moving forwards, play until the idle time of the new component
            seekTo(startTime);
            playerRef.current.play();
          }
        }
      }, [currentComponent, prevComponent, startTime, endTime, idleTime]);
    
      const seekTo = (time) => {
        playerRef.current.seek(time);
      };
    
      useEffect(() => {
        if (playerState.isPlaying && playerRef.current) {
          const checkTime = () => {
            const currentTime = playerRef.current.getState().player.currentTime;
            if (currentComponent < prevComponent && currentTime >= endTime) {
              playerRef.current.pause();
              seekTo(startTime);
            } else if (currentComponent > prevComponent && currentTime >= idleTime) {
              playerRef.current.pause();
            }
          };
          const timeCheckInterval = setInterval(checkTime, 100);
    
          return () => clearInterval(timeCheckInterval);
        }
      }, [playerState.isPlaying, currentComponent, prevComponent, idleTime, endTime, startTime]);
    
      return (
        <Player
          ref={playerRef}
          // ...other props you might want to pass, like src for the video
        >
          {/* ...other components like ControlBar if needed */}
        </Player>
      );
    };
    
    export default VideoPlayer;
    

    This VideoPlayer component does the following:

    • It plays the video when a new component is selected, either from the start time if moving forward or until the end time if moving backward.
    • It sets up an interval that checks the current time of the video every 100 milliseconds and pauses the video at the correct point based on whether the user is navigating forwards or backwards.

    Adjust the useEffect dependencies and logic as necessary for your specific use case, as this is a starting point and might require fine-tuning. Also, make sure that prevComponent state is being updated correctly in the parent component whenever a new component is selected.

    Login or Signup to reply.
  2. To address the issue you’ve described, you need to modify the component’s behavior to continue playing the video until it reaches the endTime of the prevComponent before seeking to the startTime of the currentComponent. The following changes to the VideoPlayer component should help you achieve the desired functionality:

    import React, { useState, useRef, useEffect } from 'react';
    import { Player } from 'video-react';
    
    const VideoPlayer = ({
      currentComponent,
      prevComponent,
      startTime,
      endTime,
      idleTime
    }) => {
      const playerRef = useRef(null);
      
      // Updated logic to handle playing video till the end of the previous step
      useEffect(() => {
        const playTillEndThenSeek = () => {
          if (currentComponent < prevComponent) {
            // Set up a check to see if video has played till the endTime of prevComponent
            const intervalId = setInterval(() => {
              const currentTime = playerRef.current.getState().player.currentTime;
              if (currentTime >= endTime) {
                clearInterval(intervalId);
                playerRef.current.pause();
                playerRef.current.seek(startTime); // Seek to the start time of current component
              }
            }, 100); // Check every 100ms
          } else {
            playerRef.current.seek(startTime);
            playerRef.current.play();
          }
        };
    
        playTillEndThenSeek();
    
        // Cleanup the interval on unmount
        return () => {
          clearInterval(intervalId);
        };
      }, [currentComponent, prevComponent, startTime, endTime]);
    
      // ...rest of your component
    };
    

    In this updated version, when the user navigates back (i.e., the currentComponent is less than the prevComponent), the component sets up an interval to monitor the playback position. Once the playback reaches the endTime of the previous step, it will clear the interval, pause the video, and seek to the startTime of the currentComponent.

    Ensure you have the right endTime for the prevComponent. This might involve passing the endTime of the prevComponent into the VideoPlayer component, which is not currently accounted for in your existing props.

    The interval is cleared when the endTime is reached to avoid unnecessary checks and when the component is unmounted to prevent memory leaks. Adjust the interval duration (currently 100ms) based on how accurately you want to stop the video.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search