Chế độ Hình trong hình (PiP) cho phép người dùng xem video trong một cửa sổ nổi (luôn đặt trên các cửa sổ khác) để họ có thể theo dõi nội dung đang xem trong khi tương tác với các trang web hoặc ứng dụng khác.
Với API Web Hình trong hình, bạn có thể bắt đầu và kiểm soát chế độ Hình trong hình cho các phần tử video trên trang web của mình. Hãy dùng thử trên mẫu Hình trong hình chính thức của chúng tôi.
Thông tin khái quát
Vào tháng 9 năm 2016, Safari đã thêm tính năng hỗ trợ Hình trong hình thông qua API WebKit trong macOS Sierra. Sáu tháng sau, Chrome tự động phát video Hình trong hình trên thiết bị di động khi phát hành Android O bằng API Android gốc. Sáu tháng sau, chúng tôi thông báo về ý định xây dựng và chuẩn hoá một API Web, có tính năng tương thích với Safari, cho phép các nhà phát triển web tạo và kiểm soát toàn bộ trải nghiệm liên quan đến tính năng Hình trong hình. Và đây là kết quả!
Tìm hiểu mã
Chuyển sang chế độ Hình trong hình
Hãy bắt đầu đơn giản với một phần tử video và một cách để người dùng tương tác với phần tử đó, chẳng hạn như một phần tử nút.
<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>
Chỉ yêu cầu Chế độ hình trong hình để phản hồi một cử chỉ của người dùng và không bao giờ trong lời hứa do videoElement.play()
trả về. Điều này là do các lời hứa chưa truyền tải các cử chỉ của người dùng. Thay vào đó, hãy gọi requestPictureInPicture()
trong trình xử lý lượt nhấp trên pipButtonElement
như minh hoạ bên dưới. Bạn có trách nhiệm xử lý những gì xảy ra nếu người dùng nhấp hai lần.
pipButtonElement.addEventListener('click', async function () {
pipButtonElement.disabled = true;
await videoElement.requestPictureInPicture();
pipButtonElement.disabled = false;
});
Khi lời hứa được thực hiện, Chrome sẽ thu nhỏ video thành một cửa sổ nhỏ mà người dùng có thể di chuyển và đặt lên các cửa sổ khác.
Bạn đã hoàn tất. Tuyệt vời! Bạn có thể dừng đọc và đi nghỉ ngơi xứng đáng. Đáng tiếc là không phải lúc nào cũng như vậy. Lời hứa có thể từ chối vì bất kỳ lý do nào sau đây:
- Hệ thống không hỗ trợ tính năng Hình trong hình.
- Ứng dụng Tài liệu không được phép sử dụng chế độ Hình trong hình do chính sách về quyền có nhiều quy định hạn chế.
- Siêu dữ liệu video chưa được tải (
videoElement.readyState === 0
). - Tệp video chỉ có âm thanh.
- Thuộc tính
disablePictureInPicture
mới xuất hiện trên phần tử video. - Lệnh gọi không được thực hiện trong trình xử lý sự kiện cử chỉ của người dùng (ví dụ: lượt nhấp vào nút). Kể từ Chrome 74, điều này chỉ áp dụng nếu không có phần tử nào trong chế độ Hình trong hình.
Phần Hỗ trợ tính năng bên dưới cho biết cách bật/tắt nút dựa trên các quy định hạn chế này.
Hãy thêm một khối try...catch
để ghi lại những lỗi tiềm ẩn này và cho người dùng biết điều gì đang xảy ra.
pipButtonElement.addEventListener('click', async function () {
pipButtonElement.disabled = true;
try {
await videoElement.requestPictureInPicture();
} catch (error) {
// TODO: Show error message to user.
} finally {
pipButtonElement.disabled = false;
}
});
Thành phần video hoạt động giống nhau cho dù ở chế độ Hình trong hình hay không: các sự kiện được kích hoạt và các phương thức gọi hoạt động. Trạng thái này phản ánh các thay đổi về trạng thái trong cửa sổ Hình trong hình (chẳng hạn như phát, tạm dừng, tua, v.v.) và bạn cũng có thể thay đổi trạng thái theo phương thức lập trình trong JavaScript.
Thoát chế độ Hình trong hình
Bây giờ, hãy tạo nút bật/tắt chế độ Hình trong hình. Trước tiên, chúng ta phải kiểm tra xem đối tượng chỉ có thể đọc document.pictureInPictureElement
có phải là phần tử video của chúng ta hay không. Nếu không, chúng tôi sẽ gửi yêu cầu chuyển sang chế độ Hình trong hình như trên. Nếu không, chúng ta sẽ yêu cầu rời khỏi bằng cách gọi document.exitPictureInPicture()
, nghĩa là video sẽ xuất hiện trở lại trong thẻ ban đầu. Xin lưu ý rằng phương thức này cũng trả về một lời hứa.
...
try {
if (videoElement !== document.pictureInPictureElement) {
await videoElement.requestPictureInPicture();
} else {
await document.exitPictureInPicture();
}
}
...
Theo dõi các sự kiện Hình trong hình
Các hệ điều hành thường hạn chế chế độ Hình trong hình ở một cửa sổ, vì vậy, Chrome triển khai theo mẫu này. Điều này có nghĩa là người dùng chỉ có thể phát một video ở chế độ Hình trong hình tại một thời điểm. Bạn nên dự kiến người dùng sẽ thoát khỏi chế độ Hình trong hình ngay cả khi bạn không yêu cầu.
Trình xử lý sự kiện enterpictureinpicture
và leavepictureinpicture
mới cho phép chúng ta điều chỉnh trải nghiệm cho người dùng. Đó có thể là bất kỳ nội dung nào, từ việc duyệt xem danh mục video cho đến việc hiển thị cuộc trò chuyện trong sự kiện phát trực tiếp.
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.
});
Tuỳ chỉnh cửa sổ Hình trong hình
Chrome 74 hỗ trợ các nút phát/tạm dừng, bản nhạc trước và bản nhạc tiếp theo trong cửa sổ Hình trong hình mà bạn có thể điều khiển bằng cách sử dụng Media Session API.
Theo mặc định, nút phát/tạm dừng luôn xuất hiện trong cửa sổ Hình trong hình, trừ phi video đang phát các đối tượng MediaStream (ví dụ: getUserMedia()
, getDisplayMedia()
, canvas.captureStream()
) hoặc video có thời lượng MediaSource được đặt thành +Infinity
(ví dụ: nguồn cấp dữ liệu trực tiếp). Để đảm bảo nút phát/tạm dừng luôn hiển thị, hãy đặt trình xử lý hành động Phiên phát nội dung nghe nhìn của somesee cho cả sự kiện phát "Play" và sự kiện tạm dừng "Pause" như bên dưới.
// 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.
});
Việc hiển thị các chế độ điều khiển cửa sổ "Bản nhạc trước" và "Bản nhạc tiếp theo" cũng tương tự. Việc đặt trình xử lý hành động Phiên phát nội dung đa phương tiện cho các hành động đó sẽ hiển thị các hành động đó trong cửa sổ Hình trong hình và bạn có thể xử lý các hành động này.
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" button.
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" button.
});
Để xem cách hoạt động này, hãy thử mẫu Phiên phát nội dung nghe nhìn chính thức.
Lấy kích thước cửa sổ Hình trong hình
Nếu muốn điều chỉnh chất lượng video khi video chuyển sang và thoát khỏi chế độ Hình trong hình, bạn cần biết kích thước cửa sổ Hình trong hình và được thông báo nếu người dùng đổi kích thước cửa sổ theo cách thủ công.
Ví dụ bên dưới cho biết cách lấy chiều rộng và chiều cao của cửa sổ Hình trong hình khi cửa sổ này được tạo hoặc đổi kích thước.
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.
}
Bạn không nên liên kết trực tiếp với sự kiện đổi kích thước vì mỗi thay đổi nhỏ đối với kích thước cửa sổ Hình trong hình sẽ kích hoạt một sự kiện riêng biệt có thể gây ra vấn đề về hiệu suất nếu bạn đang thực hiện một thao tác tốn kém ở mỗi lần đổi kích thước. Nói cách khác, thao tác đổi kích thước sẽ kích hoạt các sự kiện lặp đi lặp lại rất nhanh. Bạn nên sử dụng các kỹ thuật phổ biến như giới hạn tốc độ và giảm độ trễ để giải quyết vấn đề này.
Hỗ trợ tính năng
API Web Hình trong hình có thể không được hỗ trợ, vì vậy, bạn phải phát hiện điều này để cung cấp tính năng nâng cao dần. Ngay cả khi được hỗ trợ, tính năng này vẫn có thể bị người dùng tắt hoặc bị chính sách quyền vô hiệu hoá. May mắn là bạn có thể sử dụng giá trị boolean document.pictureInPictureEnabled
mới để xác định điều này.
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.');
}
Áp dụng cho một phần tử nút cụ thể cho một video, đây là cách bạn có thể xử lý chế độ hiển thị nút Hình trong hình.
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;
}
Hỗ trợ video MediaStream
Các đối tượng MediaStream phát video (ví dụ: getUserMedia()
, getDisplayMedia()
,
canvas.captureStream()
) cũng hỗ trợ tính năng Hình trong hình trong Chrome 71. Điều này có nghĩa là bạn có thể hiển thị cửa sổ Hình trong hình chứa luồng video webcam của người dùng, luồng video hiển thị hoặc thậm chí là một phần tử canvas. Xin lưu ý rằng bạn không cần đính kèm phần tử video vào DOM để chuyển sang chế độ Hình trong hình như minh hoạ bên dưới.
Hiển thị webcam của người dùng trong cửa sổ Hình trong hình
const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();
// Later on, video.requestPictureInPicture();
Hiện màn hình trong cửa sổ Hình trong hình
const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();
// Later on, video.requestPictureInPicture();
Hiển thị phần tử canvas trong cửa sổ Hình trong hình
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();
Khi kết hợp canvas.captureStream()
với Media Session API, bạn có thể tạo cửa sổ danh sách phát âm thanh trong Chrome 74. Xem Mẫu danh sách phát âm thanh chính thức.
Mẫu, bản minh hoạ và lớp học lập trình
Hãy xem mẫu Hình trong hình chính thức của chúng tôi để dùng thử API Web Hình trong hình.
Sau đó, chúng tôi sẽ cung cấp các bản minh hoạ và lớp học lập trình.
Bước tiếp theo
Trước tiên, hãy xem trang trạng thái triển khai để biết những phần nào của API hiện đang được triển khai trong Chrome và các trình duyệt khác.
Dưới đây là những điểm cải tiến mà bạn có thể thấy trong tương lai gần:
- Nhà phát triển web sẽ có thể thêm các chế độ điều khiển Chế độ hình trong hình tuỳ chỉnh.
- Hệ thống sẽ cung cấp một API Web mới để hiển thị các đối tượng
HTMLElement
tuỳ ý trong một cửa sổ nổi.
Hỗ trợ trình duyệt
API Web Hình trong hình được hỗ trợ trong Chrome, Edge, Opera và Safari. Hãy xem MDN để biết thông tin chi tiết.
Tài nguyên
- Trạng thái tính năng của Chrome: https://www.chromestatus.com/feature/5729206566649856
- Lỗi triển khai Chrome: https://crbug.com/?q=component:Blink>Media>PictureInPicture
- Thông số kỹ thuật API Web Hình trong hình: https://wicg.github.io/picture-in-picture
- Vấn đề về thông số kỹ thuật: https://github.com/WICG/picture-in-picture/issues
- Mẫu: https://googlechrome.github.io/samples/picture-in-picture/
- Trình bổ trợ Hình trong hình không chính thức: https://github.com/gbentaieb/pip-polyfill/
Cảm ơn Mounir Lamouri và Jennifer Apacible đã đóng góp cho tính năng Hình trong hình và giúp đỡ chúng tôi trong quá trình viết bài này. Và cảm ơn tất cả mọi người đã tham gia nỗ lực chuẩn hoá.