HTML Canvas animate sequence images is slow on IPAD - javascript

I have built a script which takes a sequence of images and displays them on a canvas element in a animation loop.
This works really well on my desktop, but on IPAD (3 retina) it is very slow. Could you suggest any way to improve the performance?
var videoItts = 0;
function playVideo() {
if(videoItts < 92) {
setTimeout(function() {
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.drawImage(imagesL[videoItts],0,0,1024,636);
requestAnimationFrame(playVideo);
videoItts ++;
}, 1000/22)
}
}
requestAnimationFrame(playVideo);
The imagesL is an array of pre-loaded images.

I would suggest not mixing setTimeout and requestAnimationFrame. You can solve it using only requestAnimationFrame:
var startTime = Date.now();
var fps = 22;
var lastDrawnIndex = null;
var totalFrames = 92;
function drawVideo() {
var currTime = Date.now();
var currFrameIndex = Math.round((currTime - startTime) / (1000/fps)) % totalFrames;
// Since requestAnimationFrame usually fires at 60 fps,
// we only need to draw the image if the frame to draw
// actually has changed from last call to requestAnimationFrame
if (currFrameIndex !== lastDrawnIndex) {
ctx.drawImage(imagesL[videoItts],0,0,1024,636);
lastDrawnIndex = currFrameIndex;
}
requestAnimationFrame(drawVideo);
}
requestAnimationFrame(drawVideo);
The idea is that for every call to requestAnimationFrame we calculate, based on the elapsed time and the desired animation frame rate, which frame index to draw. If it's different from last calculated frame index we draw it. Then we schedule drawVideo to be called next animation frame by calling requestAnimationFrame(drawVideo) at the end.
The code above will loop frames 0-91 continously at 22 fps. I removed the ctx.clearRect call, it is only needed if the frames contains transparency. So you might want to add that back.

Related

Crack sounds if I don't release immediately like wait for a setTimeout

I am playing with the Web Audio API. If I play the notes automatically and set the release right after attack, it sounds ok.
But when I use the piano, and set the release to wait until the key is released, (either a setTimeout for example), it produces a cracking sounds.
This demonstrates the issue very well.
Please fix this I need this.
let context = new AudioContext()
function nocrack() {
let r = play()
r(0.2)
}
function crack() {
let r = play()
setTimeout(() => {
r(0.2)
}, 200)
}
function play() {
let time = context.currentTime
let gain = context.createGain()
gain.gain.setValueAtTime(1, time)
gain.connect(context.destination)
let osc1 = new OscillatorNode(context, { type: 'triangle', detune: 15 })
let osc1_mix = new GainNode(context)
osc1.connect(osc1_mix)
osc1_mix.gain.setValueAtTime(0.5, time)
osc1_mix.connect(gain)
osc1.start(time)
return (r) => {
let now = context.currentTime
osc1_mix.gain.linearRampToValueAtTime(0, now + r)
osc1.stop(now + r)
}
}
<button onclick="nocrack()">
Play a Note
</button>
<button onclick="crack()">
Play Crackie
</button>
I think you can fix the problem by adding one line to your stop/fade-out function.
return (r) => {
const now = context.currentTime;
osc1_mix.gain.setValueAtTime(0.5, now);
osc1_mix.gain.linearRampToValueAtTime(0, now + r);
osc1.stop(now + r);
}
When scheduling a linear ramp with linearRampToValueAtTime() the starting point of that ramp is the last event preceding the ramp in the timeline. If you schedule the ramp directly it doesn't really matter. But if you do so after a while it results in a noticeable sudden volume drop heard as a click.
Let's say you start your sound at a currentTime of 10. And then you stop it 1 second later. That means you schedule a ramp which starts at a currentTime of 10 and ends at a currentTime of 11.2. But at this point currentTime is already at 11. That means the browser immediately lowers the volume to match the volume which it should have when applying about 83% of the ramp and continues from there.
I guess what you wanted to achieve was a fade-out of 0.2 seconds from the original volume starting at the time the sound gets stopped. This can be achieved by inserting a new event in the timeline right before starting the ramp.

Control enemys speed RequestAnimationFrame

i need help to discover how to do this
Basicly im trying to build a game like pacman, i have a matrix that defined where is the blocks, the pacman, the enemys and the balls, here is my matrix.
var MeshDataPac =
[
"------------------------------",
"-ooooooooooooo--ooooooooooooo-",
"-o----o------o--o------o----o-",
"-o----o------o--o------o----o-",
"-oooooooooooooooooooooooooooo-",
"-o----o--o----------o--o----o-",
"-oooooo--ooooo--ooooo--oooooo-",
"------o------ -- ------o------",
"------o- -o------",
"------o- ---- ---- -o------",
"- o- - e - -o -",
"------o- ---------- -o------",
"------o- -o------",
"------o- ---------- -o------",
"-ooooooooooooo--ooooooooooooo-",
"-o----o------o--o------o----o-",
"-ooo--oooooooo--ooooooooooooo-",
"---o--oooo oooo------",
"---o--o--o-----------o-o--o---",
"-oooooo--ooooo--oooooo-o--ooo-",
"-o-----------o--o-----------o-",
"-oooooooooooooooXoooooooooooo-",
"------------------------------"
];
to discover colisions between my pacman and the blocks and balls i discover where is the position of the "-" and "o" and the movement on each keypress is always the SCALE that i gave at the begin on creating the map for example 10, this kinda works well.
But now i need to animate the enemys "e" and i want that they move like when i press a key but randomly, if i try to increment the position on my animate function with the same as the pacman the speed is to fast, i want to control that speed but i also want that he moves always 1 block on my matrix, the only way that i know to reduce speed in this case is to put the position increment lower, but i cant do that as i said, because i want him to move 10 or SCALE each time
Tryed to use SetTimeout inside my enemyMove function and tryed to give a timeout to request animationframe but first seems like bad approach, then it becomes kinda buggy
here is my keypress function
function handleKeyPressed(e) {
var PacMan = scene.getObjectByName('PacMan');
gameSong.play();
switch(String.fromCharCode(e.which))
{
case "w": if(!detectaColisao(PacMan.position.x, PacMan.position.y + SCALE))
PacMan.position.y += SCALE;
break;
case "a": if(!detectaColisao(PacMan.position.x - SCALE, PacMan.position.y))
PacMan.position.x -= SCALE;
break;
case "s": if(!detectaColisao(PacMan.position.x, PacMan.position.y - SCALE))
PacMan.position.y -= SCALE;
break;
case "d": if(!detectaColisao(PacMan.position.x + SCALE, PacMan.position.y))
PacMan.position.x += SCALE;
break;
}
}
function anima()
{
moveEnemys();
var delta=clock.getDelta();
orbitCamera.update(delta);
requestAnimationFrame( anima);
renderer.render(scene, camera);
}
how can i do that the moveEnemys become slower but without changing the number of position increment that i want, that i have a animation like when i press a key?
Right now, the moveEnemys function gets called every time anima is run. As you've mentioned, this means moveEnemys is happening too many times and causing your enemies to move too quickly. Try replacing your anima function with this:
var enemyTimer = null;
function anima() {
if (enemyTimer == null) {
// The enemy timer has not been created yet
// Making the second argument 1000 means moveEnemys will get called
// once per second
enemyTimer = setInterval(moveEnemys, 1000);
}
var delta=clock.getDelta();
orbitCamera.update(delta);
requestAnimationFrame( anima);
renderer.render(scene, camera);
}
This way, the first time anima is called, you set up an interval causing moveEnemys to happen once per second. Modify 1000 as necessary to change how often moveEnemys occcurs.
requestAnimationFrame callbacks have current time stamp as the first argument and you should utilize that to run your app at a desired frame rate. This is how IMO the mainloop for most games should look like:
var _prevTime = 0.0;
var _at = 0.0;
var _stepSize = SOME_NUMBER;
var _frame = 0;
function loop(newTime){
newTime /= 1000;
var tick = newTime - _prevTime;
_prevTime = newTime;
tick = Math.max(tick, SOME_MAX_TICK); // important: prevent slow updates from spiraling out of control.
_at += tick;
while (_at > _stepsize){
_at -= _stepsize;
_frame += 1;
CALL_UPDATE_HERE();
}
CALL_DRAW_HERE();
requestAnimationFrame(loop);
}

Jump around in a video to times from an array

Following along the lines of Control start position and duration of play in HTML5 video, I am trying to make a video jump from one segment to the next automatically when each has finished playing. Each segment will have the same duration, and the start times for each segment will be in an array.
I can't seem to figure out how to loop through the array after addEventListener.
var video = document.getElementById('player1');
function settheVariables() {
var videoStartTime= ["4","15","26","39"];
for (var i = 0; i < videoStartTime.length; i++) {
if (video.currentTime < videoStartTime[0] ){
video.currentTime = videoStartTime[i];
}
durationTime = 5;
}
//This part works when I plug in numbers for videoStartTime.
video.addEventListener('timeupdate', function() {
if(this.currentTime > (// videoStartTime [current index] + durationTime)){
this.currentTime = (// videoStartTime with next index);
video.play(); }
});
}
you need to change the values in your array to integers, not strings - you're not comparing apples to apples.
the updated and somewhat simplified sample below plays (initially from the start of the video) until the timestamp hits the current marker plus five seconds then jumps to the next marker (and loops back around).
it doesn't cater for the user scrubbing the video themselves (though it will trap as soon as they go >5s past the start of the current section, but going back will confuse things a little) - if you want to control within those 5s boundaries you'll want to do some smarter examination of the time stamp vs the array to make sure you're where you're supposed to be
anyway ... the code:
<script>
var video = document.getElementById('player1');
var videoStartTime= [4,15,26,39];
durationTime = 5;
currentIndex=0;
video.addEventListener('timeupdate', function() {
// console.log(this.currentTime);
if (this.currentTime > (videoStartTime[currentIndex] + durationTime))
{
currentIndex = (currentIndex + 1) % videoStartTime.length // this just loops us back around
this.currentTime = videoStartTime[currentIndex];
// video.play(); // don't need this if the video is already playing
}
});
</script>

How can one force the browser to redraw an image?

I'm working on a JavaScript game that involves throwing a snowball. I need the snowball to render as often as possible during its flight path. Chrome does all the calculations, including setting the style.left and style.top properties, but doesn't actually redraw the snowball until it reaches its destination. Opera doesn't have this problem.
A relevant point is that putting in an alert() after renderSnowball() fixes the problem, except using the alert() is an obvious issue.
Here's my code so far:
function throwSnowball()
{
var theta = parseFloat(angleField.value) * Math.PI/180 ;
var Vir = parseFloat(velocityField.value) ;
if (!isNaN(Vir) && !isNaN(theta) )
{
Vix = Math.cos(theta) * Vir * 50;
Viy = Math.sin(theta) * Vir * 50;
time = new Date() ;
var timeThrown = time.getTime() ;
while (snowballPosY > 0)
{
current = new Date() ;
var currentTime = current.getTime() ;
var timeElapsed = (currentTime - timeThrown)/5000 ;
snowballPosX += Vix * timeElapsed;
snowballPosY += Viy * timeElapsed;
Viy -= GRAVITY * timeElapsed ;
renderSnowball() ; //renderSnowball() sets the style.left
// and style.top properties to snowballPosX pixels
// and snowballPosY pixels respectively
timeThrown = currentTime ;
}
snowballPosX = 0 ;
snowballPosY = 50 ;
renderSnowball() ;
}
}
You're totally blocking the main thread. Have you tried using a setTimeout (even with a zero timeout) to allow other things to happen during your animation?
If you're willing to use experimental technology, requestAnimationFrame would be even better.
Edit: the setTimeout approach would look something like this (replacing the while loop):
var drawAndWait = function() {
if (snowballPosY > 0) {
// movement/drawing code here
setTimeout(drawAndWait, 20 /* milliseconds */);
} else {
// reset code that would normally go after your while loop
}
};
drawAndWait();
So each time the drawing finishes, it arranges for itself to be invoked again, if appropriate. Note that your throwSnowball function will return quickly; the throwing isn't actually done until later on. This takes awhile to get used to doing correctly; don't be too concerned if it's not intuitive at first.
Try getting out of the tight loop. Chrome may not want to redraw until your function exits. Try using setInterval or setTimeout to give Chrome a chance to repaint.

How to make a real Javascript timer

I'm looking for a way to manipulate animation without using libraries
and as usual I make a setTimeout in another setTimout in order to smooth the UI
but I want to make a more accurate function to do it, so if I want to make a 50ms-per-piece
animation, and I type:
............
sum=0,
copy=(new Date()).getMilliseconds()
function change(){
var curTime=(new Date()).getMilliseconds(),
diff=(1000+(curTime-copy))%1000 //caculate the time between each setTimeout
console.log("diff time spam: ",diff)
sum+=diff
copy=curTime
var cur=parseInt(p.style.width)
if (sum<47){//ignore small error
//if time sum is less than 47,since we want a 50ms-per animation
// we wait to count the sum to more than the number
console.log("still wating: ",sum)
}
else{
//here the sum is bigger what we want,so make the UI change
console.log("------------runing: ",sum)
sum=0 //reset the sum to caculate the next diff
if(cur < 100)
{
p.style.width=++cur+"px"
}
else{
clearInterval(temp)
}
}
}
var temp=setInterval(change,10)
I don't know the core thought of my code is right,anyone get some ideas about how to make a more accurate timer in most browser?
Set the JsFiddle url:
http://jsfiddle.net/lanston/Vzdau/1/
Looks too complicated to me, use setInterval and one start date, like:
var start = +new Date();
var frame = -1;
var timer = setInterval(checkIfNewFrame, 20);
function checkIfNewFrame () {
var diff = +new Date() - start;
var f = Math.floor(diff / 50);
if (f > frame) {
// use one of these, depending on whether skip or animate lost frames
++frame; // in case you do not skip
frame = f; // in case you do skip
moveAnimation();
}
}
function moveAnimation () {
... do whatever you want, there is new frame, clear timer past last one
}

Categories