ดูวิดีโอโดยใช้การแสดงภาพซ้อนภาพ

François Beaufort
François Beaufort

การแสดงภาพซ้อนภาพ (PIP) ช่วยให้ผู้ใช้ดูวิดีโอในหน้าต่างแบบลอยได้ (ที่ด้านบนของหน้าต่างอื่นๆ เสมอ) เพื่อให้ผู้ใช้ติดตามดูสิ่งที่กำลังดูอยู่ขณะที่โต้ตอบกับเว็บไซต์หรือแอปพลิเคชันอื่นๆ ได้

Picture-in-Picture Web API ช่วยให้คุณเริ่มและควบคุมองค์ประกอบการแสดงภาพซ้อนภาพสำหรับวิดีโอในเว็บไซต์ได้ ลองใช้งานในตัวอย่างการแสดงภาพซ้อนภาพอย่างเป็นทางการของเรา

ที่มา

ในเดือนกันยายน 2016 Safari ได้เพิ่มการรองรับการแสดงภาพซ้อนภาพผ่าน WebKit API ใน macOS Sierra 6 เดือนต่อมา Chrome เล่นวิดีโอการแสดงภาพซ้อนภาพในอุปกรณ์เคลื่อนที่โดยอัตโนมัติพร้อมกับการเปิดตัว Android O โดยใช้ Android API แบบเนทีฟ 6 เดือนต่อมา เราประกาศเจตนาของเราในการสร้างและปรับมาตรฐาน Web API ซึ่งเป็นฟีเจอร์ที่ใช้งานได้กับ Safari โดยจะทำให้นักพัฒนาซอฟต์แวร์เว็บสร้างและควบคุมประสบการณ์การใช้งานการแสดงภาพซ้อนภาพได้อย่างเต็มที่ แล้วก็มาถึงแล้ว!

ทำความรู้จักโค้ด

เข้าสู่การแสดงภาพซ้อนภาพ

มาเริ่มกันด้วยองค์ประกอบวิดีโอและวิธีที่ผู้ใช้ โต้ตอบกับองค์ประกอบนั้น เช่น องค์ประกอบปุ่ม

<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>

ขอเฉพาะการแสดงภาพซ้อนภาพเพื่อตอบกลับท่าทางสัมผัสของผู้ใช้ และอย่าใช้คำสัญญาที่จะส่งคืนโดย videoElement.play() เนื่องจากสัญญาว่ายังไม่เผยแพร่ท่าทางสัมผัสของผู้ใช้ ให้เรียกใช้ requestPictureInPicture() ในเครื่องจัดการคลิกบน pipButtonElement ดังที่แสดงด้านล่างแทน คุณมีหน้าที่รับผิดชอบที่จะต้องจัดการสิ่งที่จะเกิดขึ้นหากผู้ใช้คลิก 2 ครั้ง

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

เมื่อได้ผลตามที่บอกไว้ Chrome จะย่อวิดีโอลงในหน้าต่างเล็กๆ ที่ผู้ใช้สามารถเลื่อนไปมาและวางไว้ไว้เหนือหน้าต่างอื่นๆ ได้

เท่านี้ก็เรียบร้อย เก่งมาก คุณสามารถหยุดอ่านและไปพักผ่อน ในวันหยุด ไม่ได้เป็นเช่นนั้นเสมอไป คำสัญญาอาจปฏิเสธด้วยเหตุผลต่อไปนี้

  • ระบบไม่สนับสนุนการแสดงภาพซ้อนภาพ
  • เอกสารไม่ได้รับอนุญาตให้ใช้การแสดงภาพซ้อนภาพเนื่องจากนโยบายสิทธิ์ที่เข้มงวด
  • ยังไม่ได้โหลดข้อมูลเมตาของวิดีโอ (videoElement.readyState === 0)
  • ไฟล์วิดีโอเป็นแบบเสียงเท่านั้น
  • แอตทริบิวต์ disablePictureInPicture ใหม่มีอยู่ในองค์ประกอบวิดีโอ
  • การโทรไม่ได้ดำเนินการในเครื่องจัดการเหตุการณ์ท่าทางสัมผัสของผู้ใช้ (เช่น การคลิกปุ่ม) ตั้งแต่ Chrome 74 เป็นต้นไป นโยบายนี้จะใช้ได้เฉพาะหากยังไม่มีองค์ประกอบในการแสดงภาพซ้อนภาพอยู่แล้ว

ส่วนการรองรับฟีเจอร์ด้านล่างจะแสดงวิธีเปิด/ปิดใช้ปุ่มตามข้อจำกัดเหล่านี้

เรามาเพิ่มการบล็อก try...catch เพื่อจับข้อผิดพลาดที่อาจเกิดขึ้นเหล่านี้ และแจ้งให้ผู้ใช้ทราบถึงสิ่งที่เกิดขึ้นกัน

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  try {
    await videoElement.requestPictureInPicture();
  } catch (error) {
    // TODO: Show error message to user.
  } finally {
    pipButtonElement.disabled = false;
  }
});

องค์ประกอบวิดีโอจะทํางานเหมือนเดิมไม่ว่าจะอยู่ในการแสดงภาพซ้อนภาพหรือไม่ก็ตาม โดยเหตุการณ์จะเริ่มทำงานและวิธีเรียกใช้จะใช้งานได้ โดยจะแสดงถึงการเปลี่ยนแปลงของสถานะในหน้าต่างการแสดงภาพซ้อนภาพ (เช่น เล่น หยุดชั่วคราว กรอวิดีโอ) และคุณยังเปลี่ยนสถานะโปรแกรมใน JavaScript ได้ด้วย

ออกจากการแสดงภาพซ้อนภาพ

ตอนนี้ เราจะใช้ปุ่มสลับเพื่อเข้าและออกจากการแสดงภาพซ้อนภาพ ก่อนอื่นเราต้องตรวจสอบว่าออบเจ็กต์แบบอ่านอย่างเดียว document.pictureInPictureElement เป็นองค์ประกอบวิดีโอของเราหรือไม่ หากไม่ถูกต้อง เราจะส่งคำขอเพื่อเข้าสู่ การแสดงภาพซ้อนภาพตามที่ระบุไว้ด้านบน ไม่เช่นนั้นเราจะขอออกจากหน้านี้โดยโทรไปที่ document.exitPictureInPicture() ซึ่งหมายความว่าวิดีโอจะกลับไปปรากฏในแท็บเดิม โปรดทราบว่าวิธีนี้ทำให้เกิดคำมั่นสัญญาด้วย

    ...
    try {
      if (videoElement !== document.pictureInPictureElement) {
        await videoElement.requestPictureInPicture();
      } else {
        await document.exitPictureInPicture();
      }
    }
    ...

ฟังกิจกรรมการแสดงภาพซ้อนภาพ

ระบบปฏิบัติการมักจะจำกัดการแสดงภาพซ้อนภาพให้อยู่ในหน้าต่างเดียว การใช้งานของ Chrome จึงเป็นไปตามรูปแบบนี้ ซึ่งหมายความว่าผู้ใช้จะเล่นวิดีโอแบบภาพซ้อนภาพได้ทีละ 1 รายการเท่านั้น คุณควรคาดหวังให้ผู้ใช้ออกจาก การแสดงภาพซ้อนภาพแม้ว่าคุณจะไม่ได้ขอก็ตาม

ตัวแฮนเดิลเหตุการณ์ enterpictureinpicture และ leavepictureinpicture ใหม่ช่วยให้เราปรับแต่งประสบการณ์ให้เหมาะกับผู้ใช้ได้ โดยอาจเป็นอะไรก็ได้ ตั้งแต่ การเรียกดูแคตตาล็อกวิดีโอ ไปจนถึงการแสดงแชทที่มีการสตรีมแบบสด

videoElement.addEventListener('enterpictureinpicture', function (event) {
  // Video entered Picture-in-Picture.
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  // Video left Picture-in-Picture.
  // User may have played a Picture-in-Picture video from a different page.
});

ปรับแต่งหน้าต่างการแสดงภาพซ้อนภาพ

Chrome 74 รองรับปุ่มเล่น/หยุดชั่วคราว แทร็กก่อนหน้า และแทร็กถัดไปในหน้าต่างการแสดงภาพซ้อนภาพที่คุณควบคุมได้โดยใช้ Media Session API

ส่วนควบคุมการเล่นสื่อในหน้าต่างการแสดงภาพซ้อนภาพ
รูปที่ 1 ส่วนควบคุมการเล่นสื่อในหน้าต่างการแสดงภาพซ้อนภาพ

โดยค่าเริ่มต้น ปุ่มเล่น/หยุดชั่วคราวจะแสดงในหน้าต่างการแสดงภาพซ้อนภาพเสมอ เว้นแต่วิดีโอกำลังเล่นออบเจ็กต์ MediaStream (เช่น getUserMedia(), getDisplayMedia(), canvas.captureStream()) หรือวิดีโอตั้งระยะเวลาของ MediaSource เป็น +Infinity (เช่น ฟีดสด) หากต้องการให้ปุ่มเล่น/หยุดชั่วคราว แสดงอยู่เสมอ ให้ตั้งค่าตัวแฮนเดิลการทำงานของเซสชันสื่อสำหรับทั้งเหตุการณ์สื่อ "เล่น" และ "หยุดชั่วคราว" ดังที่แสดงด้านล่าง

// Show a play/pause button in the Picture-in-Picture window
navigator.mediaSession.setActionHandler('play', function () {
  // User clicked "Play" button.
});
navigator.mediaSession.setActionHandler('pause', function () {
  // User clicked "Pause" button.
});

การแสดงตัวควบคุมหน้าต่าง "แทร็กก่อนหน้า" และ "แทร็กถัดไป" คล้ายกัน การตั้งค่า ตัวแฮนเดิลการทำงานเซสชันสื่อสำหรับตัวแฮนเดิลดังกล่าวจะแสดงในหน้าต่างการแสดงภาพซ้อนภาพ และคุณจะจัดการการดำเนินการเหล่านี้ได้

navigator.mediaSession.setActionHandler('previoustrack', function () {
  // User clicked "Previous Track" button.
});

navigator.mediaSession.setActionHandler('nexttrack', function () {
  // User clicked "Next Track" button.
});

ในการดูการทำงานจริง ให้ลองใช้ตัวอย่างเซสชันสื่ออย่างเป็นทางการ

รับขนาดหน้าต่างการแสดงภาพซ้อนภาพ

หากคุณต้องการปรับคุณภาพวิดีโอเมื่อวิดีโอเข้าและออกจากการแสดงภาพซ้อนภาพ คุณจำเป็นต้องทราบขนาดหน้าต่างการแสดงภาพซ้อนภาพและจะได้รับแจ้งหากผู้ใช้ปรับขนาดหน้าต่างด้วยตนเอง

ตัวอย่างด้านล่างแสดงวิธีดูความกว้างและความสูงของหน้าต่างการแสดงภาพซ้อนภาพเมื่อมีการสร้างหรือปรับขนาด

let pipWindow;

videoElement.addEventListener('enterpictureinpicture', function (event) {
  pipWindow = event.pictureInPictureWindow;
  console.log(`> Window size is ${pipWindow.width}x${pipWindow.height}`);
  pipWindow.addEventListener('resize', onPipWindowResize);
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  pipWindow.removeEventListener('resize', onPipWindowResize);
});

function onPipWindowResize(event) {
  console.log(
    `> Window size changed to ${pipWindow.width}x${pipWindow.height}`
  );
  // TODO: Change video quality based on Picture-in-Picture window size.
}

ขอแนะนำว่าอย่าเชื่อมต่อกับเหตุการณ์การปรับขนาดโดยตรงเนื่องจากการเปลี่ยนแปลงเล็กๆ น้อยๆ กับขนาดหน้าต่างการแสดงภาพซ้อนภาพแต่ละครั้งจะทำให้เหตุการณ์เริ่มทำงานแยกต่างหาก ซึ่งอาจทำให้เกิดปัญหาด้านประสิทธิภาพหากคุณต้องดำเนินการซึ่งมีค่าใช้จ่ายจำนวนมากในการปรับขนาดแต่ละครั้ง กล่าวคือ การดำเนินการปรับขนาดจะทำให้เหตุการณ์เริ่มทำงานซ้ำแล้วซ้ำอีกอย่างรวดเร็ว เราขอแนะนำให้ใช้เทคนิคทั่วไป เช่น การควบคุมและการตีกลับเพื่อแก้ไขปัญหานี้

การรองรับฟีเจอร์

Picture-in-Picture Web API อาจไม่รองรับ คุณจึงต้องตรวจหาฟีเจอร์นี้ เพื่อเพิ่มประสิทธิภาพแบบต่อเนื่อง แม้ว่าจะมีการรองรับ แต่ผู้ใช้อาจปิดใช้หรือปิดใช้โดยนโยบายสิทธิ์ โชคดีที่คุณสามารถใช้บูลีน document.pictureInPictureEnabled ใหม่เพื่อกำหนดเรื่องนี้

if (!('pictureInPictureEnabled' in document)) {
  console.log('The Picture-in-Picture Web API is not available.');
} else if (!document.pictureInPictureEnabled) {
  console.log('The Picture-in-Picture Web API is disabled.');
}

คุณอาจต้องการจัดการระดับการมองเห็นปุ่มการแสดงภาพซ้อนภาพกับองค์ประกอบปุ่มที่เฉพาะเจาะจงของวิดีโอ

if ('pictureInPictureEnabled' in document) {
  // Set button ability depending on whether Picture-in-Picture can be used.
  setPipButton();
  videoElement.addEventListener('loadedmetadata', setPipButton);
  videoElement.addEventListener('emptied', setPipButton);
} else {
  // Hide button if Picture-in-Picture is not supported.
  pipButtonElement.hidden = true;
}

function setPipButton() {
  pipButtonElement.disabled =
    videoElement.readyState === 0 ||
    !document.pictureInPictureEnabled ||
    videoElement.disablePictureInPicture;
}

การรองรับวิดีโอ MediaStream

ออบเจ็กต์ MediaStream ที่กำลังเล่นวิดีโอ (เช่น getUserMedia(), getDisplayMedia(), canvas.captureStream()) รองรับการแสดงภาพซ้อนภาพใน Chrome 71 ด้วย ซึ่งหมายความว่าคุณสามารถแสดงหน้าต่างการแสดงภาพซ้อนภาพซึ่งมีสตรีมวิดีโอของเว็บแคมของผู้ใช้ สตรีมวิดีโอจอแสดงผล หรือแม้แต่องค์ประกอบ Canvas ได้ โปรดทราบว่าองค์ประกอบวิดีโอไม่จำเป็นต้องแนบกับ DOM เพื่อเข้าสู่การแสดงภาพซ้อนภาพตามที่แสดงด้านล่าง

แสดงเว็บแคมของผู้ใช้ในหน้าต่างการแสดงภาพซ้อนภาพ

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

แสดงการแสดงผลในหน้าต่างการแสดงภาพซ้อนภาพ

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

แสดงองค์ประกอบ Canvas ในหน้าต่างการแสดงภาพซ้อนภาพ

const canvas = document.createElement('canvas');
// Draw something to canvas.
canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);

const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();

// Later on, video.requestPictureInPicture();

การรวม canvas.captureStream() กับ Media Session API ช่วยให้คุณสร้างหน้าต่างเพลย์ลิสต์เสียงใน Chrome 74 ได้ ดูตัวอย่าง ตัวอย่างเพลย์ลิสต์เสียงอย่างเป็นทางการ

เพลย์ลิสต์เสียงในหน้าต่างการแสดงภาพซ้อนภาพ
รูปที่ 2 เพลย์ลิสต์เสียงในหน้าต่างการแสดงภาพซ้อนภาพ

ตัวอย่าง การสาธิต และ Codelab

โปรดดูตัวอย่างการแสดงภาพซ้อนภาพอย่างเป็นทางการเพื่อลองใช้ Picture-in-Picture Web API

ต่อไปจะมีเดโมและ Codelab

สิ่งที่จะเกิดขึ้นหลังจากนี้

ขั้นแรก ให้ไปที่หน้าสถานะการใช้งานเพื่อดูว่าปัจจุบันส่วนใดของ API ที่ใช้งานใน Chrome และเบราว์เซอร์อื่นๆ อยู่

สิ่งที่จะเกิดขึ้นในอนาคตอันใกล้มีดังนี้

การสนับสนุนเบราว์เซอร์

Picture-in-Picture Web API ได้รับการสนับสนุนใน Chrome, Edge, Opera และ Safari ดูรายละเอียดได้ที่ MDN

แหล่งข้อมูล

ขอขอบคุณ Mounir Lamouri และ Jennifer Apacible ที่ร่วมกันสร้างการแสดงภาพซ้อนภาพและความช่วยเหลือในบทความนี้ และขอขอบคุณทุกคนที่มีส่วนร่วม ในความพยายามในการสร้างมาตรฐาน