You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js应用:如何创建可定时添加图文的视频录制器?

解决方案:React中实现带时间戳图层的视频录制功能

Hey there! Sorry to hear Video.js isn't meeting your needs for adding images and text at specific timestamps during video recording—let's build a custom solution using browser native APIs and React that gives you full control over this feature.

Core Idea

Instead of relying on a pre-built library like Video.js, we'll use:

  • MediaRecorder API: To capture the raw video stream from the user's camera
  • HTML5 Canvas: To overlay images/text onto the video stream in real-time before recording
  • React's state management: To track recording progress, trigger overlays at specific times, and handle user interactions

Step 1: Set Up Basic Video Recording in React

First, let's create a component that can access the user's camera and start/stop recording using MediaRecorder:

import { useState, useRef, useEffect } from 'react';

const VideoRecorder = () => {
  const [isRecording, setIsRecording] = useState(false);
  const [recordedChunks, setRecordedChunks] = useState([]);
  const videoRef = useRef(null);
  const mediaRecorderRef = useRef(null);
  const startTimeRef = useRef(null);

  // Access camera on component mount
  useEffect(() => {
    const startCamera = async () => {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
        if (videoRef.current) {
          videoRef.current.srcObject = stream;
        }
      } catch (err) {
        console.error('Failed to access camera:', err);
      }
    };
    startCamera();
  }, []);

  const startRecording = () => {
    const stream = videoRef.current?.srcObject;
    if (!stream) return;

    // Initialize MediaRecorder
    mediaRecorderRef.current = new MediaRecorder(stream);
    startTimeRef.current = Date.now();

    mediaRecorderRef.current.ondataavailable = (e) => {
      if (e.data.size > 0) {
        setRecordedChunks(prev => [...prev, e.data]);
      }
    };

    mediaRecorderRef.current.start();
    setIsRecording(true);
  };

  const stopRecording = () => {
    mediaRecorderRef.current?.stop();
    setIsRecording(false);
  };

  const downloadRecording = () => {
    const blob = new Blob(recordedChunks, { type: 'video/webm' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'recorded-video.webm';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div>
      <video ref={videoRef} autoPlay muted style={{ width: '640px', height: '480px' }} />
      <div style={{ marginTop: '10px' }}>
        <button onClick={startRecording} disabled={isRecording}>
          Start Recording
        </button>
        <button onClick={stopRecording} disabled={!isRecording}>
          Stop Recording
        </button>
        <button onClick={downloadRecording} disabled={recordedChunks.length === 0}>
          Download Video
        </button>
      </div>
    </div>
  );
};

export default VideoRecorder;

Step 2: Add Timed Overlays (Images & Text)

To overlay content at specific timestamps, we'll use a Canvas element to combine the video stream with our overlays, then feed the Canvas stream to MediaRecorder instead of the raw camera stream.

Here's how to modify the component:

  1. Add a Canvas ref and overlay configuration state:
const canvasRef = useRef(null);
// Array of overlay objects: { time: number (seconds), type: 'image'|'text', content: string, position: { x: number, y: number } }
const [overlays, setOverlays] = useState([
  { time: 2, type: 'text', content: 'Hello at 2s', position: { x: 50, y: 50 } },
  { time: 5, type: 'image', content: 'https://example.com/overlay-image.png', position: { x: 100, y: 100 } }
]);
  1. Modify the camera setup to use Canvas:
useEffect(() => {
  const startCamera = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
      const video = videoRef.current;
      const canvas = canvasRef.current;
      if (!video || !canvas) return;

      video.srcObject = stream;
      video.play().then(() => {
        // Set Canvas size to match video
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        const ctx = canvas.getContext('2d');

        // Preload overlay images to avoid frame lag
        const imageCache = new Map();
        overlays.forEach(overlay => {
          if (overlay.type === 'image') {
            const img = new Image();
            img.src = overlay.content;
            imageCache.set(overlay.content, img);
          }
        });

        // Function to draw video + overlays
        const drawFrame = () => {
          if (!isRecording) return requestAnimationFrame(drawFrame);
          
          // Draw video frame to Canvas
          ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

          // Calculate elapsed recording time in seconds
          const elapsed = (Date.now() - startTimeRef.current) / 1000;

          // Draw active overlays
          overlays.forEach(overlay => {
            // Show overlay for 1 second (adjust duration as needed)
            if (elapsed >= overlay.time && elapsed <= overlay.time + 1) {
              ctx.font = '24px Arial';
              ctx.fillStyle = 'white';
              ctx.strokeStyle = 'black';
              ctx.lineWidth = 2;

              if (overlay.type === 'text') {
                ctx.strokeText(overlay.content, overlay.position.x, overlay.position.y);
                ctx.fillText(overlay.content, overlay.position.x, overlay.position.y);
              } else if (overlay.type === 'image') {
                const img = imageCache.get(overlay.content);
                if (img) {
                  ctx.drawImage(img, overlay.position.x, overlay.position.y, 100, 100); // Adjust size as needed
                }
              }
            }
          });

          requestAnimationFrame(drawFrame);
        };

        drawFrame();
      });

      // Get Canvas stream for recording
      const canvasStream = canvas.captureStream();
      // Merge audio from original stream with Canvas stream
      const combinedStream = new MediaStream([
        ...canvasStream.getTracks(),
        ...stream.getAudioTracks()
      ]);
      mediaRecorderRef.current = new MediaRecorder(combinedStream);
    } catch (err) {
      console.error('Failed to access camera:', err);
    }
  };
  startCamera();
}, [isRecording, overlays]);
  1. Add the Canvas to the JSX (hide it if needed—we only need it for processing):
<canvas ref={canvasRef} style={{ display: 'none' }} />

Step 3: Key Optimizations & Notes

  • Sync Timing: Always use the recording's elapsed time (calculated from startTimeRef) instead of system time to avoid drift if the app is paused in the background.
  • Cache Images: Preload overlay images upfront instead of loading them on each frame to improve performance.
  • Overlay Duration: Adjust the elapsed <= overlay.time + 1 condition to control how long each overlay stays visible.
  • Customization: Extend the overlays state to support font sizes, colors, image dimensions, and longer durations.

Final Notes

This approach gives you full control over when and how overlays are added during recording—something that's hard to do with Video.js alone. You can expand it further by adding a UI to let users add/remove overlays, set timestamps, and preview them before recording.

内容的提问来源于stack exchange,提问作者moshera Darweesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 14:12:38