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,這是以非同步方式傳回單一結果的函式。如果播放成功,系統會完成 Promise,並同時觸發 playing 事件。如果播放失敗,系統會拒絕 Promise,並顯示說明失敗的錯誤訊息。

請按照下列步驟操作:

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

由於我們無法在程式碼中處理影片播放 Promise,因此 Chrome 開發人員工具會顯示錯誤訊息。

修正方式

瞭解根本原因後,接著來看看該如何修正這個問題。

首先,不要假設媒體元素 (影片或音訊) 會播放。請查看 play 函式傳回的 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 Commit 支援

撰寫本文時,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 錯誤