Video manipulation
You can manipulate a video buffer by rendering a <Video> onto a <canvas> element using the drawImage() API and keeping it in sync using the requestVideoFrameCallback() API.
note
This API currently only works in Chrome.
Basic example
In this example, a Video is rendered and made invisible. Then it is rendered onto a Canvas and a grayscale filter is applied.
tsxexport constVideoOnCanvas :React .FC = () => {constvideo =useRef <HTMLVideoElement >(null);constcanvas =useRef <HTMLCanvasElement >(null);const {width ,height } =useVideoConfig ();// Process a frameconstonVideoFrame =useCallback (() => {if (!canvas .current || !video .current ) {return;}constcontext =canvas .current .getContext ("2d");if (!context ) {return;}context .filter = "grayscale(100%)";context .drawImage (video .current , 0, 0,width ,height );}, [height ,width ]);// Synchronize the video with the canvasuseEffect (() => {const {current } =video ;if (!current ?.requestVideoFrameCallback ) {return;}lethandle = 0;constcallback = () => {onVideoFrame ();handle =current .requestVideoFrameCallback (callback );};callback ();return () => {current .cancelVideoFrameCallback (handle );};}, [onVideoFrame ]);return (<AbsoluteFill ><AbsoluteFill ><Video ref ={video }// Hide the original video tagstyle ={{opacity : 0 }}startFrom ={300}src ="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"/></AbsoluteFill ><AbsoluteFill ><canvas ref ={canvas }width ={width }height ={height } /></AbsoluteFill ></AbsoluteFill >);};
tsxexport constVideoOnCanvas :React .FC = () => {constvideo =useRef <HTMLVideoElement >(null);constcanvas =useRef <HTMLCanvasElement >(null);const {width ,height } =useVideoConfig ();// Process a frameconstonVideoFrame =useCallback (() => {if (!canvas .current || !video .current ) {return;}constcontext =canvas .current .getContext ("2d");if (!context ) {return;}context .filter = "grayscale(100%)";context .drawImage (video .current , 0, 0,width ,height );}, [height ,width ]);// Synchronize the video with the canvasuseEffect (() => {const {current } =video ;if (!current ?.requestVideoFrameCallback ) {return;}lethandle = 0;constcallback = () => {onVideoFrame ();handle =current .requestVideoFrameCallback (callback );};callback ();return () => {current .cancelVideoFrameCallback (handle );};}, [onVideoFrame ]);return (<AbsoluteFill ><AbsoluteFill ><Video ref ={video }// Hide the original video tagstyle ={{opacity : 0 }}startFrom ={300}src ="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"/></AbsoluteFill ><AbsoluteFill ><canvas ref ={canvas }width ={width }height ={height } /></AbsoluteFill ></AbsoluteFill >);};
Greenscreen example
In this example, we loop over each pixel in the image buffer and if it's green, we transparentize it. Drag the slider below to turn the video transparent.
Slide to adjust transparency:
tsxexport constGreenscreen :React .FC <{opacity : number;}> = ({opacity }) => {constvideo =useRef <HTMLVideoElement >(null);constcanvas =useRef <HTMLCanvasElement >(null);const {width ,height } =useVideoConfig ();// Process a frameconstonVideoFrame =useCallback ((opacity : number) => {if (!canvas .current || !video .current ) {return;}constcontext =canvas .current .getContext ("2d");if (!context ) {return;}context .drawImage (video .current , 0, 0,width ,height );constimageFrame =context .getImageData (0, 0,width ,height );const {length } =imageFrame .data ;// If the pixel is very green, reduce the alpha channelfor (leti = 0;i <length ;i += 4) {constred =imageFrame .data [i + 0];constgreen =imageFrame .data [i + 1];constblue =imageFrame .data [i + 2];if (green > 100 &&red < 100 &&blue < 100) {imageFrame .data [i + 3] =opacity * 255;}}context .putImageData (imageFrame , 0, 0);},[height ,width ]);useEffect (() => {const {current } =video ;if (!current || !current .requestVideoFrameCallback ) {return;}lethandle = 0;constcallback = () => {onVideoFrame (opacity );handle =current .requestVideoFrameCallback (callback );};callback ();return () => {current .cancelVideoFrameCallback (handle );};}, [onVideoFrame ,opacity ]);return (<AbsoluteFill ><AbsoluteFill ><Video ref ={video }style ={{opacity : 0 }}startFrom ={300}// If we access the data of a remote video, we must add this prop, and the remote video must have CORS enabledcrossOrigin ="anonymous"src ="https://remotion-assets.s3.eu-central-1.amazonaws.com/just-do-it.mp4"/></AbsoluteFill ><AbsoluteFill ><canvas ref ={canvas }width ={width }height ={height } /></AbsoluteFill ></AbsoluteFill >);};
tsxexport constGreenscreen :React .FC <{opacity : number;}> = ({opacity }) => {constvideo =useRef <HTMLVideoElement >(null);constcanvas =useRef <HTMLCanvasElement >(null);const {width ,height } =useVideoConfig ();// Process a frameconstonVideoFrame =useCallback ((opacity : number) => {if (!canvas .current || !video .current ) {return;}constcontext =canvas .current .getContext ("2d");if (!context ) {return;}context .drawImage (video .current , 0, 0,width ,height );constimageFrame =context .getImageData (0, 0,width ,height );const {length } =imageFrame .data ;// If the pixel is very green, reduce the alpha channelfor (leti = 0;i <length ;i += 4) {constred =imageFrame .data [i + 0];constgreen =imageFrame .data [i + 1];constblue =imageFrame .data [i + 2];if (green > 100 &&red < 100 &&blue < 100) {imageFrame .data [i + 3] =opacity * 255;}}context .putImageData (imageFrame , 0, 0);},[height ,width ]);useEffect (() => {const {current } =video ;if (!current || !current .requestVideoFrameCallback ) {return;}lethandle = 0;constcallback = () => {onVideoFrame (opacity );handle =current .requestVideoFrameCallback (callback );};callback ();return () => {current .cancelVideoFrameCallback (handle );};}, [onVideoFrame ,opacity ]);return (<AbsoluteFill ><AbsoluteFill ><Video ref ={video }style ={{opacity : 0 }}startFrom ={300}// If we access the data of a remote video, we must add this prop, and the remote video must have CORS enabledcrossOrigin ="anonymous"src ="https://remotion-assets.s3.eu-central-1.amazonaws.com/just-do-it.mp4"/></AbsoluteFill ><AbsoluteFill ><canvas ref ={canvas }width ={width }height ={height } /></AbsoluteFill ></AbsoluteFill >);};
TypeScript issues
In our experience, the types for requestVideoFrameCallback and cancelVideoFrameCallback are missing or wrong by default. Install the newest version for @types/web
or add a // @ts-expect-error comment to suppress the errors.