javascriptreactjsreact-hooksuse-ref

ReactJS: How to play video which comes in center and pause all other videos


In maplist I have 20 items that is 20 videos I want to do is play video which comes in center of viewport and pause all others and keep that behaviour throughout scrolling:

const [reels, setReels] = useState([]);
useEffect(() => {
  fetchAllVideos();
}, []);

const fetchAllVideos = async () => {
  const getAllVideosRes = await getAllVideos(pageNumber);
  setReels(getAllVideosRes);
};

return (
  <>
  reels.map((item, idx) => {
  return (
    <MDBox mb={3} key={idx}>
      <Card sx={{ borderRadius: "25px" }}>
        <video style={{ height: "100%", width: "100%", objectFit: "cover", borderRadius: "25px", }} loop controls>
          <source src={item.photo} type="video/mp4" />
        </video>
    </MDBox>
  </Card>
  </MDBox >
  );
  })

</>
)

Solution

  • Use an intersectionObserver to track the items as they enter or leave the container.

    Set the threshold to be 100% of the element (threshold: 1), and the rootMargin to limit the intersection area to the center of the container (rootMargin: '-20% 0% -20% 0%').

    const { useRef, useState, useEffect, useCallback } = React
    
    const usePlayIntersection = () => { 
      const containerRef = useRef();
      const [observer, setObserver] = useState();
    
      useEffect(() => {
        const cb = entries => {
          entries.forEach(({ isIntersecting, target, intersectionRect }) => {
            if(isIntersecting) target.play()
            else target.pause()
          })
        };
      
        const obs = new IntersectionObserver(cb, {
          root: containerRef.current,
          rootMargin: '-20% 0% -20% 0%',
          threshold: 1
        })
        
        setObserver(obs)
      
        return () => {
          obs.disconnect()
        }
      }, [])
      
      const observe = useCallback(el => {
        if(observer) observer.observe(el)
      }, [observer])
      
      return [observe, containerRef]
    }
    
    const Demo = ({ videos }) => {
      const [observe, containerRef] = usePlayIntersection();
      
      return (
        <div className="container" ref={containerRef}>
          <div className="filler" />
          {videos.map(vid => (
            <video 
              ref={observe}
              muted
              key={vid}
              loop controls>
            <source src={vid} type="video/mp4" />
          </video>
          ))}
          <div className="filler" />
        </div>
      )
    }
    
    const videos = ["http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4","http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4","http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4","http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4","http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4"]
    
    ReactDOM
      .createRoot(root)
      .render(<Demo videos={videos} />)
    html, body {
      margin: 0;
      padding: 0;
    }
    
    .container {
      height: 100vh;
      overflow: auto;
    }
    
    video, .filler {
      display: block;
      margin: 1em;
      height: 40%; 
      width: 40%;
      objectFit: cover;
      borderRadius: 25px;
    }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <div id="root"></div>