SuprMan
SuprMan

Reputation: 380

html5 audio: distingish NotAllowedError and NotSupportedError

Most browsers no longer allow html audio to play without some user interaction first. They recommend handing the NotAllowedError with something like:

async function playAudio() {
  try {
    await audioEl.play();
    console.log('playing')
  } catch(err) {
    console.log('err',err)
    // NotAllowedError, Ask for user interaction to confirm audio play.
  }
}

But: this also catches the NotSupportedError which occurs if the user has gone offline or the media source is bad.

What is the proper way to parse the error in order to distinguish and handle two different errors separately?

Logging the error in Firefox gives:

DOMException: The media resource indicated by the src attribute or assigned media provider object was not suitable.

and for NotSupportedError

DOMException: The play method is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

for NotAllowedError but parsing that with js doesn't make sense.

Sadly the example section of mozilla's article on DOMExceptions is blank https://developer.mozilla.org/en-US/docs/Web/API/DOMException/DOMException

Upvotes: 0

Views: 2314

Answers (1)

Kaiido
Kaiido

Reputation: 136736

Both latest Chrome and Firefox do pass a reason to the rejected promise, you could thus check it.

new Audio("bafile.mp3").play().catch(console.log)

But note that they didn't always did.
Also note that the NotAllowedError will always win: even if the resource can't be loaded, it is the reason that will get output.

So an other solution, able to distinguish between both and that will work even in prior versions that didn't have this rejection message, would be to wait for whatever is the first to fire between the loadedmetadata and error events. If one of these fires, you know it failed because of a NotAllowedError or because of a NotSupportedError respectively.

function test( url ) {

  const aud = new Audio( url );
  const event_prom = Promise.race( [
    promisifyEvent( aud, "error" ),
    promisifyEvent( aud, "loadedmetadata" )
  ] );
  
  aud.play()  
    .then( () => {
       log.textContent += "\nERROR: " + url + " was authorized to play";
    } )
    .catch( async (reason) => {
        const evt = await event_prom;
      if( evt.type === "error" ) {
        log.textContent += "\n" + url + ": network error";
      }
      else {
        log.textContent += "\n" + url + ": not authorized";
      }
    });
}
test( "https://upload.wikimedia.org/wikipedia/en/transcoded/d/dc/Strawberry_Fields_Forever_%28Beatles_song_-_sample%29.ogg/Strawberry_Fields_Forever_%28Beatles_song_-_sample%29.ogg.mp3" );
test( "badfile.mp3" );

function promisifyEvent( target, event_name ) {
  return new Promise( (resolve) =>
    target.addEventListener( event_name, resolve, { once: true } )
  );
}
<pre id="log"></pre>

As a jsfiddle since it might be easier to control the authorizations.

Upvotes: 2

Related Questions