DOMException - คำขอ play() ถูกขัดจังหวะ

François Beaufort
François Beaufort

คุณเพิ่งพบข้อผิดพลาดเกี่ยวกับสื่อที่ไม่คาดคิดนี้ในคอนโซล JavaScript ของเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ใช่ไหม

หรือ

คุณมาถูกที่แล้ว ไม่ต้องกังวล เราจะอธิบายสาเหตุของปัญหานี้และวิธีแก้ไข

สาเหตุที่ทำให้เกิดปัญหานี้

ด้านล่างนี้คือโค้ด 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 การเรียก play() ในองค์ประกอบ <video> หรือ <audio> จะแสดงผล Promise ซึ่งเป็นฟังก์ชันที่แสดงผลลัพธ์เดียวแบบไม่สอดคล้องกัน หากเล่นสำเร็จ Promise จะสำเร็จและระบบจะเรียกเหตุการณ์ playing พร้อมกัน หากเล่นไม่สำเร็จ ระบบจะปฏิเสธ Promise พร้อมกับแสดงข้อความแสดงข้อผิดพลาดที่อธิบายสาเหตุของการไม่สำเร็จ

สิ่งที่เกิดขึ้นมีดังนี้

  1. video.play() เริ่มโหลดเนื้อหาวิดีโอแบบไม่พร้อมกัน
  2. video.pause() ขัดจังหวะการโหลดวิดีโอเนื่องจากวิดีโอยังไม่พร้อม
  3. video.play() ปฏิเสธแบบอะซิงโครนัสเสียงดัง

เนื่องจากเราไม่ได้จัดการ Promise การเล่นวิดีโอในโค้ด ข้อความแสดงข้อผิดพลาดจึงปรากฏในเครื่องมือสำหรับนักพัฒนาเว็บของ Chrome

วิธีแก้ไข

เมื่อทราบสาเหตุแล้ว เรามาหาวิธีแก้ไขปัญหานี้กัน

ประการแรก อย่าเพิ่งคิดว่าองค์ประกอบสื่อ (วิดีโอหรือเสียง) จะเล่น ดูที่ Promise ที่ฟังก์ชัน play แสดงผลเพื่อดูว่าถูกปฏิเสธหรือไม่ โปรดทราบว่า 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

โซนอันตราย

<source> ภายใน <video> ทำให้ play() สัญญาว่าจะไม่ปฏิเสธ

สำหรับ <video src="not-existing-video.mp4"\> สัญญา play() ปฏิเสธตามที่คาดไว้เนื่องจากไม่มีวิดีโอ สำหรับ <video><source src="not-existing-video.mp4" type='video/mp4'></video> สัญญา play() จะปฏิเสธไม่ทำงาน กรณีนี้จะเกิดขึ้นก็ต่อเมื่อไม่มีแหล่งที่มาที่ถูกต้อง

ข้อบกพร่องของ Chromium