Search code examples
javascripthtmlonclickhtml5-audio

Audio play button firing phantom second click when currentTime is modified programmatically, preventing playback


When I pause an Audio and set it's currentTime, it does something weird. when I press a button that controls the .paused state of the Audio, it seems to click again automatically, preventing the audio from playing. Why?

Additionally, if I set the currentTime again, it decides to stop clicking the play button twice. this happens on and off every time I set the currentTime. it can be demonstrated with this code. (slowed audio is only because the example audio is too short).

<!doctype html><html><body>
  <button id="play" disabled>play</button>
  <button id="idk">load audio</button>
  <script>
    var clicks = 0; console.clear();
    idk = document.getElementById("idk");
    idk.addEventListener("click", async ()=>{
      aud = new Audio("https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3")
      aud.addEventListener('canplaythrough', init);
    }, {once: true});
    function init(){
      /**/aud.playbackRate = 0.25;//only to lengthen the audio to give you time.
      playButton = document.getElementById("play");
      playButton.disabled = false;
      idk.innerText = "pause and set time to 1";
      playButton.addEventListener("click", e=>{
        if(aud.paused){
          aud.play();
          playButton.innerText = "pause";
        }else{
          aud.pause();
          playButton.innerText = "play";
        }
        //Nothing different between duplicate click events, logging it takes up space
        console.log(clicks++, aud.paused, /*e*/);//clicks++ to tell apart logs.
      });
      idk.addEventListener("click", e=>{
        console.log("Rightmost element clicked");
        if(!aud.paused){ aud.pause(); playButton.innerText = "play"; }
        aud.currentTime = 1;
      });
    }
  </script>
</body></html>

See live example (removed event logging due to pollution of console)

simply click load audio, then click play once permitted, and finally click on pause and set time to 1.

after such, the click event will be fired twice per actual click (as seen in the log), preventing the audio from playing. the only way to fix it is to click pause and set time to 1 again, and attempt clicking play. You will be successful, but if you click the right button again, you will face this issue again.

I also noticed that the double-clicking turns into quadruple-clicking when modified slightly, still causing the issue, as 4 clicks is an even number.

<!doctype html><html><body>
  <button id="play">play</button>
  Control+Click to pause the audio and change it's currentTime value
  <script>
    var clicks = 0; console.clear();    
    playButton = document.getElementById("play");
    playButton.addEventListener("click", async ()=>{
      /**/window.aud = new Audio("https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3");
      aud.addEventListener('canplaythrough', ()=>{
        playButton.style.color = "#080";
        /**/aud.playbackRate = 0.25;//only to lengthen the audio to give you time.
        playButton.addEventListener("click", e=>{
          if(e.ctrlKey){
            if(!aud.paused){ aud.pause(); playButton.innerText = "play"; }
            aud.currentTime = 1;
            return;
          }
          if(aud.paused){
            aud.play();
            playButton.innerText = "pause";
          }else{
            aud.pause();
            playButton.innerText = "play";
          }
          //Nothing different between duplicate click events, logging it takes up space
          console.log(clicks++, aud.paused, /*e*/);//clicks++ to tell apart logs.
        });
      });
    }, {once: true});
  </script>
</body></html>

See live example (removed event logging due to pollution of console)

to use the second example, use CTRL+Click as the pause and set time to 1 button.

in both examples, running aud.play(); in the console seems to work, but still doesn't allow the play/pause button to act and double clicks them. Setting aud.currentTime in the console (after it has been set an even number of times) has no effect, and neither does having the code set aud.currentTime twice in succession. The user has to click it and then click it again.

This occurs on Chrome Beta 127.0.6533.94 on ChromeOS, as well as Chrome 127.0.6533.85 and Firefox 129.0 on Android 14. I Can't get it to work at all on non-EU iOS.

HOWEVER...

Safari 17.3 on MacOS Sonoma (via Browserling, if any difference made) appears to work as expected.

Sorry in advance, if that's insufficient testing, or I made a really obvious mistake.

This is a simplification pinpoint of a bug occurring on a larger codebase (minus "base", but still significantly larger than these codes). Some of the proposed changes might not be compatible with my case, therefore, I would very much like to (and prefer to, Even without fixes, but would be happy to recieve a fix as well) understand simply-put what is actually happening and why. If a fix is included, please change very little if possible. all i know is when currentTime is changed, the code doesnt function as expected, and when otherwise, it pauses and works perfectly. No extraneous events are being listened to, as found in DevTools, async is left there by accident, it is a part of the real codebase.

The code including "idk" is the focus, and the other is just code i came across trying to simplify the code further for stackoverflow, until i found it quadruple-clicked.


Solution

  • The problem is that Chromium and Firefox hate when I put the play button's "click" event listener inside init (the listener function, not the addEventListener call). the answer involves placing and defining that function anywhere outside of the Audio.oncanplaythrough event listener function, like so:

    <!doctype html><html><body>
      <button id="play" disabled>play</button>
      <button id="idk">load audio</button>
      <script>
        var clicks = 0; console.clear();
        idk = document.getElementById("idk");
        idk.addEventListener("click", async ()=>{
          aud = new Audio("https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3")
          aud.addEventListener('canplaythrough', init);
        }, {once: true});
        function playButtonClick(e){
          if(aud.paused){
            aud.play();
            playButton.innerText = "pause";
          }else{
            aud.pause();
            playButton.innerText = "play";
          }
          //Nothing different between duplicate click events, logging it takes up space
          console.log(clicks++, aud.paused, /*e*/);//clicks++ to tell apart logs.
        }
        function init(){
          /**/aud.playbackRate = 0.25;//only to lengthen the audio to give you time.
          playButton = document.getElementById("play");
          playButton.disabled = false;
          idk.innerText = "pause and set time to 1";
          playButton.addEventListener("click", playButtonClick);
          idk.addEventListener("click", e=>{
            console.log("Rightmost element clicked");
            if(!aud.paused){ aud.pause(); playButton.innerText = "play"; }
            aud.currentTime = 1;
          });
        }
      </script>
    </body></html>