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