Audio being skipped during fast ontouch IOS

47 Views Asked by At

I have created a flip tile memory game. A sequence of tiles will flip over displaying a colour. The user must remember the sequence and repeat it. When the user selects correctly a correct Mp 3 is played. On the Iphone if the tiles are selected quickly the audio isn't being played for each touch, its as though the audio is being skipped for some. link

const elements = {
    gameContainer: $('#game-container'),
    gameMenu: $('#game-menu'),
    audioPlayer: document.querySelector('#player'),
    audioPlayer2: document.querySelector('#player2'),
    audioPlayer3: document.querySelector('#player3'),
    tiles: $('.tile'),
    correctAlert: $('#correct-alert'),
    wrongAlert: $('#wrong-alert'),
    failAlert: $('#fail-alert'),
    alertModal: $('#alert-modal'),
    stageNumber: $('.stage-number'),
    maxStageNumber: $('.max-stage-number'),
    gamemodeCheckbox: $('#gamemode-checkbox'),
    stageProgress: $('#stage-progress'),
    waitText: $('#wait-text'),
    wonAlert: $('#won'),
    goText: $('#go-text')
};

function tileClicked(tile) {
  console.dir(tile)
  // only allow clicking on tiles when game is started and game is not showing pattern
  if (!game.showing && game.started && !tile.classList.contains('flip-card-onclick')) {

    flipTile(tile);

    // check if game reached maximum number of stages i.e. game has been won
    if (game.playerMove <= game.maxStageNumber) {

      // check if current move (tile clicked) matches the tile in the generated pattern
      if (parseInt(tile.id) == game.currentGame[game.playerMove]) {
        // increase the pattern pointer
        game.playerMove++;

        // play sound when correct tile has been clicked
        elements.audioPlayer.pause();
        elements.audioPlayer.currentTime = 0;
        elements.audioPlayer.play();



        // check if we reached the end of the current pattern
        if (game.playerMove == game.currentGame.length) {
          // update the progress bar
          elements.stageProgress.css('width', `${(game.currentGame.length / game.maxStageNumber) * 100}%`);

          // show alert prompting user to go to the next stage
          elements.correctAlert.modal('show');
        }
        // current move did not match current pattern, wrong move
      } else {

        if (game.strictGamemode) {
          elements.audioPlayer2.play();
          // show fail alert and prompt to restart or exit game if strict mode has been selected
          elements.failAlert.modal('show');
        } else {
          // show wrong move alert and prompt to show pattern again
          elements.audioPlayer2.play();
          elements.wrongAlert.modal('show');
        }
      }
    }
  }
}
<!--Audio Player-->
<audio controls id="player" class="d-none">
         <source id="player-src" src="assets/audio/correct.mp3">
      </audio>
<audio controls id="player2" class="d-none">
         <source id="player-src-2" src="assets/audio/incorrect.mp3">
      </audio>
<audio controls id="player3" class="d-none">
         <source id ="player-src-3" src="assets/audio/won.mp3">
   </audio>

1

There are 1 best solutions below

0
Emiel Zuurbier On

It is very hard to tell where your bug comes from, so your solution may not be so easy to find. Some research might tell you some things but otherwise there is an alternative you could try.

The Web Audio API is an interface where you can get more control over the audio you play. So in your case instead of manipulating the <audio> element, use the Web Audio API to play your audio files.

Down here I've created a snippet which utilizes this API. It currently selects all of your <audio> elements and extracts the sound into a node which then can use to play the sound. This gives you control over how the sound plays.

So here it creates an object, which is stored in the sounds constant, that holds all the names as keys and the players as values. Something like this:

const sounds {
  'correct': MediaElementAudioSourceNode,
  'incorrect': MediaElementAudioSourceNode,
  'won': MediaElementAudioSourceNode
};

Each of those MediaElementAudioSourceNode are the sounds which can be played. Later in the script there is a playSound function, which plays one of the sounds found in your sounds object.

const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();

const audioElements = document.querySelectorAll('audio');

const createAudioSources = audioElements => {
    const audioSources = {};
    for (const audioElement of audioElements) {
        const name = audioElement.dataset.name;
        const track = audioContext.createMediaElementSource(audioElement);
        audioSources[name] = track;
    }
    return audioSources;
};

const sounds = createAudioSources(audioElements);

function playSound(track) {
    const sound = sounds[track];
    if (sound === undefined) return;
    sound.connect(audioContext.destination);
    sound.start(audioContext.currentTime);
};

playSound('correct');
playSound('incorrect');
playSound('won');

So all of this could be added above your original script so that the sound files are loaded and ready for use. And then use the playSound() function anywhere in your script whenever you want to play anyone of the sounds. Example below:

function tileClicked(tile) {
  console.dir(tile)
  // only allow clicking on tiles when game is started and game is not showing pattern
  if (!game.showing && game.started && !tile.classList.contains('flip-card-onclick')) {

    flipTile(tile);

    // check if game reached maximum number of stages i.e. game has been won
    if (game.playerMove <= game.maxStageNumber) {

      // check if current move (tile clicked) matches the tile in the generated pattern
      if (parseInt(tile.id) == game.currentGame[game.playerMove]) {
        // increase the pattern pointer
        game.playerMove++;

        // play sound when correct tile has been clicked
        playSound('correct');

        // check if we reached the end of the current pattern
        if (game.playerMove == game.currentGame.length) {
          // update the progress bar
          elements.stageProgress.css('width', `${(game.currentGame.length / game.maxStageNumber) * 100}%`);

          // show alert prompting user to go to the next stage
          elements.correctAlert.modal('show');
        }
        // current move did not match current pattern, wrong move
      } else {

        if (game.strictGamemode) {
          playSound('incorrect');
          // show fail alert and prompt to restart or exit game if strict mode has been selected
          elements.failAlert.modal('show');
        } else {
          // show wrong move alert and prompt to show pattern again
          playSound('incorrect');
          elements.wrongAlert.modal('show');
        }
      }
    }
  }
}

Also, add a data-name attribute to each <audio> element so that JavaScript knows how to call each player and it's accompanying sound.

<audio controls id="player" class="d-none" data-name="correct">
  <source id="player-src" src="assets/audio/correct.mp3">
</audio>
<audio controls id="player2" class="d-none" data-name="incorrect">
  <source id="player-src-2" src="assets/audio/incorrect.mp3">
</audio>
<audio controls id="player3" class="d-none" data-name="won">
  <source id ="player-src-3" src="assets/audio/won.mp3">
</audio>

All my code above is untested and might throw an error, or worse, makes no difference at all. But hey, at least it's worth a shot.