DOMException - Yêu cầu play() bị gián đoạn

François Beaufort
François Beaufort

Bạn có gặp lỗi nội dung nghe nhìn ngoài dự kiến này trong Bảng điều khiển JavaScript của Chrome DevTools không?

hoặc

Vậy thì bạn đã đến đúng nơi rồi. Đừng lo. Tôi sẽ giải thích nguyên nhân gây ra vấn đề nàycách khắc phục.

Nguyên nhân gây ra vấn đề này

Dưới đây là một số mã JavaScript giúp tái hiện lỗi "Uncaught (in promise)" (Không phát hiện được (trong lời hứa)) mà bạn đang thấy:

Không nên
<video id="video" preload="none" src="https://example.com/file.mp4"></video>

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

Mã ở trên sẽ dẫn đến thông báo lỗi này trong Công cụ của Chrome cho nhà phát triển:

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

Vì video không được tải do preload="none", nên quá trình phát video không nhất thiết phải bắt đầu ngay sau khi video.play() được thực thi.

Hơn nữa, kể từ Chrome 50, lệnh gọi play() trên phần tử <video> hoặc <audio> sẽ trả về một Promise (Lời hứa), một hàm trả về một kết quả không đồng bộ. Nếu phát thành công, Lời hứa sẽ được thực hiện và sự kiện playing sẽ được kích hoạt cùng lúc. Nếu không phát được, Lời hứa sẽ bị từ chối cùng với thông báo lỗi giải thích lý do không phát được.

Sau đây là những gì đang xảy ra:

  1. video.play() bắt đầu tải nội dung video không đồng bộ.
  2. video.pause() làm gián đoạn quá trình tải video vì video chưa sẵn sàng.
  3. video.play() từ chối không đồng bộ một cách to tiếng.

Vì chúng ta không xử lý Lời hứa phát video trong mã, nên một thông báo lỗi sẽ xuất hiện trong Công cụ cho nhà phát triển Chrome.

Cách khắc phục

Giờ đây, khi đã hiểu rõ nguyên nhân gốc rễ, hãy xem chúng ta có thể làm gì để khắc phục vấn đề này.

Trước tiên, đừng bao giờ giả định rằng một thành phần nội dung nghe nhìn (video hoặc âm thanh) sẽ phát. Hãy xem Promise do hàm play trả về để xem liệu Promise đó có bị từ chối hay không. Xin lưu ý rằng Lời hứa sẽ không thực hiện cho đến khi quá trình phát thực sự bắt đầu, nghĩa là mã bên trong then() sẽ không thực thi cho đến khi nội dung nghe nhìn đang phát.

Nên

Ví dụ: Tự động phát

<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>
Nên

Ví dụ: Phát và tạm dừng

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

Đó là một ví dụ đơn giản nhưng tuyệt vời, nhưng nếu bạn sử dụng video.play() để có thể phát video sau thì sao?

Tôi sẽ cho bạn biết một bí mật. Bạn không nhất thiết phải sử dụng video.play(), bạn có thể sử dụng video.load() và sau đây là cách thực hiện:

Nên

Ví dụ: Tìm nạp và phát

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

Hỗ trợ về cam kết của Play

Tại thời điểm viết, HTMLMediaElement.play() trả về một lời hứa trong Chrome, Edge, Firefox, Opera và Safari.

Vùng nguy hiểm

<source> trong <video> khiến play() hứa hẹn không bao giờ từ chối

Đối với <video src="not-existing-video.mp4"\>, lời hứa play() sẽ từ chối như dự kiến vì video không tồn tại. Đối với <video><source src="not-existing-video.mp4" type='video/mp4'></video>, lời hứa play() không bao giờ từ chối. Điều này chỉ xảy ra nếu không có nguồn nào hợp lệ.

Lỗi Chromium