78

is it possible to display a html5-video as part of the canvas?

basically the same way as you draw an Image in the canvas.

context.drawVideo(vid, 0, 0);

thanks!

clamp
  • 33,000
  • 75
  • 203
  • 299

5 Answers5

122
var canvas = document.getElementById('canvas');
var ctx    = canvas.getContext('2d');
var video  = document.getElementById('video');

video.addEventListener('play', function () {
    var $this = this; //cache
    (function loop() {
        if (!$this.paused && !$this.ended) {
            ctx.drawImage($this, 0, 0);
            setTimeout(loop, 1000 / 30); // drawing at 30fps
        }
    })();
}, 0);

I guess the above code is self Explanatory, If not drop a comment below, I will try to explain the above few lines of code

Edit :
here's an online example, just for you :)
Demo

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var video = document.getElementById('video');

// set canvas size = video size when known
video.addEventListener('loadedmetadata', function() {
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
});

video.addEventListener('play', function() {
  var $this = this; //cache
  (function loop() {
    if (!$this.paused && !$this.ended) {
      ctx.drawImage($this, 0, 0);
      setTimeout(loop, 1000 / 30); // drawing at 30fps
    }
  })();
}, 0);
<div id="theater">
  <video id="video" src="http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv" controls></video>
  <canvas id="canvas"></canvas>
  <label>
    <br />Try to play me :)</label>
  <br />
</div>
syahid246
  • 109
  • 2
  • 7
  • 2
    could someone explain the point of this. is there any benefits from doing this interns of performance?? – Lpc_dark Aug 14 '13 at 15:52
  • 7
    1) You can manipulate the canvas, this is the first benefit. Anything you can imagine is possible - applying filters in realtime, making the canvas explode, whatever you want. 2) You can play videos on mobile devices without having to open a new video or display the native pause/play controls. An example is the macbook pro website, which leverages video + canvas to support mobile: https://www.apple.com/mac-pro/ – TaylorMac May 06 '14 at 17:51
  • 11
    Ok, but say you want to display the video in the canvas without also adding the video directly to the page. Can the above be modified to that or would that require a different approach? – Seanonymous Nov 03 '15 at 23:54
  • 24
    Should use `requestAnimationFrame` – Brad Oct 05 '16 at 21:45
  • Thanks for your work! Can you please clarify the need for caching this please? I've thought hard about it but I don't get it. – Rami Awar Apr 18 '18 at 11:28
  • @RamiAwar you need to have *this* cached because *this* will loose the execution context. Try running the code without the cached *$this* an instead use *this*. Using *this* instead of the cached version will have *this* as the global execution context (window) – kitimenpolku Jul 30 '18 at 09:55
  • 1
    Instead of using the $this, you can also just use an arrow functions, and regular this will work as you expect. – DoYouEvenCodeBro Nov 10 '19 at 18:00
  • @Seanonymous you can always create the video element using js. Here's a sample snippet. var video = document.createElement("video"); video.onplay = function () { var $this = this; //cache (function loop() { console.log(`$this = ${JSON.stringify($this)}`) if (!$this.paused && !$this.ended) { ctx.drawImage($this, 0, 0, 190, 90); requestAnimationFrame(loop) // drawing at 30fps } })(); } video.src = "./your_video.mp4"; video.muted = true; video.play(); – kevin godfrey Jul 08 '22 at 17:20
  • @TaylorMac But incompletely implemented. On IOS the combination with globalCompositeOperation seems to be broken. Or am I on the hose?https://stackoverflow.com/questions/75922018/mask-a-video-overlay-with-globalcompositeoperation-rendered-in-a-canvas-in-ios – metamagikum Apr 04 '23 at 17:30
99

Here's a solution that uses more modern syntax and is less verbose than the ones already provided:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const video = document.querySelector("video");

video.addEventListener("play", () => {
  function step() {
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
});

Some useful links:

holem
  • 105
  • 1
  • 10
David
  • 4,191
  • 2
  • 31
  • 40
  • 4
    Shorter, simpler, understandable answer without noice or bloated code, take my upvote! – Endless Jun 09 '19 at 11:43
  • 3
    This solution is rendering a video container in the html & then copying that info into the Canvas, how do you play video directly into the canvas? – Ryan Stone Jan 30 '20 at 04:43
  • 1
    @RyanStone it's not possible to play video directly to the canvas. But this solution works pretty good. – Roman Podlinov May 22 '20 at 16:42
  • Hi, Can you show a example ? for play and pause in canvas video ?, I tried play the video after pause but I think in background it is still playing. – Shaiju T Sep 11 '20 at 20:23
  • @RomanPodlinov if it's not possible to play video directly to canvas, does this mean it's not possible to build a canvas video editor that allows users to upload mp4/mov/avi videos and overlay text animations, where the output is a webm video? thanks for the time. – Crashalot Jan 09 '21 at 21:08
  • @Crashalot maybe to try web assembly instead of canvas? – Roman Podlinov Jan 11 '21 at 11:40
  • Vote up for `requestAnimationFrame` – Roman Podlinov Jan 11 '21 at 11:41
  • Why are there two calls to `requestAnimationFrame(step)`? I was trying this out and found that it had pretty slow performance. On a hunch, I removed the anonymous function that wraps the definition of `step`, as well as that outer `requestAnimationFrame(step)` call. It seems to accomplish the same result, only with better performance. – TigerhawkT3 Apr 18 '22 at 10:27
63

Using canvas to display Videos

Displaying a video is much the same as displaying an image. The minor differences are to do with onload events and the fact that you need to render the video every frame or you will only see one frame not the animated frames.

The demo below has some minor differences to the example. A mute function (under the video click mute/sound on to toggle sound) and some error checking to catch IE9+ and Edge if they don't have the correct drivers.

Keeping answers current.

The previous answers by user372551 is out of date (December 2010) and has a flaw in the rendering technique used. It uses the setTimeout and a rate of 33.333..ms which setTimeout will round down to 33ms this will cause the frames to be dropped every two seconds and may drop many more if the video frame rate is any higher than 30. Using setTimeout will also introduce video shearing created because setTimeout can not be synced to the display hardware.

There is currently no reliable method that can determine a videos frame rate unless you know the video frame rate in advance you should display it at the maximum display refresh rate possible on browsers. 60fps

The given top answer was for the time (6 years ago) the best solution as requestAnimationFrame was not widely supported (if at all) but requestAnimationFrame is now standard across the Major browsers and should be used instead of setTimeout to reduce or remove dropped frames, and to prevent shearing.

The example demo.

Loads a video and set it to loop. The video will not play until the you click on it. Clicking again will pause. There is a mute/sound on button under the video. The video is muted by default.

Note users of IE9+ and Edge. You may not be able to play the video format WebM as it needs additional drivers to play the videos. They can be found at tools.google.com Download IE9+ WebM support

// This code is from the example document on stackoverflow documentation. See HTML for link to the example.
// This code is almost identical to the example. Mute has been added and a media source. Also added some error handling in case the media load fails and a link to fix IE9+ and Edge support.
// Code by Blindman67.


// Original source has returns 404
// var mediaSource = "http://video.webmfiles.org/big-buck-bunny_trailer.webm";
// New source from wiki commons. Attribution in the leading credits.
var mediaSource = "http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv"

var muted = true;
var canvas = document.getElementById("myCanvas"); // get the canvas from the page
var ctx = canvas.getContext("2d");
var videoContainer; // object to hold video and associated info
var video = document.createElement("video"); // create a video element
video.src = mediaSource;
// the video will now begin to load.
// As some additional info is needed we will place the video in a
// containing object for convenience
video.autoPlay = false; // ensure that the video does not auto play
video.loop = true; // set the video to loop.
video.muted = muted;
videoContainer = {  // we will add properties as needed
     video : video,
     ready : false,   
};
// To handle errors. This is not part of the example at the moment. Just fixing for Edge that did not like the ogv format video
video.onerror = function(e){
    document.body.removeChild(canvas);
    document.body.innerHTML += "<h2>There is a problem loading the video</h2><br>";
    document.body.innerHTML += "Users of IE9+ , the browser does not support WebM videos used by this demo";
    document.body.innerHTML += "<br><a href='https://tools.google.com/dlpage/webmmf/'> Download IE9+ WebM support</a> from tools.google.com<br> this includes Edge and Windows 10";
    
 }
video.oncanplay = readyToPlayVideo; // set the event to the play function that 
                                  // can be found below
function readyToPlayVideo(event){ // this is a referance to the video
    // the video may not match the canvas size so find a scale to fit
    videoContainer.scale = Math.min(
                         canvas.width / this.videoWidth, 
                         canvas.height / this.videoHeight); 
    videoContainer.ready = true;
    // the video can be played so hand it off to the display function
    requestAnimationFrame(updateCanvas);
    // add instruction
    document.getElementById("playPause").textContent = "Click video to play/pause.";
    document.querySelector(".mute").textContent = "Mute";
}

function updateCanvas(){
    ctx.clearRect(0,0,canvas.width,canvas.height); 
    // only draw if loaded and ready
    if(videoContainer !== undefined && videoContainer.ready){ 
        // find the top left of the video on the canvas
        video.muted = muted;
        var scale = videoContainer.scale;
        var vidH = videoContainer.video.videoHeight;
        var vidW = videoContainer.video.videoWidth;
        var top = canvas.height / 2 - (vidH /2 ) * scale;
        var left = canvas.width / 2 - (vidW /2 ) * scale;
        // now just draw the video the correct size
        ctx.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
        if(videoContainer.video.paused){ // if not playing show the paused screen 
            drawPayIcon();
        }
    }
    // all done for display 
    // request the next frame in 1/60th of a second
    requestAnimationFrame(updateCanvas);
}

function drawPayIcon(){
     ctx.fillStyle = "black";  // darken display
     ctx.globalAlpha = 0.5;
     ctx.fillRect(0,0,canvas.width,canvas.height);
     ctx.fillStyle = "#DDD"; // colour of play icon
     ctx.globalAlpha = 0.75; // partly transparent
     ctx.beginPath(); // create the path for the icon
     var size = (canvas.height / 2) * 0.5;  // the size of the icon
     ctx.moveTo(canvas.width/2 + size/2, canvas.height / 2); // start at the pointy end
     ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 + size);
     ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 - size);
     ctx.closePath();
     ctx.fill();
     ctx.globalAlpha = 1; // restore alpha
}    

function playPauseClick(){
     if(videoContainer !== undefined && videoContainer.ready){
          if(videoContainer.video.paused){                                 
                videoContainer.video.play();
          }else{
                videoContainer.video.pause();
          }
     }
}
function videoMute(){
    muted = !muted;
 if(muted){
         document.querySelector(".mute").textContent = "Mute";
    }else{
         document.querySelector(".mute").textContent= "Sound on";
    }


}
// register the event
canvas.addEventListener("click",playPauseClick);
document.querySelector(".mute").addEventListener("click",videoMute)
body {
    font :14px  arial;
    text-align : center;
    background : #36A;
}
h2 {
    color : white;
}
canvas {
    border : 10px white solid;
    cursor : pointer;
}
a {
  color : #F93;
}
.mute {
    cursor : pointer;
    display: initial;   
}
<h2>Basic Video & canvas example</h2>
<p>Code example from Stackoverflow Documentation HTML5-Canvas<br>
<a href="https://stackoverflow.com/documentation/html5-canvas/3689/media-types-and-the-canvas/14974/basic-loading-and-playing-a-video-on-the-canvas#t=201607271638099201116">Basic loading and playing a video on the canvas</a></p>
<canvas id="myCanvas" width = "532" height ="300" ></canvas><br>
<h3><div id = "playPause">Loading content.</div></h3>
<div class="mute"></div><br>
<div style="font-size:small">Attribution in the leading credits.</div><br>

Canvas extras

Using the canvas to render video gives you additional options in regard to displaying and mixing in fx. The following image shows some of the FX you can get using the canvas. Using the 2D API gives a huge range of creative possibilities.

Image relating to answer Fade canvas video from greyscale to color Video filters "Lighten", "Black & white", "Sepia", "Saturate", and "Negative"

See video title in above demo for attribution of content in above inmage.

Graham
  • 7,431
  • 18
  • 59
  • 84
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • Just a couple of notes: `video.autoPlay` is all low-caps (doesn't follow the typical camelcase for some reason), e.g.: `video.autoplay`. It's not really needed as it defaults to false unless defined in the video tag. Most video available is either 30 fps (ntsc/hd) or 25 fps (pal) so you can typically safely toggle drawing inside the loop so you only draw 30 fps (saving 30 draw ops per second). –  Sep 26 '17 at 15:12
  • An additional tip: you don't need to clear the canvas per frame unless the video contains an alpha channel (very rare at consumer end and currently only supported in webm format+blink). –  Sep 26 '17 at 15:16
  • I tried setting autoplay to true and added an onload event to the playPauseClick function. However it doesn't seem to autoplay. Is there a way to have the video autoplay? – user3605780 Feb 23 '19 at 10:43
  • 1
    @user3605780 That is odd. I could not get it to autoplay without a little help. Simplest solution is to add the line `video,play();` as the last line of the function `readyToPlayVideo` it is the listener to the media event `canplay` However the video may not play if its not in the current active browser tab. – Blindman67 Feb 23 '19 at 16:02
  • @Blindman67 thanks the autoplay worked on Windows and Android. However on iOS with Safari the entire canvas video doesn't work. Is there a solution to get this working on iOS? – user3605780 Feb 24 '19 at 08:50
  • Audio is playing, but the video is not getting rendered. Any possible mistakes? – JustCurious Oct 06 '20 at 10:11
1

You need to update currentTime video element and then draw the frame in canvas. Don't init play() event on the video.

You can also use for ex. this plugin https://github.com/tstabla/stVideo

Lysy
  • 11
  • 1
-1

I started with the answer from 2018 about requestAnimationFrame. However, it has three problems.

  • unnecessarily complex
  • there is a new way to handle this task (since about 2020)
  • attaching an event listener to the play event invites performance issues

First, the event listener can simply be connected to the function that does your desired processing and schedules the next call to itself. There's no need to wrap it in an anonymous function with another call to requestAnimationFrame.

Second, don't use requestAnimationFrame. That function schedules the next call to your callback as quickly as the browser can handle (generally 60Hz), which results in a significant processing workload. video.requestVideoFrameCallback only calls your callback when the video proceeds to its next frame. This reduces the workload when a video runs at less than 60 FPS and obviates the need for any processing at all while the video isn't playing, significantly improving performance.

Third, an event listener attached to the play event will fire whenever you tell the video to video.play(), which you'll do every time you load a new video into the video element and tell it to start playing, and also when you resume playback after using video.pause(). Thus, the video is drawn on the canvas (plus whatever other processing you're doing) once for each time the video has been told to play(), which quickly accumulates.

If you're sure you'll only be playing one video which you'd like to pause and resume, you can toggle play/pause by changing the playback rate, e.g. video.playbackRate = !video.playbackRate. If you'll be loading multiple videos into this element, it's better to forego the play event listener entirely and insert a manual call to step() when you load your first video to get that started. Note that this is active on the video element, not on specific loaded videos, so you'll need to either set and check a flag to ensure that you only call step() when loading the first video, or cancel any active video frame request before making a new one (shown below).

let animation_handle;
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const video = document.querySelector("video");
video.addEventListener('loadeddata', video_load_callback, false);

function video_load_callback() {
    video.cancelVideoFrameCallback(animation_handle);
    step()
}
function step() { // update the canvas when a video proceeds to next frame
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    animation_handle = video.requestVideoFrameCallback(step);
}
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • Can you show full example please? I've got `video.cancelVideoFrameCallback is not a function` error – Oleg2tor Jul 28 '22 at 21:08
  • 1
    @Oleg2tor - Looks like Firefox hasn't implemented that API yet, on either desktop or mobile. :( Works in Chromium-based browsers, haven't tried it in Safari. – TigerhawkT3 Jul 30 '22 at 03:27