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

François Beaufort
François Beaufort

Tính năng 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 ở 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.

Nhờ API web Hình trong hình, bạn có thể bắt đầu và kiểm soát các thành phần Hình trong hình cho các thành phần video trên trang web của mình. Hãy dùng thử tính năng này 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ợ tính năng Hình trong hình thông qua API WebKit trong macOS Sierra. 6 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. 6 tháng sau, chúng tôi thông báo về ý định tạo và chuẩn hoá 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 xung quanh tính năng Hình trong hình. Và chúng ta đã sẵn sàng!

Tìm hiểu về đoạn mã

Nhập 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 Hình trong hình để phản hồi cử chỉ của người dùng và không bao giờ nằm trong lời hứavideoElement.play() trả về. Điều này là do các hứa hẹn chưa truyền 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ý điều sẽ 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 giải quyết, 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ể ngừng đọc và đi nghỉ mát mà mình xứng đáng. Tiếc là không phải lúc nào cũng đúng. Lời hứa có thể từ chối vì bất kỳ lý do nào sau đây:

  • Chế độ Hình trong hình không được hệ thống hỗ trợ.
  • Tài liệu không được phép sử dụng tính năng Hình trong hình do chính sách về quyền 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 đã có trong 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ụ: nhấp vào nút). Kể từ Chrome 74, tính năng này chỉ áp dụng nếu chưa có phần tử nào trong Hình trong hình.

Phần Hỗ trợ tính năng dưới đây cho biết cách bật/tắt một nút dựa trên các hạn chế này.

Hãy thêm một khối try...catch để ghi lại các lỗi có thể xảy ra này và thông báo 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;
  }
});

Phần tử video hoạt động như nhau dù ở chế độ Hình trong hình hay không: sự kiện được kích hoạt và các phương thức gọi hoạt động. Thuộc tính 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.), đồng thời 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 nhấn nút chuyển vào và thoát Hình trong hình. Trước tiên, chúng ta phải kiểm tra xem đối tượng chỉ đọ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 tôi sẽ yêu cầu thoát bằng cách gọi document.exitPictureInPicture(). Điều này có nghĩa là video sẽ xuất hiện lại trong thẻ gốc. 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();
      }
    }
    ...

Nghe các sự kiện Hình trong hình

Các hệ điều hành thường giới hạn chế độ Hình trong hình ở một cửa sổ, vì vậy, cách 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 Hình trong hình tại một thời điểm. Bạn nên cho phép người dùng thoát khỏi chế độ Hình trong hình ngay cả khi bạn không yêu cầu.

Các 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 phù hợp với người dùng. Bất cứ điều gì có thể là từ việc duyệt qua danh mục video cho đến việc tạo một cuộc trò chuyện 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 chế độ 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 chế độ 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 hiển thị 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 thiết lập một số trình xử lý hành động trong Phiên phát nội dung đa phương tiện cho cả sự kiện phát nội dung đa phương tiện "Phát" và "Tạm dừng" 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.
});

Cách hiển thị các tuỳ chọn điều khiển cửa sổ "Bản nhạc trước" và "Bản nhạc tiếp theo" tương tự nhau. Việc thiết lập trình xử lý hành động trong Phiên đa phương tiện cho các thao tác đó sẽ hiển thị các trình xử lý này trong cửa sổ Hình trong hình và bạn sẽ có thể xử lý các thao tác 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ế về quá trình này, hãy dùng thử mẫu Phiên nội dung đa phương tiệ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 vào và ra 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à nhận đượ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ổ đượ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 hệ 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 thực hiện một thao tác tốn kém cho mỗi lần thay đổ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 nhiều lần một cách nhanh chóng. Bạn nên sử dụng các kỹ thuật phổ biến như điều tiết và gỡ 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 tăng dần. Ngay cả khi được hỗ trợ, người dùng vẫn có thể tắt tính năng này hoặc bị tắt theo chính sách về quyền. Thật may là bạn có thể sử dụng boolean mới document.pictureInPictureEnabled để 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ể của video, đây là cách bạn nên xử lý chế độ hiển thị của 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

Video phát các đối tượng MediaStream (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. Lưu ý rằng phần tử video không cần phải được đính kèm vào DOM để nhập Hình trong hình như minh họa dưới đây.

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();

Bằng cách kết hợp canvas.captureStream() với Media Session API (API Phiên nội dung đa phương tiện), bạn có thể tạo cửa sổ danh sách phát âm thanh trong Chrome 74. Hãy 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 đây là 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 của API hiện đang được triển khai trong Chrome và các trình duyệt khác.

Trong tương lai gần, bạn có thể thấy những thay đổi sau đây:

Hỗ trợ trình duyệt

API Web hình trong hình được hỗ trợ trên Chrome, Edge, Opera và Safari. Hãy xem phần MDN để biết thông tin chi tiết.

Tài nguyên

Cảm ơn Mounir Lamouri và Jennifer Apacible đã làm việc về tính năng Hình trong hình cũng như trợ giúp cho bài viết này. Và cảm ơn rất nhiều tất cả mọi người đã tham gia vào nỗ lực chuẩn hoá.