website logo

Last Updated:

Play Videos in Html Canvas and Add Custom Controls

feature.webp

Videos are a collection of images displayed in a very fast rate. Each image of a video is called a frame. When you heard terms like 30fps or 60fps, this means 30 images or 60 images are displayed per second, respectively.

During the video playback, these video frames can be drawn in an HTML canvas.

The process seems complex. But believe me, it is very simple as drawing a still image in an HTML canvas.

Add a Video on Your Webpage

There are 2 ways to add a video source on your webpage.

  1. Add the video source in your HTML document.
  2. Accept user file input and add that to the video source.

The first one is very easy as it is hard coded in your HTML document. Just add the URL of your video in the src attribute of the video element, and you are good to go.

<video id="video" src="./vid.mp4"></video>

The second one is a bit complex. Let’s break it down into easily digestible points.

  1. First define your file input element in HTML document.
<input type="file" id="videoInput" />
  1. Listen for any file events.
const video = document.getElementById("video");
const vidInput = document.getElementById("videoInput");
vidInput.onchange = (e) => {
  let file = e.target.files[0];
  let url = URL.createObjectURL(file);
};
  1. Add the url to the <video> element source.
vidInput.onchange = (e) => {
  let file = e.target.files[0];
  let url = URL.createObjectURL(file);
  video.src = url;
  video.preload = "metadata";
  video.load();
};

Draw the First Frame of Your Video in the Canvas

  1. Define a <canvas> element in your HTML file.
<canvas data-state="pause" id="canvas"></canvas>
  1. Change the default width and height of the <canvas> as per the video dimension.
const canvas = document.getElementById("canvas");
video.onloadeddata = () => {
  canvas.height = video.videoHeight;
  canvas.width = video.videoWidth;
};
  1. Draw the first frame of the video on the canvas.
const context = canvas.getContext("2d");
video.oncanplay = () => {
  context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
};

Add Custom Controls on the Canvas

Previously, we have added a data-state attribute in the canvas element. We use this data-attribute to implement the play/pause functionality of the video playback on our canvas element.

  1. Define an action button in the HTML document.
<button class="action"></button>
  1. Listen for click on the action button and modify the canvas data-state value.
const action = document.querySelector(".action");

action.onclick = () => {
  console.log("running");
  if (canvas.dataset.state === "pause") {
    canvas.dataset.state = "play";
    action.textContent = "⏸";
  } else if (canvas.dataset.state === "play") {
    canvas.dataset.state = "pause";
    action.textContent = "▶";
  }
};

Play the Video on the HTML Canvas

We can play the video using video.play() method and pause the video using video.pause() method. Let’s add these methods in the action button’s click events to pause/play video on button click.

action.onclick = () => {
  console.log("running");
  if (canvas.dataset.state === "pause") {
    canvas.dataset.state = "play";
    action.textContent = "⏸";
    video.play(); // Play the video
  } else if (canvas.dataset.state === "play") {
    canvas.dataset.state = "pause";
    action.textContent = "▶";
    video.pause(); // Pause the video
  }
};

When a video is playing, we can display the video frames in the canvas using the drawImage method. But we have to call the drawImage method multiple time per second (precisely, equal to your display refresh rate) to play the video.

This is a very resource intensive task. But thankfully, browsers provide a requestAnimationFrame method that runs multiple times per second equal to your display refresh rate.

Let’s make a play function that draw the video frames in our canvas multiple times per second.

function play() {
  context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
  animationFrame = requestAnimationFrame(play);
}

Now listen to the video.onplay event. When the video will be playing, we can run the play method and draw the video frames in the canvas.

video.onplay = () => {
  animationFrame = requestAnimationFrame(play);
  // Don't worry about the animationFrame variable. I will talk about it later
};

Now you can click on your action button and your video should start playing in the canvas.

Reduce resource usage by your web application

In the above code example, you can see an animationFrame variable. This variable contains a reference to the requestAnimationFrame method.

When you pause your video or video itself finish playing to the end, the requestAnimationFrame method still running and consuming resources. To stop running this method on video pause or ended event, you can use cancelAnimationFrame() method.

video.onpause = () => {
  cancelAnimationFrame(animationFrame);
};

video.onended = () => {
  canvas.dataset.state = "pause";
  action.textContent = "▶";
  cancelAnimationFrame(animationFrame);
};

In addition to this, if you’re done using the video source, call the URL.revokeObjectURL(objectURL) method to release the video file from the RAM. This reduces the RAM usage of your web application.

Source Code

  1. File: index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom video player</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <main>
      <section class="container">
        <h2>Video Element</h2>
        <video id="video" src="./vid.mp4"></video>
      </section>
      <section class="container controls">
        <h2>Canvas Element</h2>
        <canvas data-state="pause" id="canvas"></canvas>
        <button class="action"></button>
      </section>
    </main>
    <script src="./index.js"></script>
  </body>
</html>
  1. File: index.js
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById("canvas");

/** @type {HTMLVideoElement} */
const video = document.getElementById("video");

const context = canvas.getContext("2d");

const action = document.querySelector(".action");

let animationFrame;

video.onloadeddata = () => {
  canvas.height = video.videoHeight;
  canvas.width = video.videoWidth;
};

video.oncanplay = () => {
  context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
};

action.onclick = () => {
  console.log("running");
  if (canvas.dataset.state === "pause") {
    canvas.dataset.state = "play";
    action.textContent = "⏸";
    video.play();
  } else if (canvas.dataset.state === "play") {
    canvas.dataset.state = "pause";
    action.textContent = "▶";
    video.pause();
  }
};

video.onplay = () => {
  animationFrame = requestAnimationFrame(play);
};

video.onpause = () => {
  cancelAnimationFrame(animationFrame);
};

video.onended = () => {
  canvas.dataset.state = "pause";
  action.textContent = "▶";
  cancelAnimationFrame(animationFrame);
};

function play() {
  context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
  animationFrame = requestAnimationFrame(play);
}
  1. File: style.css
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

main {
  display: flex;
  max-width: 1024px;
  margin: auto;
  gap: 40px;
  padding: 100px;
}

.container {
  width: 400px;
  font-family: "Fira Sans";
}

h2 {
  text-align: center;
}

video {
  width: 100%;
}

canvas {
  width: 100%;
}

.controls {
  position: relative;
}

.action {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  border: 2px solid black;
  border-radius: 4px;
  padding: 10px 15px;
  background-color: #3a86ff;
}

See Also