Draw Canvas with video loop - javascript

I loop over a part of a HTML video and simultaneously draw a Canvas with the current frame of the video.
When the Videos starts over, there is always 1 gray frame at the canvas.
If the loop region is long, its not a big problem, but for my needs these regions are maybe 0.5 seconds and then the canvas starts to flicker if you loop over and over again.
When drawing the canvas, I also tried different video properties (ended, loop, networkState, readyState) - doesnt help
I provided a jsfiddle to show you my problem. (just press play at the video)
https://jsfiddle.net/Lz17fnf3/2/
$('#v').on('timeupdate', function () {
if ($('#v')[0].currentTime > 2) {//Loop for one second
$('#v')[0].currentTime = 1;
}
var $this = $('#v')[0]; //cache
(function loop() {
if (!$this.paused && !$this.ended) {
drawCanvas();
setTimeout(loop, 1000 / 25); // drawing at 25fps
}
})();
});
function drawCanvas() {
var elem = document.getElementById('c');
var c = elem.getContext('2d');
var v = $('#v')[0];
$('#c').attr('width', v.videoWidth);
$('#c').attr('height', v.videoHeight);
if (v.readyState == 4) {
c.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, v.videoWidth, v.videoHeight);
}
}

The reason it flickers is because when you assign the width or height to a canvas element, this action resets the entire context of the canvas, most likely that is causing the blank frame. Try moving all the canvas/context definitions outside the drawCanvas.
Something like:
var elem = document.getElementById('c');
var c = elem.getContext('2d');
var v = $('#v')[0];
// In order to set the canvas width & height, we need to wait for the video to load.
function init() {
if (v.readyState == 4) {
$('#c').attr('width', v.videoWidth);
$('#c').attr('height', v.videoHeight);
} else {
requestAnimationFrame(init);
}
}
init();
$('#v').on('timeupdate', function () {
if ($('#v')[0].currentTime > 2) { //Loop for one second
$('#v')[0].currentTime = 1;
}
var $this = $('#v')[0]; //cache
(function loop() {
if (!$this.paused && !$this.ended) {
drawCanvas();
setTimeout(loop, 1000 / 25); // drawing at 25fps
}
})();
});
function drawCanvas() {
c.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, v.videoWidth, v.videoHeight);
}

Related

HTML5 How to get first frame of a video

I am trying to get the first frame of a video in order to put it in a canvas, using this code but the canvas remains empty.
video.addEventListener("canplay",function () {
var $this = this;
console.log("canplay");
console.log(video.readyState);
canvas.getContext('2d').drawImage($this, 0, 0, canvas.width, canvas.height);
});
I also use this code to mirror the video in a canvas and it works.
video.addEventListener('play', function() {
var $this = this;
(function loop() {
if (!$this.paused && !$this.ended) {
context.save();
context.scale(-1, 1);
context.drawImage($this, 0, 0, mCanvas.width*-1, mCanvas.height);
context.restore();
setTimeout(loop, 1000 / 30); // drawing at 30fps
}
})();
}, 0);
How can I get the first frame of the video? Morevoer I have the possibility to change the URL dynamically: what should I do to get the first frame of the new video?

I want to start a new animation every time I click with requestFrameAnimation

I'm having multiple issues.
Everytime I click the animation goes faster. SOLVED #Jorge Fuentes González
Everytime I click the
last animation stops moving SOLVED #Kaiido
I have changed about everything I could think of around and still the same issue. Any help would be appreciated. Thanks!
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
function step() {
frameCount++;
if (frameCount < 30) {
window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
step();
}
==============================
JS Fiddle:
https://jsfiddle.net/HYUTS/q4fazt6L/9/
=======================================
Each time you click, you call step();, which will call window.requestAnimationFrame(step);, which will call step() the next animation frame. I don't see any stop point so the loop will be called forever.
So, when you call step() the first time, step() will be called continuously for ever, and if you click again, another step() "line" will be called a second time which will call window.requestAnimationFrame(step); for ever again, so now you will have two "lines" calling step(). That's why the animation goes faster, because on each animation frame step() will be called twice, doubling the calculations.
What you have to do is to check if the animation is already running (with a flag) and do not run it again, or to window.cancelAnimationFrame(ID) before starting the step() loop again. Note that on each click you must restart the variables that control the animation, like frameCount and currentLoopIndex
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
var animationid = null;
function step() {
frameCount++;
if (frameCount < 30) {
animationid = window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
animationid = window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
frameCount = currentLoopIndex = 0;
window.cancelAnimationFrame(animationid);
step();
}
First step in your situation, is to create different objects for every animatables, so they can be drawn and updated independently.
After, you will have to split your logic in several parts.
A basic setup is to have one main loop that runs constantly in the background, and which will call all higher level objects update function, then all the drawing functions.
It's in these higher level methods that you will do the checks as to whether they should actually be discarded or not. The main loop doesn't have to take care of it.
In the example below, I created a class for your animatable objects. These objects will now have their own status, and will be able to update as they wish independently of others.
With this setup, adding a new Object in the scene is just a matter of pushing it in an Array.
// Our Animatable class (ES5 style...)
// Each object as its own frameCount and its own loopIndex
function Animatable(x, y) {
this.x = x;
this.y = y;
this.frameCount = 0;
this.loopIndex = 0;
this.cycleLoop = [3, 2, 1, 0, 7, 6, 5];
}
Animatable.prototype = {
update: function() {
this.frameCount++;
if (this.frameCount < 30) {
return;
}
this.frameCount = 0;
this.loopIndex++
if (this.loopIndex >= this.cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
this.cycleLoop.reverse();
// Reseting position of which sprite to use
this.loopIndex = 0;
}
},
draw: function() {
// check the image is loaded
if (!img.naturalWidth) return;
var frameX = this.cycleLoop[this.loopIndex];
ctx.drawImage(img,
frameX * width, 0,
width, height,
this.x - scaledWidth/2, this.y - scaledHeight/2,
scaledWidth, scaledHeight);
}
};
// the main anim loop, independent
function startAnimLoop() {
animloop();
function animloop() {
requestAnimationFrame(animloop);
// updates
animatables.forEach(update);
// drawings
ctx.clearRect(0,0,canvas.width, canvas.height);
animatables.forEach(draw);
}
function update(animatable) {
animatable.update();
}
function draw(animatable) {
animatable.draw();
}
}
// one image for all
var img = new Image();
img.src = 'https://imgur.com/u2hjhwq.png';
img.onload = startAnimLoop;
// here we will hold all our objects
var animatables = [new Animatable(50, 50)]; // start with a single one
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
// some constant from OP's fiddle
var scale = 1.5;
var width = 100; // Bigger numbers push left <-, smaller right ->
var height = 100;
var scaledWidth = scale * width;
var scaledHeight = scale * height;
canvas.onclick = function(evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
// we simply create a new object ;-)
animatables.push(new Animatable(x, y));
};
canvas{border:1px solid}
<canvas id="canvas" width="500" height="500"></canvas>
window.requestAnimationFrame is still running when you click again, and when you click you add another tick per frame to your animation, doubling your speed, as step() is called two times each frame now. You should cancel the previous animation frame when clicking again, using window.cancelAnimationFrame()
https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame
Like this:
...
var animationID;
//in step() save the id in every call
function step() {
...
animationID = window.requestAnimationFrame(step);
...
}
//In getPosition cancel the current animation
function.getPosition(event) {
...
window.cancelAnimationFrame(animationId);
...
}
And if you want multiple animations running, create an object for each and make the function step() their property, then run window.requestAnimationFrame(this.step) inside of step(). You'd also have to save every variable needed for the animation like currentLoopIndex as part of the object.

Display canvas as gif for video preview

I'm working on a website and I am dealing with videos.
My need is to display a gif a a preview / teaser for the videos on another page, that redirects to the video.
What I found & added so far
HTML
<div id=thumbs></div>
CSS
#video {width:320px}
JS
var i =0;
var video = document.createElement("video");
var thumbs = document.getElementById("thumbs");
video.addEventListener('loadeddata', function() {
thumbs.innerHTML = "";
video.currentTime = i;
}, false);
video.addEventListener('seeked', function() {
var j = video.duration;
var u = j/4;
// now video has seeked and current frames will show
// at the time as we expect
generateThumbnail(i);
// when frame is captured, increase
i+=u;
// if we are not passed end, seek to next interval
if (i <= video.duration) {
// this will trigger another seeked event
video.currentTime = i;
}
}, false);
video.preload = "auto";
video.src = "https://www.html5rocks.com/en/tutorials/video/basics/devstories.webm";
function generateThumbnail() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 160;
c.height = 90;
ctx.drawImage(video, 0, 0, 160, 90);
thumbs.appendChild(c);
thumbs.replaceChild(c, thumbs.childNodes[0]);
}
What I do is I get a video from its Url and I get 5 frames at equal timing. It gives me canvas and I'd like to display them as a gif or a succession of images.
Since you only want to display it, instead of trying to generate a gif, the easiest way is probably to do the animation yourself.
You already have the code to fetch the video frames, but you are currently showing it in the DOM, and forget about it once displayed.
What you can do from this, is to store these frames, directly as canvas, and draw all these canvases on a final, visible canvas, in a timed loop.
var thumbsList = []; // we will save our frames as canvas in here
var delay = 500; // the speed of the animation (ms)
function generateThumbnail() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 160;
c.height = 90;
ctx.drawImage(video, 0, 0, 160, 90);
thumbsList.push(c); // store this frame in our list
if (thumbsList.length === 1) {
displayThumbs(); // start animating as soon as we got a frame
}
}
// initialises the display canvas, and starts the animation loop
function displayThumbs() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 160;
c.height = 90;
thumbs.appendChild(c);
startAnim(ctx); // pass our visible canvas' context
}
function startAnim(ctx) {
var currentFrame = 0;
// here is the actual loop
function anim() {
ctx.drawImage(thumbsList[currentFrame], 0, 0); // draw the currentFrame
// increase our counter, and set it to 0 if too large
currentFrame = (currentFrame + 1) % thumbsList.length;
setTimeout(anim, delay); // do it again in x ms
}
anim(); // let's go !
}
var i = 0;
var video = document.createElement("video");
var thumbs = document.getElementById("thumbs");
/* OP's code */
video.addEventListener('loadeddata', function() {
thumbs.innerHTML = "";
video.currentTime = i;
}, false);
video.addEventListener('seeked', function() {
var j = video.duration;
var u = j / 4;
// now video has seeked and current frames will show
// at the time as we expect
generateThumbnail(i);
// when frame is captured, increase
i += u;
// if we are not passed end, seek to next interval
if (i <= video.duration) {
// this will trigger another seeked event
video.currentTime = i;
} else {
// displayFrame(); // wait for all images to be parsed before animating
}
}, false);
video.preload = "auto";
video.src = "https://www.html5rocks.com/en/tutorials/video/basics/devstories.webm";
<div id=thumbs></div>

Why does my object (rectangle) flickers when I move my image?

When I move my object from the left to the right my rectangle (where I am supposed to put the high score and lives) flickers? How can I fix this? I have the same problem when I use a fillText, the text flickers each time I go to the left or right with my paddle.
var canvas = document.getElementById("mijnCanvas");
var mijnObject = canvas.getContext("2d");
var afbeelding = new Image();
var balkX = (canvas.width/2)-50;
var balkY = canvas.height-100;
function makenBalkKort() {
mijnObject.drawImage(afbeelding, balkX, balkY, afbeelding.width, afbeelding.height);
}
afbeelding.src = "Afbeeldingen/BrickSmasher_Balk_Kort.png";
function tekenenObjecten() {
mijnObject.clearRect(0, 0, canvas.width, canvas.height);
makenBalkKort();
drawFrame();
}
setInterval(tekenenObjecten, 20);
window.addEventListener("keydown", function LinksOfRechts() {
mijnObject.clearRect(balkX, balkY, canvas.width, canvas.height);
var balkNaarX = 15;
var code = event.which || event.keyCode;
if(code == 37) {
if(balkX > 0) {
balkX -= balkNaarX;
}
}
else if(code == 39) {
if(balkX < canvas.width-afbeelding.width) {
balkX += balkNaarX;
}
}
mijnObject.drawImage(afbeelding, balkX, balkY, afbeelding.width, afbeelding.height);
});
function drawFrame() {
mijnObject.beginPath();
mijnObject.fillStyle = "#000000";
mijnObject.strokeStyle = "#000000";
mijnObject.rect(0, 750, canvas.width, 50);
mijnObject.fill();
mijnObject.stroke();
mijnObject.closePath();
}
Here is my image:
Your image is flickering because you're clearing a large rectangle in your keydown event handler, and part of this covers the bottom area. There is then a delay of up to 20 milliseconds before the function that redraws the whole board is queued.
A simple, but dirty fix would be to adjust the area being cleared in the keydown handler to only cover the image area:
mijnObject.clearRect(balkX, balkY, afbeelding.width, afbeelding.height);
However, a better solution would be to avoid making any changes to the canvas in any event handlers; that is, to remove the clearRect and drawImage calls from the event handler. To ensure that the canvas refreshes as soon as possible with the updated state, you can then use requestAnimationFrame instead of setInterval to call the tekenenObjecten function:
function tekenenObjecten() {
mijnObject.clearRect(0, 0, canvas.width, canvas.height);
makenBalkKort();
drawFrame();
requestAnimationFrame(tekenenObjecten);
}
requestAnimationFrame(tekenenObjecten);

setInterval doesn't work properly

I have a image and I'm trying to animate it through setInterval() . My aim is to move the image up by a certain pixel only when the user clicks on the Canvas else the image should be moving downwards.Everything goes well but the timer increases the speed for every onclick()
my canvas tag
<canvas id="myCanvas" width="400" height="600" onclick=init()></canvas>
java script:
function init()
{
function draw1()
{
context.clearRect(0,0,400,600);
img_y = img_y - 40;
context.drawImage(image1,img_x,img_y);
}
function move()
{
context.clearRect(0,0,400,600);
img_y = img_y + 7;
context.drawImage(image1,img_x,img_y);
}
draw1();
setInterval(move,70);
}
My animation starts when the user clicks the Canvas of my game. When I click the Canvas for the second time or so,the animation speed increases. What is wrong with my logic?
Your problem is that each click ADDS an interval. So your code basically runs more times than you want (making your animation faster). Make sure to clear the interval before starting a new one. Try the following (docs):
window.myTimer = '';
function init()
{
function draw1()
{
context.clearRect(0,0,400,600);
img_y = img_y - 40;
context.drawImage(image1,img_x,img_y);
}
function move()
{
context.clearRect(0,0,400,600);
img_y = img_y + 7;
context.drawImage(image1,img_x,img_y);
}
draw1();
window.clearInterval(window.myTimer); // clear previous interval
window.myTimer = setInterval(move,70); // set it again
}
As #Pointy pointed out,, you are increasing the 'speed' every click, because you are creating a new interval which is calling that same function.
Consider either clearing the interval and making a new one each time : window.clearInterval(intervalId);
OR
implementing a bool within your function which will prevent its movement until click.
You are mixing up the code that initiates movement with the code that moves the object up. They need to be separate functions.
<canvas id="myCanvas" width="400" height="600" onclick="moveUp"></canvas>
JS:
var movement = 7, timer;
function animate() {
context.clearRect(0, 0, 400, 600);
img_y += 7;
context.drawImage(image1, img_x, img_y);
}
function moveUp() {
// no need to redraw the image here as it will be drawn at the next frame anyway
img_y -= 40;
}
function init() { // call this when you want the main animation to start
if (timer) { // if timer variable already exists
clearInterval(timer); // clear the corresponding interval
timer = null; // and also clear the variable itself
}
timer = setInterval(animate, 70);
}
(This code clears the timer before setting it, in case, for some reason, init is called more than once.)
You could also work with the onmouseup, onmousedown events, letting it move as long mouse is pressed, and stop moving when mouse is no longer pressed. This way you can create and remove the interval on both events
var interval;
function startInterval() {
interval = setInterval(animation.move.bind(animation), 70);
}
function stopInterval() {
clearInterval(interval);
}
var animation = {
old: undefined,
x: 0,
y: 0,
context: undefined,
move: function() {
this.y += 10;
animation.draw();
},
draw: function() {
if (typeof animation.context === 'undefined') {
var el = document.getElementById('ctx');
animation.context = el.getContext('2d');
}
var context = animation.context;
if (typeof animation.old !== 'undefined') {
context.clearRect(animation.old.x, animation.old.y, 10, 10);
}
animation.old = {
x: animation.x,
y: animation.y
};
context.fillStyle = "#FF0000";
context.fillRect(animation.old.x, animation.old.y, 10, 10);
}
};
<canvas id="ctx" width="300" height="300" onmousedown="startInterval()" onmouseup="stopInterval()"></canvas>

Categories

Resources