I'am trying to display a video inside a canvas and to loop the video. The issue sometimes happen when looping the video. Before playing the video again the video flicker for one frame. It's doesn't happen all the time and I don't get what's going on.
Here is the code
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let src = "https://i.imgur.com/5ZiAeSX.mp4";
let video = document.createElement("video");
video.src = src;
video.muted = true;
video.play();
video.onended = () => {
video.play();
};
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(video, 0, 0);
requestAnimationFrame(render);
}
render();
You can also try the fiddle here
This is because looping a MediaElement is never a seamless operation.
This is particularly audible when looping audio media.
Normally, when played in a <video> we don't see it because the browser just pauses the video rendering for this short lapse of time, and our brain simply ignores the few frames that are paused and concentrate rather on all the ones that do move.
However, in your case it becomes very visible because you do clear the canvas anyway, but there is no video frame to be drawn on top of it. So it causes a big white flash.
A simple fix is to check whether the currentTime is 0 and to not redraw during this time:
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let src = "https://i.imgur.com/5ZiAeSX.mp4";
let video = document.createElement("video");
video.src = src;
video.muted = true;
video.play();
let missed_frames = 0; // only for demo
video.onended = () => {
video.play();
// only for demo
setTimeout(() => {
_log.textContent = 'missed ' + missed_frames +' frames while looping';
missed_frames = 0;
}, 200);
};
function render() {
if(video.currentTime) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
// only for demo
else {
missed_frames++;
}
requestAnimationFrame(render);
}
render();
#_log { color: white; position: absolute; }
<pre id="_log"></pre>
<canvas></canvas>
An harder fix if you really need to loop seamlessly would be to use a MediaSource object, but if not really needed, that's a bit cumbersome to set in place.
You can set viedo.loop to true and avoid the part when you ask your video to go on playing.
I've did the trick this way :
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let src = "https://i.imgur.com/5ZiAeSX.mp4";
let video = document.createElement("video");
video.src = src;
video.muted = true;
video.videoHeight = '500px'; // returns the intrinsic height of the video
video.videoWidth = '100px'; // returns the intrinsic width of the video
video.play();
video.loop = true
/* video.onended = () => {
video.play();
}; */
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(video, 0, 0);
requestAnimationFrame(render);
}
render();
this is why your video flicker, it doesn't loop, it just replay again and again.
give it a try ;)
Related
I'm using my webcam to take screenshots from all the frames after pressing a button and store all of them in a folder. Is there any solution to solve this problem?
function takepicture() {
var context = canvas.getContext('2d');
if (width && height) {
canvas.width = width;
canvas.height = height;
context.drawImage(video, 0, 0, width, height);
var data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
} else {
clearphoto();
}
}
this function only can take the screenshot from the current frame but I want to store all the frames.
Try this fiddle in Chrome and in Firefox.
https://jsfiddle.net/lvl5hm/xosan6w9/29/
In Chrome it takes about 0.5-2ms to draw video to canvas, but FF for some reason takes 20-40, which is pretty insane.
Is there something that can help me improve FF performance?
const canvas = document.getElementById('canvas')
canvas.width = 500
canvas.height = 300
const ctx = canvas.getContext('2d')
const video = document.createElement('video')
video.src = 'https://static.beeline.ru/upload/images/business/delo/newmain/main.mp4'
video.muted = true
video.loop = true
video.oncanplay = () => {
video.play()
}
const frameCounterElement = document.getElementById('frameCounter')
let duration = 0
setInterval(() => {
frameCounterElement.innerHTML = duration.toFixed(2) + 'ms to render'
}, 400)
function loop() {
const startTime = performance.now()
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
duration = performance.now() - startTime
requestAnimationFrame(loop)
}
loop()
I need to render a video with HTML Canvas in red color. Code Below (if doesn't work, try here codepen code). This code add red layer, but I need in first place - desaturate, after reduce brightness and only then add red layer. I tried work with pixels (problems with perfomance), ctx.filter doesnt work either.
const URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
const video = document.createElement('video');
navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
}).then((stream) => {
video.src = URL.createObjectURL(stream);
});
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
const loop = () => {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(255, 0, 0, .45)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(loop);
};
loop();
<canvas id="canvas" width="400" height="400"></canvas>
You can use multiply blending mode to knock out the other colors channels. Since you only define red the other channels with be multiplied with 0, leaving them "empty".
Just remember to reset composite mode to "source-over" before drawing the next video frame.
var img = new Image(); img.onload = draw; img.src = "//i.imgur.com/Kzz84cr.png";
function draw() {
c.width = this.width; c.height = this.height;
var ctx = c.getContext("2d");
// main loop
ctx.drawImage(this, 0, 0); // draw video frame
ctx.globalCompositeOperation = "multiply"; // change blending mode
ctx.fillStyle = "red"; // draw red on top
ctx.fillRect(0, 0, c.width, c.height);
ctx.globalCompositeOperation = "source-over"; // "reset"
// rinse, repeat
}
<canvas id=c></canvas>
How to i can draw base64 video to the canvas ,
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var video = new Video();
video.onload = function() {
ctx.drawVideo(video, 0, 0);
//ctx.fillText("Hello World", 10,50);
};
video.src = "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21.......
aWxzdAAAACOpdG9vAAAAG2RhdGEAAAABAAAAAExhdmY1My4yNC4y";
I want to show some text over base64 video.
$(function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var video = document.createElement("VIDEO"); video.setAttribute("src","http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv");
video.play();
video.addEventListener('play', function() {
var $this = this; //cache
(function loop() {
if (!$this.paused && !$this.ended) {
ctx.drawImage($this, 0, 0);
ctx.fillText("Hello World", 10,50);
setTimeout(loop, 1000 / 30); // drawing at 30fps
}
})();
}, 0);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="theater">
<canvas id="canvas"></canvas>
</div>
There is no Video() class and no drawVideo() method present. You can only do using drawImage() by getting screen every interval, then add your text.
I'm trying to build a function that extracts frames from a video in JavaScript. Here the code I came up with. The function receives a source and a callback. From there, I create a video with the source and I want to draw frames of the video in the canvas with a set interval.
Unfortunately, the frames returned are all transparent images.
I tried a few different things, but I can't make it work. Can someone help?
Thanks.
const extractFramesFromVideo = function(src, callback) {
var video = document.createElement('video');
video.src = src;
video.addEventListener('loadeddata', function() {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.setAttribute('width', video.videoWidth);
canvas.setAttribute('height', video.videoHeight);
var frames = [];
var fps = 1; // Frames per seconds to
var interval = 1 / fps; // Frame interval
var maxDuration = 10; // 10 seconds max duration
var currentTime = 0; // Start at 0
while (currentTime < maxDuration) {
video.currentTime = currentTime;
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
var base64ImageData = canvas.toDataURL();
frames.push(base64ImageData);
currentTime += interval;
if (currentTime >= maxDuration) {
console.log(frames);
callback(frames);
}
}
});
}
export default extractFramesFromVideo;
After tweaking your code to wait for the seeked event, and fixing a few bits and pieces, it seems to work fine:
async function extractFramesFromVideo(videoUrl, fps=25) {
return new Promise(async (resolve) => {
// fully download it first (no buffering):
let videoBlob = await fetch(videoUrl).then(r => r.blob());
let videoObjectUrl = URL.createObjectURL(videoBlob);
let video = document.createElement("video");
let seekResolve;
video.addEventListener('seeked', async function() {
if(seekResolve) seekResolve();
});
video.addEventListener('loadeddata', async function() {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
let [w, h] = [video.videoWidth, video.videoHeight]
canvas.width = w;
canvas.height = h;
let frames = [];
let interval = 1 / fps;
let currentTime = 0;
let duration = video.duration;
while(currentTime < duration) {
video.currentTime = currentTime;
await new Promise(r => seekResolve=r);
context.drawImage(video, 0, 0, w, h);
let base64ImageData = canvas.toDataURL();
frames.push(base64ImageData);
currentTime += interval;
}
resolve(frames);
});
// set video src *after* listening to events in case it loads so fast
// that the events occur before we were listening.
video.src = videoObjectUrl;
});
}
Usage:
let frames = await extractFramesFromVideo("https://example.com/video.webm");
currentComponent.refs.videopreview is <video ref="videopreview" autoPlay></video> in HTML page
Below is the code to extract the frame from the video.
const getFrame = () => {
const video = currentComponent.refs.videopreview;
if(!video.srcObject) {
return null;
}
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
canvas.getContext('2d').drawImage(video, 0,0);
const data = canvas.toDataURL('image/jpeg');
return data; // frame data
}
getFrame function can be called at required interval.