Audio having a weird behaviour while being played

I have a problem with playing audio when I press or click a button.

[https://gamintor.github.io/DM_VanillaJS.github.io/q]

It seems like my audio has a delay but I put a audio.currentTime = 0 , so I don’t know what’s going on.

Here is my JS:

const $ = document.querySelectorAll.bind(document);

const sounds = [
    {id:'Bass Drum', letter:'Q', src:'https://dight310.byu.edu/media/audio/FreeLoops.com/3/3/Free%20Kick%20Sample%2011-909-Free-Loops.com.mp3'}
];
let volumeVal = 0.5;

$('input[type=range]')[0].addEventListener('input', function(e) {
    volumeVal = e.target.value / 100;
});

function main(url, name) {
    const audio = new Audio(url);
    audio.currentTime = 0;
    audio.preload = "auto";
    audio.volume = parseFloat(volumeVal);
    audio.play();
    $('.name')[0].textContent = name;
}

window.onkeydown = function(e) {
    function seter(id) {
        setTimeout(() => {
            $(`.${id}-but`)[0].classList.add('pressed');
            setTimeout(() => {
                $(`.${id}-but`)[0].classList.remove('pressed');
            }, 140);
        }, 0);
    }
    switch(e.key) {
        case 'q':
            main(sounds[0].src, sounds[0].id);
            seter('q');
        break;
    }
}

$('.q-but')[0].addEventListener('click', () => {
    main(sounds[0].src, sounds[0].id);
});

There is going to be an initial delay as it has to fetch the audio file before it can play it. I would suggest you download all the files and put them in a folder then reference them using a file path instead.

If instead of using the Audio() constructor the audio elements were in the DOM with the preload attribute the browser should (might) start to pre-fetch on page load.

1 Like

What’s the alternative to using new Audio() ?
I think that is the only way to play sounds in JS, or is it?

You can use an HTML audio element.

<audio preload="auto" src="https://dight310.byu.edu/media/audio/FreeLoops.com/3/3/Free%20Kick%20Sample%2011-909-Free-Loops.com.mp3"></audio>

<button>Play</button>
const audio = document.querySelector("audio");

document.querySelector("button").addEventListener("click", () => {
  audio.currentTime = 0;
  audio.play();
});

Having the audio element in the DOM when the page loads should/may make the browser prefetch the file. It might happen with or without the preload attribute. I believe most browsers will use metadata as the default value if the attribute is not added.

preload

The default value is different for each browser. The spec advises it to be set to metadata .

1 Like

Yeah this works but now my code is a lot bigger and do You have any tips how to reduce it considering that I have to do this for 9 separate buttons.
This is my current JS file [DM_VanillaJS.github.io/index.js at main · Gamintor/DM_VanillaJS.github.io · GitHub]

If you were using React (or some other framework) you would just map the sounds object into the DOM.

You can still do that using plain JS. Loop the object and construct the elements and add them to the DOM. There are a few ways of doing it here is one example using Template literals.

1 Like

But I don’t think I should add extra elements to the DOM, all I am doing in JS is setting event listeners for my buttons. Or I don’t fully understand what do You exactly want me to do?

I’m saying if you want the audio elements in the DOM you can do it programmatically at run time instead of having to write it manually in the HTML. That’s all.

Whether or not you want to use audio elements is up to you. But like I said it has some benefits. I would suggest you at least start by downloading the audio files and reference them as local files.

Well I said in the previous comment that now I use the HTML audio element and that it solves my issue. I just wrote elements manually, I don’t mind that. So everything works fine right now. And the only question I had after that was: Can I somehow reduce my JS code because it’s repetitive. Could I maybe create a main function to reduce the total number of lines or something else dunno?

Start by looking at the code you are repeating. Think about what is the same and what is different. Then think about how you can combine the common code but still allow for differences where needed.

I know this might sound vague, but it’s good practice. Just think about it a bit and play around with the code. If you run into any specific issues or questions just post them.

1 Like

One thing I would suggest is to use querySelectorAll for the button element. You get a NodeList back and can use forEach for attaching the event listeners. There is really no need to manually attach the listeners to each button using the button class.

If you put each of the audio elements inside their corresponding button element you can also more easily get to the audio element inside the forEach loop.

For the sounds[index].id; you can use the index of the loop for the index.

1 Like

Here it is:

const audioElements = $('audio');
const display = $('.name')[0];

$('button').forEach((but, i) => {
    but.addEventListener('click', () => {
        let audio = audioElements[i];
        audio.currentTime = 0;
        audio.volume = parseFloat(volumeVal);
        audio.play();
        display.textContent = sounds[i].id;
    });
});

Looks great now.
From 90 lines down to 10.

1 Like