DOMException - play() 要求中斷

François Beaufort
François Beaufort

您是否在 Chrome 開發人員工具 JavaScript 控制台中意外發現這個非預期的媒體錯誤?

你來對地方了。別擔心,我會說明造成這個問題的原因,以及如何修正

造成這個問題的原因

以下是一些 JavaScript 程式碼,可重現您看到的「Uncaught (in promise)」錯誤:

錯誤做法
<video id="video" preload="none" src="https://example.com/file.mp4"></video>

<script>
  video.play(); // <-- This is asynchronous!
  video.pause();
</script>

上方的程式碼會在 Chrome 開發人員工具中產生以下錯誤訊息:

_Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause().

由於 preload="none" 未載入影片,因此影片播放不一定會在執行 video.play() 後立即開始。

此外,自 Chrome 50 起,對 <video><audio> 元素的 play() 呼叫會傳回 Promise,這是會以非同步方式傳回單一結果的函式。如果播放成功,承諾就會達成,同時觸發 playing 事件。如果播放失敗,系統會拒絕 Promise,並附上說明失敗原因的錯誤訊息。

以下是發生的情況:

  1. video.play() 會開始以非同步方式載入影片內容。
  2. video.pause() 會中斷影片載入作業,因為影片尚未準備就緒。
  3. video.play() 以非同步方式大聲拒絕。

由於我們未在程式碼中處理影片播放 Promise,因此 Chrome DevTools 會顯示錯誤訊息。

修正方式

瞭解根本原因後,我們來看看如何解決這個問題。

首先,請勿假設媒體元素 (影片或音訊) 會播放。查看 play 函式傳回的 Promise,瞭解 Promise 是否遭到拒絕。值得注意的是,Promise 必須等到播放實際開始後才會完成,也就是說,媒體必須播放後,then() 內的程式碼才會執行。

正確做法

範例:自動播放

<video id="video" preload="none" src="https://example.com/file.mp4"></video>

<script>
  // Show loading animation.
  var playPromise = video.play();

  if (playPromise !== undefined) {
    playPromise.then(_ => {
      // Automatic playback started!
      // Show playing UI.
    })
    .catch(error => {
      // Auto-play was prevented
      // Show paused UI.
    });
  }
</script>
正確做法

示例:播放及暫停

<video id="video" preload="none" src="https://example.com/file.mp4"></video>
 
<script>
  // Show loading animation.
  var playPromise = video.play();
 
  if (playPromise !== undefined) {
    playPromise.then(_ => {
      // Automatic playback started!
      // Show playing UI.
      // We can now safely pause video...
      video.pause();
    })
    .catch(error => {
      // Auto-play was prevented
      // Show paused UI.
    });
  }
</script>

這個簡單的範例很實用,但如果您使用 video.play() 來稍後播放影片,又會如何呢?

我要告訴你一個秘密。您不必使用 video.play(),也可以使用 video.load(),方法如下:

正確做法

示例:擷取並播放

<video id="video"></video>
<button id="button"></button>

<script>
  button.addEventListener('click', onButtonClick);

  function onButtonClick() {
    // This will allow us to play video later...
    video.load();
    fetchVideoAndPlay();
  }

  function fetchVideoAndPlay() {
    fetch('https://example.com/file.mp4')
    .then(response => response.blob())
    .then(blob => {
      video.srcObject = blob;
      return video.play();
    })
    .then(_ => {
      // Video playback started ;)
    })
    .catch(e => {
      // Video playback failed ;(
    })
  }
</script>

Play 承諾支援

在撰寫本文時,HTMLMediaElement.play() 會在 Chrome、Edge、Firefox、Opera 和 Safari 中傳回承諾。

危險區

<video> 中的 <source> 會讓 play() 承諾永遠不會遭到拒絕

對於 <video src="not-existing-video.mp4"\>play() 應許會因影片不存在而遭到拒絕,針對 <video><source src="not-existing-video.mp4" type='video/mp4'></video>play() 承諾永遠不會拒絕。只有在沒有有效來源時才會發生。

Chromium 錯誤