Search code examples
javascriptkonvajsreact-konva

Play video on Canvas in React Konva


Building off of Konva documentation on how to render a video to the canvas, I would like to play the video by accessing the video reference directly from the Konva.Image instance. Below is a contrived example to simulate my goal of playing the video dynamically from outside the component. The below does not play the video as expected, even though imageRef.current.image() returns the video element reference. Any suggestions on how I can access the video reference alternatively?

import React, { useEffect, useContext, useState } from 'react'
import { Image } from "react-konva";
import Konva from 'konva'

 export const MainVideo = ({ shape, dispatch  }) =>  {
  const imageRef = React.useRef(null);
  const video = document.createElement('video');
  video.setAttribute('src',shape.url);


  useEffect(() => {
    if(imageRef) {

  imageRef.current.image().play();
  const layer = imageRef.current.getLayer();

  const anim = new Konva.Animation(() => {
  }, layer);

        anim.start()

    }
  }, [imageRef])

    return (
     <Image 
     ref={imageRef}
     opacity={shape.o}
     id={shape.id} 
     image={video} 
     x={shape.x} 
     y={shape.y} 
     zIndex={0}
     height={360}
     width={640} />
    )
}

Solution

  • You can do this:

    const Video = ({ src }) => {
      const imageRef = React.useRef(null);
      const [size, setSize] = React.useState({ width: 50, height: 50 });
    
      // we need to use "useMemo" here, so we don't create new video elment on any render
      const videoElement = React.useMemo(() => {
        const element = document.createElement("video");
        element.src = src;
        return element;
      }, [src]);
    
      // when video is loaded, we should read it size
      React.useEffect(() => {
        const onload = function() {
          setSize({
            width: videoElement.videoWidth,
            height: videoElement.videoHeight
          });
        };
        videoElement.addEventListener("loadedmetadata", onload);
        return () => {
          videoElement.removeEventListener("loadedmetadata", onload);
        };
      }, [videoElement]);
    
      // use Konva.Animation to redraw a layer
      React.useEffect(() => {
        videoElement.play();
        const layer = imageRef.current.getLayer();
    
        const anim = new Konva.Animation(() => {}, layer);
        anim.start();
    
        return () => anim.stop();
      }, [videoElement]);
    
      return (
        <Image
          ref={imageRef}
          image={videoElement}
          x={20}
          y={20}
          stroke="red"
          width={size.width}
          height={size.height}
          draggable
        />
      );
    };
    

    Demo: https://codesandbox.io/s/react-konva-video-on-canvas-oygvf