DOMException - play() リクエストが中断される

François Beaufort
François Beaufort

Chrome DevTools JavaScript コンソールで、このような予期せぬメディアエラーに遭遇したことはありますか?

or

そんな時はお任せください。ご安心ください。この問題の原因修正方法を説明いたします。

原因

以下は、「Uncunt (in promise)」というエラーを再現する JavaScript コードです。

すべきでないこと
<video id="video" preload="none" src="https://example.com/file.mp4"></video>

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

上記のコードにより、Chrome DevTools で次のエラー メッセージが表示されます。

_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 DevTools にエラー メッセージが表示されます。

解決方法

根本原因がわかったところで、修正方法を見てみましょう。

まず、メディア要素(動画や音声)が再生されると想定しないでください。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 Promise のサポート

この記事の執筆時点で、HTMLMediaElement.play()Chrome、Edge、Firefox、Opera、Safari で Promise を返します。

危険ゾーン

<video> 内の <source> により、play() Promise が拒否されなくなります。

<video src="not-existing-video.mp4"\> の場合、動画が存在しないため、play() Promise は期待どおりに拒否されます。<video><source src="not-existing-video.mp4" type='video/mp4'></video> の場合、play() Promise は拒否されません。これは、有効なソースがない場合にのみ発生します。

Chromium のバグ