0

I'm trying to create a DTMF keypad emulator using only HTML, CSS, and JavaScript,

I'm having a small problem with sound. When an image is clicked, that runs the offHook() function which starts playing dial tone. However, I want that dial tone to stop as soon as the first number is clicked (or pressed once I had a keyboard listener). For each number function i.e. dial1(), dial2(), etc. which is tied up to the individual buttons that are clicked, I run the numberDial() function, which is supposed to stop the dial tone audio. However, it refuses to stop.

I have tried:

-dialTone.pause();

-dialTone.stop();

-dialTone.src = "";

Regardless, the dial tone continues. How can I get it to stop when numberDial() is run? This function is run every time a number is pressed (I'll add other stuff later) so it will only be stopping the audio the first number, but it shouldn't do any harm the other times either.

My understanding is that the function numberDial() should recognize the variable dialTone - so why is dialTone refusing to pause?

function offHook() {
  document.getElementById("WE2500").style.display = "none";
  document.getElementById("dialPad").style.display = "block";
  var dialTone = new Audio('dialTone.m4a');
  dialTone.play();
}

var number = "";

function numberDial() {
  dialTone.src = "";
}

function dial1() {
  numberDial();
  number = number + "1";
  var tone1 = new Audio('DTMF-1.wav');
  tone1.play();
}
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
InterLinked
  • 1,247
  • 2
  • 18
  • 50

1 Answers1

0

The problem here is "my understanding is that the function numberDial() should recognize the variable dialTone". Your understanding is very wrong indeed: variables declared with an allocator keyword (var,let, or `const) inside functions do not persist outside those functions. This is pretty much how every conventional programming language works, so the fact that you thought it would persist is... curious? It might be worth reading up on how Javascript works.

The only time this is not true is if you don't use an allocator at all to declare your variables, but this is an extreme bad practice: always use an allocator keyword and make sure you're properly scoping them.

So, let's do that: the var dialTone that you define in your offHook() function is only accessible inside the offHook function right. If you need access to it at the global level, it'll have to be declared it at the global level, even if you don't then initialize it until you call your offHook function:

var dialTone;

function offHook() {
  ...
  dialTone = new Audio('dialTone.m4a'); // note: NO 'var'. It already exists.
  dialTone.play();
}

function numberDial() {
  // we now need to make sure we only stop dialTone if
  // the dialTone variable actually points to something.
  if (dialTone) {
    // see http://stackoverflow.com/questions/14834520 on why this "stops" audio.
    dialTone.pause();
    dialTone.currentTime = 0;
  }
}
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • It would even be better to do `var dialTone = new Audio(XXX)` in the global and only `dialTone.play()` in `offHook`. No need to create a new AudioElement every time, nor to download the media again and again. – Kaiido May 17 '17 at 05:29
  • 1
    No, it wouldn't. It would be insanely much better to turn this code into a class object that allocates a `this.dialTone` during construction, with event handling hooked into the object, so that we don't pollute the global namespace and can create any number of instances. – Mike 'Pomax' Kamermans May 17 '17 at 06:04
  • Of course an class object would be better, the best would even be to load the media as an AudioBuffer and control its playing through the AudioContext API ; but avoiding to create an new HTMLAudioElement + loading the resource at each iteration is already a great improvement compared to what you are proposing here, and would still be comprehensible to OP's apparent current skills. And when I said `global` it didn't mean `window`'s global, but the function's scope's one, be it the `this` of your class object. – Kaiido May 17 '17 at 06:15
  • @Mike'Pomax'Kamermans Thanks - I watched an MVA the other day about JavaScript for about 4 hours straight and I remember him talking about how JavaScript was unique in that variables persist ... maybe that was if you don't use var which makes the variable global in scope... – InterLinked May 17 '17 at 11:25
  • correct, there at there allocators in modern Javascript (`var`, `let`, and `const`) each with their own scoping and redeclaration rules, and a fourth "secret" allocator which is "not using one at all". Only for that last one, where you don't use an allocator keyword, does the variable get bound to the global scope. But if you want to write good code, *always* use an allocator keyword =) – Mike 'Pomax' Kamermans May 17 '17 at 16:30
  • @Kaiido unfortunately (or, actually the opposite) you don't get a choice in what you mean when you say "global scope". The word has a well-defined meaning and referse to "the broadest execution scope for the code". In the browser, that's the `window` namespace, in a module that's "anywhere in the module file", etc. It never means "function scoped" or "class instance scoped" because those things are always defined "in some kind of file" and global scope for that code is always whatever scope the file itself is assigned. – Mike 'Pomax' Kamermans May 17 '17 at 16:35
  • You still understand that when I used "global" it was to talk about your first `var dialTone;` declaration. I don't mind if you pollute the window object or not, what I care about is that your code won't work if e.g we call twice `offHook`. Then two AudioElements will be created, each one requesting the media resource, and each one playing independently., and since you'd have lost the reference for the first one, you won't even be able to stop it. – Kaiido May 18 '17 at 00:27