0

I'm trying to add sound and animation to this "ping-pong" (fizz-buzz) program. What I want to happen is for each item in the array (number/ping/pong/pingpong) the text fades in on the list while the sound and animation play.

What I'm getting is - the whole list fades in all at once, the sounds all play one at a time (and if the number entered is large it goes on forever) but the animation happens only once (apparently for just the last animation).

The whole project is at: https://github.com/karenfreemansmith/Epic-AdvancedJSwk1-PingPongCalculator, along with a link to a page with what is currently working. (Slightly earlier than the code below, which has only broken it in new ways.)

I've been trying to use setInterval and setTimeout to sync them all by calling a function that will show one element at a time with it's sound and animation:

var play=setInterval(function() {
  var i=1;
  output.forEach(item => {
    showNext(item);
    if(i>=output.length) {
      clearInterval(play);
    }
    i++;
  });
}, 1000);

And the function looks like this:

function showNext(item) {
  acorn.style.animation= "";
  acorn.style.webkitAnimation="";
  if(item==="ping") {
    window.setTimeout(playPing(), 1000);
  } else if(item==="pong") {
    window.setTimeout(playPong(), 1000);
  } else if(item==="ping-pong") {
    window.setTimeout(playVolley(), 1000);
  } else {
    window.setTimeout(playMiss(), 1000);
    $("#pingpong").append("<p class='"+item+"'>" + item + "</p>");
  }
}

and the play functions are all basically the same, but with different sounds:

function playPing() {
  acorn.style.animation= "ping 1s linear";
  acorn.style.webkitAnimation="ping 1s linear";
  sndSlam1.currentTime = 0;
  sndSlam1.play();
  $("#pingpong").append("<p class='ping'>ping</p>");
}

I think I must be misunderstanding how the setTimeout is working. Why does the animation only play once? And why is there no pause between the elements being added to the list?

KAS
  • 53
  • 1
  • 5
  • See [HTML5 audio streaming: precisely measure latency?](http://stackoverflow.com/questions/38768375/html5-audio-streaming-precisely-measure-latency/) – guest271314 Jan 04 '17 at 23:08
  • I'm not streaming the audio, just using .mp3 files to play ping-pong sounds. It looks like the events here are mainly checking to see if the file has downloaded or not. – KAS Jan 05 '17 at 18:13
  • The media is fetched, then `timeupdate` or `progress` events can be used to determine `.currentTime` of media playback to apply specific styles or animations. – guest271314 Jan 05 '17 at 18:17
  • I have only one animation per sound file, the animation plays (or at least starts) first, and then all the sounds, but no more animations. I find this strange because the animation is the one that matches the LAST sound. It looks like there is also an "ended" event, so if I use an eventListener and .then() to play the animations? – KAS Jan 05 '17 at 18:45
  • Can you create a jsfiddle http://jsfiddle.net or plnkr http://plnkr.co to demonstrate? – guest271314 Jan 05 '17 at 18:51

1 Answers1

1

The problem here is because, yes, you do slightly misunderstand how setTimeout works.

What you do is call it like this:

window.setTimeout(playVolley(), 1000);

Which is equivalent to saying: "hey JS, immediately execute my function playVolley (since I use () to specify that I want it called), and THEN in 1000 seconds call whatever it has returned".

What, I strongly suspect, you really wanted to do, is:

window.setTimeout(playVolley, 1000);

Note how there are no "()" after playVolley. This is equivalent to saying: "hey JS, in 1000 seconds execute my cool func called playVolley".

If "passing function name without ()" doesn't make sense to you, that's okay, just read about "functions as first-class objects" (for example, here). The idea is just any function is really like a variable which holds a "function" in it, and you can pass it to anything takes "function as an input. Which, for example, setTimeout does - it needs a "function" and an "integer" to set a timeout.

But only fixing this won't help you. There's another problem here:

output.forEach(item => {
  showNext(item);
  ...
}

See, here you effectively set output.length timeouts, all of them at once, to fire in 1000 seconds. Which they will do - in 1000 all of them will be executed simultaneously. So all you'll fix by the first fix is that all of your animations and sounds will play not immediately, but after a 1000ms delay.

What, I again strongly suspect, you wanted to do is to call every step of output array one by one, with 1000 delay between each other.

To achieve this you'll need to refactor the way you schedule your calls. Instead of scheduling them all at once, you'll need to chain them. A dirty, but simple example would be to have an index to current animation step, and when your playXXX finishes, it schedules next step to run, until all the steps are completed.

var currentAnimationStep = 0;
var output = ["ping", "pong", "ping", "pong"];

snowNext(output[currentAnimationStep]);

function showNext(item) {
+  if (item === undefined) {
+    return;
+  }
+
  ...
}

function playPing() {
  acorn.style.animation= "ping 1s linear";
  acorn.style.webkitAnimation="ping 1s linear";
  sndSlam1.currentTime = 0;
  sndSlam1.play();
  $("#pingpong").append("<p class='ping'>ping</p>");

+ currentAnimationStep += 1;
+ showNext(output[currentAnimationStep]);
}

// All other playXXX functions will need the same call added

Again, this is a very dirty example (globals, eeeew), don't tell anybody I showed you this, but it can get you started, and when you'll get a hang of closures, you'll rewrite it to something more manageable.

  • And yes, you might also experience some audio latency problems, but I don't think you really will, and anyway this you'll take care about when you'll get to them (in a new question, I suppose). – Andrey Stolbovsky Jan 04 '17 at 23:34
  • I don't like the globals, but it works great, thank you! – KAS Jan 05 '17 at 20:31
  • I hate them, too, but it's the easiest way to start. You can also add some closures to encapsulate these steps, or use some chaining methods from some library, I suppose even jQuery must have some. I'm really not familiar with these libs, back in a day I used "async" library to do all this stuff, but I blinked a couple of times back then and the whole JS landscape had changed, so I'm afraid can't help you there. – Andrey Stolbovsky Jan 06 '17 at 06:27