BogisW
BogisW

Reputation: 473

Javascript html audio element play error handling (neither ends nor shows any error)

Program purpose and description

I've created a javascript audio controller that runs perfectly in browsers on my Windows computer (for testing reasons), and on my android mobile phone. It is capable of

To achieve this last point, I have to listen to the audio.onended event, in order wait with the start of the following sound until the previous sound has finished playing.

Problem description

The javascript audio controller is part of a mobile application, thus it has to run on mobile/smartphone browsers. When I run the same app it in iPhone Safari, a few sounds are played, but then a certain sound is not played. On order to troubleshoot this, I have added an onerror event listener, but neither the onended handler, nor the

Code

   async playNwait(audio) { // plays sound and waits until it has finished
      return new Promise ((resolve) => {
         audio.onerror = (event) => {
            log('Play Error: ' + audio.error.code);
            log('Play Error: ' + audio.error.message);
            log('Play Error: ' + event.currentTarget.error.code);
            log('Play Error: ' + event.currentTarget.error.message);
            reject('Audio failed');
         };
         audio.onended = (event) => {
            log('      playNwait ended');
            resolve('Audio ended');
         };
         log('      playNwait started');
         log(audio.src);
         audio.addEventListener("error", function(e) {
            log("Playback error: " + e.currentTarget.error.code);
            var prop, props='\nEvent properties are: ';
            var o = e.currentTarget.error;
            for (prop in o) {
                props += prop+'='+o[prop]+' ';
             }
            log(props);
            reject('Audio failed');
         });         
         audio.play();
      });
   }

The function log calls a console.log function with the same parameter, and also sends the value via an API to a server, where it is logged in a database table, on order to analyse the log entries for mobile browsers.

I know I have two error handlers that have the same purpose. None of them fires.

Log entries

For easy comparison, here are the relevant log entries generated by Firefox on Andriod and Safari on iOS:

Firefox on Android

"Playing 4: 60_sechzig"     2021-05-28 23:40:28
"      playNwait started"   2021-05-28 23:40:28
"data:audio\/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPA..."  2021-05-28 23:40:28
"      playNwait ended"     2021-05-28 23:40:29
"Played 4"  2021-05-28 23:40:29

iPhone Safari

"Playing 4: 60_sechzig"     2021-05-28 22:21:13
"      playNwait started"   2021-05-28 22:21:13
"data:audio\/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPA..."  2021-05-28 22:21:13

... and nothing more - neither the onended nor one of the error handler fires. Moreover, further sounds are not played, as the app wrongly assumes, that the previous sound has not finished playing. (It doesn't start playing.)

4 is just the number of the sound in the "play list".

Question

How can I get the onerror event handler working? How can I further troubleshoot this?

Any help is highly appreciated.

Upvotes: 1

Views: 2524

Answers (1)

BogisW
BogisW

Reputation: 473

Problem identified: The audio play function failed on iOS Safari due to a NotAllowedError: "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission."

Here is my updated function that reports the error (and adds some debugging):

   async playNwait(audio) { // plays sound an wait until it has finished
      return new Promise (async (resolve, reject) => {
         audio.onerror = (event) => {
            log('Play Error: ' + audio.error.code);
            log('Play Error: ' + audio.error.message);
            log('Play Error: ' + event.currentTarget.error.code);
            log('Play Error: ' + event.currentTarget.error.message);
            reject('Audio failed');
         };
         audio.abort = (event) => {
            log('      playNwait abort');
         };
         audio.loadstart = (event) => {
            log('      playNwait loadstart');
         };
         audio.onplay = (event) => {
            log('      playNwait playing');
         };
         audio.onended = (event) => {
            log('      playNwait ended');
            resolve('Audio ended');
         };
         log('      playNwait started');
         log(audio.src);
         audio.addEventListener("error", function(e) {
            log("Playback error: " + e.currentTarget.error.code);
            var prop, props='\nEvent properties are: ';
            var o = e.currentTarget.error;
            for (prop in o) {
                props += prop+'='+o[prop]+' ';
             }
            log(props);
            reject('Audio failed');
         });         
         try {
            await audio.play();
         } catch (err) {
            error ('Media play error: ' + err.name + ' - ' + err.message);
         }
      });
   }

I know that iOS Safari blocks playing audio when it was not initialized by user interactio. I know I had a successful solution for that in the past, that is, to load the audio files on user interaction.

However, this is a different problem, and this question here is addressed and solved: The error handler never fires because the play method already fails.

Upvotes: 5

Related Questions