การแสดงภาพซ้อนภาพ (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
โดยค่าเริ่มต้น ปุ่มเล่น/หยุดชั่วคราวจะแสดงในหน้าต่างการแสดงภาพซ้อนภาพเสมอ เว้นแต่วิดีโอกำลังเล่นออบเจ็กต์ 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 ได้ ดูตัวอย่าง
ตัวอย่างเพลย์ลิสต์เสียงอย่างเป็นทางการ
ตัวอย่าง การสาธิต และ Codelab
โปรดดูตัวอย่างการแสดงภาพซ้อนภาพอย่างเป็นทางการเพื่อลองใช้ Picture-in-Picture Web API
ต่อไปจะมีเดโมและ Codelab
สิ่งที่จะเกิดขึ้นหลังจากนี้
ขั้นแรก ให้ไปที่หน้าสถานะการใช้งานเพื่อดูว่าปัจจุบันส่วนใดของ API ที่ใช้งานใน Chrome และเบราว์เซอร์อื่นๆ อยู่
สิ่งที่จะเกิดขึ้นในอนาคตอันใกล้มีดังนี้
- นักพัฒนาเว็บจะเพิ่มการควบคุมการแสดงภาพซ้อนภาพแบบกำหนดเองได้
- คุณจะได้รับ Web API ใหม่เพื่อแสดงออบเจ็กต์
HTMLElement
ที่กำหนดเองในหน้าต่างแบบลอย
การสนับสนุนเบราว์เซอร์
Picture-in-Picture Web API ได้รับการสนับสนุนใน Chrome, Edge, Opera และ Safari ดูรายละเอียดได้ที่ MDN
แหล่งข้อมูล
- สถานะฟีเจอร์ของ Chrome: https://www.chromestatus.com/feature/5729206566649856
- ข้อบกพร่องในการใช้งาน Chrome: https://crbug.com/?q=component:Blink>Media>PictureInPicture
- ข้อกำหนดของ Web API สำหรับการแสดงภาพซ้อนภาพ: https://wicg.github.io/picture-in-picture
- ปัญหาเกี่ยวกับข้อมูลจำเพาะ: https://github.com/WICG/picture-in-picture/issues
- ตัวอย่าง: https://googlechrome.github.io/samples/picture-in-picture/
- ไฟล์ Polyfill สำหรับการแสดงภาพซ้อนภาพอย่างไม่เป็นทางการ: https://github.com/gbentaieb/pip-polyfill/
ขอขอบคุณ Mounir Lamouri และ Jennifer Apacible ที่ร่วมกันสร้างการแสดงภาพซ้อนภาพและความช่วยเหลือในบทความนี้ และขอขอบคุณทุกคนที่มีส่วนร่วม ในความพยายามในการสร้างมาตรฐาน