reactjshtml5-canvashtml5-videokonvajsreact-konva

How to capture the first frame of a video in react-konva


I made a simple video player using react-konva and I want to display the first frame as a thumbnail before the video is played.

Here's my approach:

I'm using Konva.Image to display the video and Konva.Animation to update the image.

import Konva from "konva";
import React, { useState, useRef, useEffect } from "react";
import { Stage, Layer, Image, Text } from "react-konva";

function VideoPlayer({ width, height }) {
  const video = useRef();
  const image = useRef();
  const anim = useRef();
  const text = useRef();
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    anim.current = new Konva.Animation(() => {
      image.current.image(video.current);
    }, image.current.getLayer());
  });

  return (
    <div>
      <button
        onClick={() => {
          text.current.destroy();
          video.current.play();
          anim.current.start();
        }}
      >
        PLAY
      </button>
      <button
        onClick={() => {
          video.current.pause();
          anim.current.stop();
        }}
      >
        PAUSE
      </button>
      <video
        style={{ display: "none" }}
        ref={video}
        onLoadedData={() => {
          setLoading(false);
        }}
        src="https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm"
      />
      <Stage width={window.innerWidth} height={window.innerHeight}>
        <Layer>
          <Text
            ref={text}
            text={loading ? "Loading..." : "Press PLAY..."}
            {...{
              width: window.innerWidth,
              height: window.innerHeight,
              align: "center",
              verticalAlign: "middle"
            }}
          />
          <Image
            x={100}
            y={100}
            ref={image}
            width={width}
            height={height}
            stroke="black"
          />
        </Layer>
      </Stage>
    </div>
  );
}

export default VideoPlayer;

I have created a working demo of my implementation here.


Solution

  • You can use a hook to:

    1. create a canvas element for preview image
    2. create a video element
    3. wait for video load
    4. draw the first frame into the canvas
    5. when the video is not played, use preview canvas as source
    const usePreview = (url) => {
      const [canvas, setCanvas] = useState(null);
    
      useEffect(() => {
        const video = document.createElement("video");
        video.src = url;
        const onLoad = () => {
          const canvas = document.createElement("canvas");
          canvas.width = video.videoWidth;
          canvas.height = video.videoHeight;
          video.currentTime = 1;
          const ctx = canvas.getContext("2d");
          ctx.drawImage(video, 0, 0);
          setCanvas(canvas);
        };
        video.addEventListener("canplay", onLoad);
        return () => video.removeEventListener("load", onLoad);
      }, [url]);
    
      return canvas;
    };
    

    Full demo: https://codesandbox.io/s/react-konva-video-preview-0we9d?file=/src/index.js