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

François Beaufort
François Beaufort

คุณเพิ่งเจอข้อผิดพลาดสื่อที่ไม่คาดคิดในคอนโซล JavaScript ของ Chrome DevTools ใช่ไหม

หรือ

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

สาเหตุของปัญหานี้คืออะไร

นี่คือโค้ด JavaScript บางส่วนด้านล่างที่จำลองข้อผิดพลาด "ไม่พบ (มีสัญญา)" ที่คุณเห็น

ไม่ควรทำ
<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 แสดงผลเพื่อดูว่าถูกปฏิเสธหรือไม่ เป็นที่น่าสังเกตว่าข้อเสนอจะไม่ประสบความสำเร็จจนกว่าการเล่นจะเริ่มเล่นจริงๆ ซึ่งหมายความว่าโค้ดใน 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 Pro

ในขณะที่เขียน 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