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

François Beaufort
François Beaufort

Có phải bạn vừa gặp phải lỗi không mong muốn về nội dung nghe nhìn này trong Bảng điều khiển JavaScript của Chrome Công cụ cho nhà phát triển không?

hoặc

Vậy thì bạn đã đến đúng nơi rồi. Không sợ hãi. 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 dưới đây tái hiện lỗi "Uncaught (trong hứa)" mà bạn đang gặp:

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>

Đoạn mã ở trên dẫn đến thông báo lỗi sau 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ề Promise, một hàm sẽ trả về một kết quả không đồng bộ. Nếu phát thành công, Promise sẽ được thực hiện và sự kiện playing sẽ được kích hoạt cùng lúc. Nếu quá trình phát không thành công, Promise sẽ bị từ chối cùng với một thông báo lỗi giải thích lỗi đó.

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

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

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

Cách khắc phục

Giờ thì chúng ta đã hiểu được căn nguyên, hãy xem chúng ta có thể làm gì để khắc phục vấn đề này.

Thứ nhất, đừng bao giờ giả định rằng thành phần nội dung đa phương tiện (video hoặc âm thanh) sẽ phát. Hãy xem Promise được hàm play trả về để xem liệu nó có bị từ chối hay không. Lưu ý: Promise 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>

Điều đó thật tuyệt trong ví dụ đơn giản này, nhưng nếu bạn sử dụng video.play() để có thể phát video sau này 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() theo cách như sau:

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ợ Play Promise

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

Vùng nguy hiểm

<source> trong <video> tạo ra play() lời hứa không bao giờ bị từ chối

Đối với <video src="not-existing-video.mp4"\>, lời hứa play() sẽ từ chối như mong đợi 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() sẽ không bao giờ từ chối. Điều này chỉ xảy ra nếu không có nguồn hợp lệ.

Lỗi Chromium