skip to Main Content

I’m attempting to make a game, and in it I have music (like most games). However, changing the track or even looping the same one tends to come with a very slight delay. I want to remove that using pure vanilla Javascript.

I tried this:

// Sets an object with music URLs
const mus = {
    'Surface': "https://codeberg.org/NerdB0I/dungeoncrawlerost/raw/branch/main/mus/Surface.wav",
    'Deeper': "https://codeberg.org/NerdB0I/dungeoncrawlerost/raw/branch/main/mus/Deeper.wav"
} 
// This ensures that music will play and wait for each other using Promises 
function musPlay(name='') {
     return new Promise(resolve => {
         let audio = new Audio(mus[name]);
         audio.onended = () => resolve();
         audio.play();
     });
 }  
// This runs once everytime the song ends and plays a new song based on the variable currSong 
let currSong = 'Surface'; 
async function run() {
     await musPlay(currSong);
     run();
} 
run();  
// This allows you to press keys to change the song (and to see what key pressed) 
// I do not mind that the song continues playing after keydown, I just need it to set the currSong variable for when the files end 
window.addEventListener('keydown', (event) => {
     if (event.key == 'a') {
         currSong = 'Surface';
     } else if (event.key == 'd') {
         currSong = 'Deeper';
     }
     document.getElementById('body').innerHTML = event.key;
});

My Liveweave
If this is just a problem of Liveweave and not code, please let me know.

2

Answers


  1. Creating a new audio element each time you play a new audio file will make a pause, because the audio is not loaded. Start the script by loading all the audio files, and then use the same audio element instead of creating ones each time.

    // Sets an object with music URLs
    let mus = {
      'Surface': "https://codeberg.org/NerdB0I/dungeoncrawlerost/raw/branch/main/mus/Surface.wav",
      'Deeper': "https://codeberg.org/NerdB0I/dungeoncrawlerost/raw/branch/main/mus/Deeper.wav"
    };
    
    // This runs once everytime the song ends and plays a new song based on the variable currSong
    let currSong = 'Surface';
    
    let audiopromises = Object.keys(mus).map(key => {
      return new Promise(resolve => {
        let audio = new Audio();
        audio.addEventListener('canplay', e => {
          resolve({key: key, elm: e.target});
        });
        audio.addEventListener('ended', e => {
          musPlay();
        });
        audio.src = mus[key];
      });
    });
    
    Promise.all(audiopromises).then(audio_arr => {
      mus = audio_arr;
      musPlay();
    });
    
    function musPlay(){
      let audio = mus.find(audio_obj => audio_obj.key == currSong);
      audio.elm.play();
    }
    
    document.addEventListener('keydown', (event) => {
      if (event.key == 'a') {
        currSong = 'Surface';
      } else if (event.key == 'd') {
        currSong = 'Deeper';
      }
      document.body.innerHTML = event.key;
    });
    Login or Signup to reply.
  2. While I agree with with @chrwahl about pre-loading and not creating a new Audio every time, it’s not the only cause of the gap. As there is still an amount of time between the ended event and the starting of the playback, the gap remains

    Easiest is to set .loop = true on the audio, until a different track is required, then set .loop = false, the ended will fire when the end is reached, and a new audio track can be started

    You’ll note this code does not "preload" the audio, yet there is no stutter, because the audio is loaded only when changed

    Depending on how much audio you have, I would probably still "pre load" the audio, but that’s not important to the issue you have.

    const mus = {
        'Surface': "https://codeberg.org/NerdB0I/dungeoncrawlerost/raw/branch/main/mus/Surface.wav",
        'Deeper': "https://codeberg.org/NerdB0I/dungeoncrawlerost/raw/branch/main/mus/Deeper.wav"
    }
    const { musPlay, setLoop } = (function () {
        let audio;
        let playing;
    
        return {
            musPlay(name = '') {
                return new Promise(resolve => {
                    playing = name;
                    audio = new Audio(mus[name]);
                    audio.onended = resolve;
                    audio.loop = true;
                    audio.play();
                });
            },
            setLoop(name) {
                if (audio) {
                    audio.loop = name === playing;
                }
            }
        }
    })();
    
    let currSong = 'Surface';
    async function run() {
        await musPlay(currSong);
        run();
    }
    run();
    
    window.addEventListener('keydown', (event) => {
        if (event.key == 'a') {
            currSong = 'Surface';
        } else if (event.key == 'd') {
            currSong = 'Deeper';
        }
        // this sets the .loop property appropriately
        setLoop(currSong);
        document.getElementById('body').innerHTML = event.key;
    });
    <div id="body"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search