HTML5 audio element: Visual elements of button to play audio do not update upon click but wait until audio starts to play (mobile iOS issue)

  Kiến thức lập trình

Background

I have a button that plays the audio connected to a nearby audio element when a click event is detected. The button element has a nested img element whose src I change to point to various .svg files depending on on whether audio is

  1. playing (pause icon)
  2. paused (play icon)
  3. about to play but hasn’t started playing yet (rotating buffering icon)

Problem

The switch to the pause or play icon happens with no problems at all, but the switch to the buffering icon happens so late it isn’t really useful (i.e., user clicks the play button, user waits for however long, and the buffering icon flashes for a few ms right before the audio starts playing).

On desktop, this is all very snappy and the buffering icon wouldn’t even be needed, but on mobile (iOS) there can be a one to four second delay from when the user clicks the play button and the audio actually starts playing (which is why I want a buffering icon for that wait time).

Code

HTML structure

There are a whole slew of these li elements. The real focus here is on the button.playOrPauseBtn element:

<li>
    <audio
        id="TR-21"
        class="dialogueAudio"
        src="/static/audio/en/es/end/tr-21.mp3"
        preload="auto"
    />
    </audio>
    <div>
        <button type="button" class="svgBtn playOrPauseBtn" >
            <img class="playIcon" src="/static/svg/play.svg" height="22px" width="22px" />
        </button>
    </div>
</li>

javascript

The focus here would be the event listener at the bottom, which I would expect to immediately change the UI of the play/pause button to my buffer icon right when the button is clicked, but on mobile (iOS; haven’t tested android), it actually waits and then only flashes the buffer icon right before the audio starts playing (1 to 4 second wait, depending on audio).

If it seems weird that everything is separated into lots of mini functions, it’s because these are extremely simplified versions of the actual functions (I’m trying to only focus on the problem at hand). They do a lot more with the UI, such as changing title and alt text for various elements. Let me know if you think it would be helpful to see the JS code and HTML markup in their original, unsimplified form.

/* ----------------------------------------------
------------------- FUNCTIONS -------------------
---------------------------------------------- */

const changeAudioControlBtn = function(btn, svg, disabled=false, spinny=false) {
  // get other elements
  let btnImg = btn.querySelector("img");
  // change UI of btn
  btn.disabled = disabled;
  // change UI of img
  btnImg.src = svg;
  if (spinny === false) {
    btnImg.classList.remove("spinny");
  } else {
    btnImg.classList.add("spinny");
  }
}

/* Function to pause all audios */
const pauseAll = async function() {
  let allYeAudios = document.querySelectorAll("audio");
  allYeAudios.forEach((item, i) => {
    item.pause()
  });
}

/* Function for playing or pausing audio associated with play/pause button */
const playOrPause = async function(soundByte) {
  if (soundByte.paused == true) {
    // pause other audios before playing
    await pauseAll();
    soundByte.play();
  } else {
    soundByte.pause();
  }
};

/* Function to change play/pause button icon to play or pause symbol */
const changePlayOrPauseGraphics = function() {
  let playPauseBtn = this.parentElement.querySelector(".playOrPauseBtn");
  if (this.paused === true) {
    // set properties for function to change UI of play/pause btn
    let btn = playPauseBtn;
    let svg = "/static/svg/play.svg";
    changeAudioControlBtn(btn, svg);
    }
  } else {
    // set properties for function to change UI of play/pause btn
    let btn = playPauseBtn;
    let svg = "/static/svg/pause.svg";
    changeAudioControlBtn(btn, svg);
  }
};

/* ----------------------------------------------
---------------- EVENT LISTENERS ----------------
---------------------------------------------- */

const allDialogueAudios = document.querySelectorAll(".dialogueAudio");

// Change visuals if playing
allDialogueAudios.forEach((item, i) => {
  item.addEventListener("playing", changePlayOrPauseGraphics);
});

const allPlayButtons = document.querySelectorAll(".playOrPauseBtn");

// Play buttons
allPlayButtons.forEach((item, i) => {
  item.addEventListener("click", () => {
    // set properties for UI function (to change btn to spinny buffer icon)
    let btn = item;
    let svg = "/static/svg/loady_spinner.svg";
    let disabled = true;
    let spinny = true;
    changeAudioControlBtn(btn, title, svg, alt, disabled, spinny);
    let soundByte = item.parentElement.parentElement.querySelector(".dialogueAudio");
    playOrPause(soundByte);
  });
});

What I’ve tried

I used to have the code that calls the changeAudioControlBtn() function in order to change the UI of the playOrPauseBtn to a buffer icon inside the playOrPause() function, and the event listener handler for the play/pause buttons was much simpler (it just called the playOrPause() function, but I thought moving the code for the buffer icon change to the event listener handler would be more immediate. But that didn’t have any effect.

Other details

The issue does not seem to be Safari related because it does not happen on Safari desktop, and the same issue happens on my iOS device on Chrome, Safari, and Firefox browsers.

LEAVE A COMMENT