html5 音频:区分 NotAllowedError 和 NotSupportedError

html5 audio: distingish NotAllowedError and NotSupportedError

大多数浏览器不再允许 html 音频在没有用户交互的情况下播放。他们 recommendNotAllowedError 交给 NotAllowedError 类似的东西:

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.
  }
}

但是:这也捕获了 NotSupportedError 如果用户离线或媒体源有问题就会发生。

为了区分和处理两种不同的错误,正确的错误解析方法是什么?

在 Firefox 中记录错误给出:

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

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 但用 js 解析没有意义。

遗憾的是,mozilla 关于 DOMExceptions 的文章的示例部分是空白的 https://developer.mozilla.org/en-US/docs/Web/API/DOMException/DOMException

最新的 Chrome 和 Firefox 都会向拒绝的承诺传递原因,因此您可以检查一下。

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

但请注意,他们并非总是如此。
另请注意,NotAllowedError 将始终获胜:即使资源无法加载,也是将得到输出的原因。

因此,另一种解决方案能够区分两者,并且即使在没有此拒绝消息的先前版本中也能正常工作,那就是等待 loadedmetadata 之间第一个触发的任何内容和 error 个事件。 如果这些火灾之一,你知道它失败是因为 NotAllowedErrorNotSupportedError 分别。

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 因为这样可能更容易控制权限。