How to Record Video of a Dynamic Div Containing Multiple Media Elements in React Konva?

I’m working on a React application where I need to record a video of a specific div with the class name “layout.” This div contains multiple media elements (such as images and videos) that are dynamically rendered inside divisions. I’ve tried several approaches, including using MediaRecorder, canvas-based recording with html2canvas, RecordRTC, and even ffmpeg, but none seem to capture the entire div along with its dynamic content effectively.

What would be the best approach to achieve this? How can I record a video of this dynamically rendered div including all its media elements, ensuring a smooth capture of the transitions?

What I’ve Tried:
MediaRecorder API: Didn’t work effectively for capturing the entire div and its elements.
html2canvas: Captures snapshots but struggles with smooth transitions between media elements.
RecordRTC HTML Element Recording: Attempts to capture the canvas, but the output video size is 0 bytes.
CanvasRecorder, FFmpeg, and various other libraries also didn’t provide the desired result.

import React, { useEffect, useState, useRef } from "react";

const Preview = ({ layout, onClose }) => {
  const [currentContent, setCurrentContent] = useState([]);
  const totalDuration = useRef(0);
  const videoRefs = useRef([]); // Store refs to each video element
  const [totalTime, setTotalTime] = useState(0); // Add this line
  const [elapsedTime, setElapsedTime] = useState(0); // Track elapsed time in seconds

  // video recording variable and state declaration
  //  video recorder end
  // for video record useffect
  // Function to capture the renderDivision content

  const handleDownload = async () => {
    console.log("video downlaod function in developing mode.");
  };

  // end video record useffect

  // to apply motion and swtich in media of division start
  useEffect(() => {
    if (layout && layout.divisions) {
      const content = layout.divisions.map((division) => {
        let divisionDuration = 0;

        division.imageSrcs.forEach((src, index) => {
          const mediaDuration = division.durations[index]
            ? division.durations[index] * 1000 // Convert to milliseconds
            : 5000; // Fallback to 5 seconds if duration is missing
          divisionDuration += mediaDuration;
        });

        return {
          division,
          contentIndex: 0,
          divisionDuration,
        };
      });

      // Find the maximum duration
      const maxDuration = Math.max(...content.map((c) => c.divisionDuration));

      // Filter divisions that have the max duration
      const maxDurationDivisions = content.filter(
        (c) => c.divisionDuration === maxDuration
      );

      // Select the first one if there are multiple with the same max duration
      const selectedMaxDurationDivision = maxDurationDivisions[0];

      totalDuration.current = selectedMaxDurationDivision.divisionDuration; // Update the total duration in milliseconds

      setTotalTime(Math.floor(totalDuration.current / 1000000)); // Convert to seconds and set in state

      // console.log(
      //   "Division with max duration (including ties):",
      //   selectedMaxDurationDivision
      // );

      setCurrentContent(content);
    }
  }, [layout]);

  useEffect(() => {
    if (currentContent.length > 0) {
      const timers = currentContent.map(({ division, contentIndex }, i) => {
        const duration = division.durations[contentIndex]
          ? division.durations[contentIndex] // Duration is already in ms
          : 5000; // Default to 5000ms if no duration is defined

        const mediaElement = videoRefs.current[i];
        if (mediaElement && mediaElement.pause) {
          mediaElement.pause();
        }

        // Set up a timeout for each division to move to the next media after duration
        const timeoutId = setTimeout(() => {
          // Update content for each division independently
          updateContent(i, division, contentIndex, duration); // Move to the next content after duration

          // Ensure proper cleanup
          if (contentIndex + 1 >= division.imageSrcs.length) {
            clearTimeout(timeoutId); // Clear timeout to stop looping
          }
        }, duration);

        // Cleanup timers on component unmount
        return timeoutId;
      });

      // Return cleanup function to clear all timeouts
      return () => timers.forEach((timer) => clearTimeout(timer));
    }
  }, [currentContent]);
  // to apply motion and swtich in media of division end

  // Handle video updates when the duration is changed or a new video starts
  const updateContent = (i, division, contentIndex, duration) => {
    const newContent = [...currentContent];

    // Check if we are on the last media item
    if (contentIndex + 1 < division.imageSrcs.length) {
      // Move to next media if not the last one
      newContent[i].contentIndex = contentIndex + 1;
    } else {
      // If this is the last media item, pause here
      newContent[i].contentIndex = contentIndex; // Keep it at the last item
      setCurrentContent(newContent);

      // Handle video pause if the last media is a video
      const mediaElement = videoRefs.current[i];
      if (mediaElement && mediaElement.tagName === "VIDEO") {
        mediaElement.pause();
        mediaElement.currentTime = mediaElement.duration; // Pause at the end of the video
      }
      return; // Exit the function as we don't want to loop anymore
    }

    // Update state to trigger rendering of the next media
    setCurrentContent(newContent);

    // Handle video playback for the next media item
    const mediaElement = videoRefs.current[i];
    if (mediaElement) {
      mediaElement.pause();
      mediaElement.currentTime = 0;
      mediaElement
        .play()
        .catch((error) => console.error("Error playing video:", error));
    }
  };

  const renderDivision = (division, contentIndex, index) => {
    const mediaSrc = division.imageSrcs[contentIndex];

    if (!division || !division.imageSrcs || division.imageSrcs.length === 0) {
      return (
        <div
          style={{
            width: `${division.width}px`,
            height: `${division.height}px`,
            backgroundColor: division.color || "lightgray",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          <p>No media available</p>
        </div>
      );
    }

    if (!mediaSrc) {
      return (
        <div
          style={{
            width: `${division.width}px`,
            height: `${division.height}px`,
            backgroundColor: division.color || "lightgray",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          <p>No media available</p>
        </div>
      );
    }

    if (mediaSrc.endsWith(".mp4")) {
      return (
        <video
          crossOrigin="anonymous"
          ref={(el) => (videoRefs.current[index] = el)}
          src={mediaSrc}
          autoPlay
          controls={false}
          style={{
            width: "100%",
            height: "100%",
            objectFit: "cover",
            pointerEvents: "none",
          }}
          onLoadedData={() => {
            // Ensure video is properly loaded
            const mediaElement = videoRefs.current[index];
            if (mediaElement && mediaElement.readyState >= 3) {
              mediaElement.play().catch((error) => {
                console.error("Error attempting to play the video:", error);
              });
            }
          }}
        />
      );
    } else {
      return (
        <img
          crossOrigin="anonymous"
          src={mediaSrc}
          alt="content"
          style={{
            width: `${division.width}px`,
            height: `${division.height}px`,
            objectFit: "cover",
          }}
        />
      );
    }
  };

  // progress bar code start
  useEffect(() => {
    if (totalDuration.current > 0) {
      // Reset elapsed time at the start
      setElapsedTime(0);

      const interval = setInterval(() => {
        setElapsedTime((prevTime) => {
          // Increment the elapsed time by 1 second if it's less than the total time
          if (prevTime < totalTime) {
            return prevTime + 1;
          } else {
            clearInterval(interval); // Clear the interval when totalTime is reached
            return prevTime;
          }
        });
      }, 1000); // Update every second

      // Clean up the interval on component unmount
      return () => clearInterval(interval);
    }
  }, [totalTime]);

  // progress bar code end

  return (
    <div
      style={{
        position: "fixed",
        top: 0,
        left: 0,
        width: "100%",
        height: "100%",
        backgroundColor: "rgba(0, 0, 0, 0.5)",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        zIndex: 1000,
      }}
    >
      <div
        style={{
          backgroundColor: "white",
          padding: "20px",
          position: "relative",
        }}
      >
        <button
          onClick={onClose}
          style={{
            position: "absolute",
            top: "10px",
            right: "10px",
            padding: "5px 10px",
            cursor: "pointer",
          }}
        >
          Close
        </button>
        <h2>Preview Layout: {layout.name}</h2>
        <div
          className="layout"
          style={{
            position: "relative",
            width: "720px",
            height: "380px",
            border: "2px solid black",
          }}
        >
          {currentContent.map(({ division, contentIndex }, i) => (
            <div
              key={division.id}
              style={{
                position: "absolute",
                top: `${division.y}px`,
                left: `${division.x}px`,
                width: `${division.width}px`,
                height: `${division.height}px`,
                backgroundColor: division.color || "lightgray",
                display: "flex",
                justifyContent: "center",
                alignItems: "center",
              }}
            >
              {renderDivision(division, contentIndex, i)}
            </div>
          ))}
          {/* canvas code for video start */}
          {/* canvas code for video end */}
          {/* Progress Bar and Time */}
          <div
            style={{
              position: "absolute",
              bottom: 0,
              left: 0,
              width: "100%",
              height: "8px",
              backgroundColor: "#e0e0e0", // Background color for progress bar track
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
            }}
          >
            <div
              style={{
                height: "100%",
                width: `calc(${(elapsedTime / totalTime) * 100}%)`,
                backgroundColor: "#28a745", // Green color for progress bar
                transition: "width 0.5s linear", // Smooth transition
              }}
            ></div>

            {/* Time display */}
            {/* <span
              style={{
                position: "absolute",
                right: "0px", // Fixed right margin
                zIndex: 1, // Ensure it's above the progress bar
                padding: "5px",
                fontSize: "18px",
                fontWeight: "600",
                color: "#333",
                // backgroundColor: "rgba(255, 255, 255, 0.8)", // Add a subtle background for readability
              }}
            >
              {elapsedTime} / {totalTime}s
            </span> */}
          </div>
        </div>

        {/* Download button */}
        <button
          onClick={handleDownload}
          style={{
            marginTop: "20px",
            padding: "10px 20px",
            cursor: "pointer",
            backgroundColor: "#28a745",
            color: "white",
            border: "none",
            borderRadius: "5px",
            fontSize: "16px",
            transition: "background-color 0.3s ease",
          }}
          onMouseOver={(e) => (e.target.style.backgroundColor = "#218838")}
          onMouseOut={(e) => (e.target.style.backgroundColor = "#28a745")}
        >
          Download Video
        </button>
        {/* {recording && <p>Recording in progress...</p>} */}
      </div>
    </div>
  );
};

export default Preview;

I tried several methods to record the content of the div with the class “layout,” which contains dynamic media elements such as images and videos. The approaches I attempted include:

MediaRecorder API: I expected this API to capture the entire div and its contents, but it didn’t handle the rendering of all dynamic media elements properly.

html2canvas: I used this to capture the layout as a canvas and then attempted to convert it into a video stream. However, it could not capture smooth transitions between media elements, leading to a choppy or incomplete video output.

RecordRTC: I integrated RecordRTC to capture the canvas stream of the div. Despite setting up the recorder, the resulting video file either had a 0-byte size or only captured parts of the content inconsistently.

FFmpeg and other libraries: I explored these tools hoping they would provide a seamless capture of the dynamic content, but they also failed to capture the full media elements, including videos playing within the layout.

In all cases, I expected to get a complete video recording of the div, including all media transitions, but the results were incomplete or not functional.

Now, I’m seeking an approach or best practice to record the entire div with its dynamic content and media playback.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật