Xem video bằng tính năng Hình trong hình

François Beaufort
François Beaufort

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à chúng tôi ở đây!

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 xung quanh và đặt lên trê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 hiện đã có 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 chưa 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 của 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 để chuyển sang và thoát khỏi 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. Lưu ý rằng phương thức này cũng trả về một hàm hứa hẹn.

    ...
    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ế tính năng Hình trong hình ở một cửa sổ, vì vậy, quá trình triển khai của Chrome tuân 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 enterpictureinpictureleavepictureinpicture 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 đế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.

Các nút điều khiển phát nội dung nghe nhìn trong cửa sổ Hình trong hình
Hình 1. Các nút điều khiển phát nội dung nghe nhìn trong cửa sổ Hình trong hình

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 đa phương tiệ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 thiết lập 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 ví dụ thực tế, hãy thử mẫu Phiên 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ư điều tiết và loại bỏ để 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ị thành phần 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.

Danh sách phát âm thanh trong cửa sổ Hình trong hình
Hình 2. Danh sách phát âm thanh trong cửa sổ Hình trong hình

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 đượ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:

Hỗ trợ trình duyệt

API Web Hình trong hình được hỗ trợ trong Chrome, Edge, Opera và Safari. Xem MDN để biết chi tiết.

Tài nguyên

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á này.