Просмотр видео с помощью функции «Картинка в картинке»

Франсуа Бофор
François Beaufort

Функция «Картинка в картинке» (PiP) позволяет пользователям смотреть видео в плавающем окне (всегда поверх других окон), чтобы они могли следить за тем, что смотрят, взаимодействуя с другими сайтами или приложениями.

С помощью веб-API «Картинка в картинке» вы можете запускать и управлять функцией «Картинка в картинке» для видеоэлементов на своем веб-сайте. Попробуйте это на нашем официальном образце «Картинка в картинке» .

Фон

В сентябре 2016 года Safari добавил поддержку «Картинка в картинке» через API WebKit в macOS Sierra. Шесть месяцев спустя Chrome автоматически воспроизводил видео «картинка в картинке» на мобильных устройствах с выпуском Android O, используя собственный API Android . Шесть месяцев спустя мы объявили о своем намерении создать и стандартизировать веб-API, функцию, совместимую с Safari, которая позволила бы веб-разработчикам создавать и контролировать все возможности «Картинка в картинке». И вот мы здесь!

Вникнуть в код

Войдите в режим «картинка в картинке»

Начнем с простого видеоэлемента и способа взаимодействия пользователя с ним, например элемента кнопки.

<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>

Запрашивайте «Картинка в картинке» только в ответ на жест пользователя и никогда в обещании , возвращаемом videoElement.play() . Это связано с тем, что обещания еще не распространяют жесты пользователя. Вместо этого вызовите requestPictureInPicture() в обработчике кликов на pipButtonElement , как показано ниже. Вы несете ответственность за то, что произойдет, если пользователь щелкнет дважды.

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 соответствует этому шаблону. Это означает, что пользователи могут одновременно воспроизводить только одно видео «картинка в картинке». Вы должны ожидать, что пользователи выйдут из режима «Картинка в картинке», даже если вы об этом не просили.

Новые обработчики событий 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 поддерживает кнопки воспроизведения/паузы, перехода к предыдущей и следующей дорожке в окне «Картинка в картинке», которыми можно управлять с помощью API сеанса мультимедиа .

Элементы управления воспроизведением мультимедиа в окне «Картинка в картинке»
Рис. 1. Элементы управления воспроизведением мультимедиа в окне «Картинка в картинке»

По умолчанию кнопка воспроизведения/паузы всегда отображается в окне «Картинка в картинке», если видео не воспроизводит объекты 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.
});

Чтобы увидеть это в действии, попробуйте официальный пример Media Session .

Получить размер окна «Картинка в картинке»

Если вы хотите настроить качество видео при входе и выходе из режима «Картинка в картинке», вам необходимо знать размер окна «Картинка в картинке» и получать уведомления, если пользователь вручную изменяет размер окна.

В приведенном ниже примере показано, как получить ширину и высоту окна «Картинка в картинке» при его создании или изменении размера.

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.
}

Я бы посоветовал не подключаться напрямую к событию изменения размера, поскольку каждое небольшое изменение размера окна «Картинка в картинке» вызывает отдельное событие, которое может вызвать проблемы с производительностью, если вы выполняете дорогостоящую операцию при каждом изменении размера. Другими словами, операция изменения размера будет запускать события снова и снова очень быстро. Для решения этой проблемы я бы рекомендовал использовать распространенные методы, такие как регулирование и устранение дребезга .

Поддержка функций

Веб-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();

Показать элемент холста в окне «Картинка в картинке»

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() с API сеанса мультимедиа , вы можете, например, создать окно списка воспроизведения аудио в Chrome 74. Ознакомьтесь с официальным примером списка воспроизведения аудио .

Список воспроизведения аудио в окне «Картинка в картинке»
Рисунок 2. Список воспроизведения аудио в окне «Картинка в картинке»

Примеры, демонстрации и лаборатории кода

Ознакомьтесь с нашим официальным примером «Картинка в картинке», чтобы попробовать веб-API «Картинка в картинке».

Далее последуют демоверсии и кодлабы.

Что дальше

Сначала посетите страницу состояния реализации , чтобы узнать, какие части API в настоящее время реализованы в Chrome и других браузерах.

Вот что вы можете ожидать увидеть в ближайшем будущем:

Поддержка браузера

Веб-API «картинка в картинке» поддерживается в Chrome, Edge, Opera и Safari. Подробности смотрите в MDN .

Ресурсы

Большое спасибо Муниру Ламури и Дженнифер Апасибл за работу над «Картинка в картинке» и помощь в написании этой статьи. И огромное спасибо всем, кто участвовал в усилиях по стандартизации .