Функция «Картинка в картинке» (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 сеанса мультимедиа .
По умолчанию кнопка воспроизведения/паузы всегда отображается в окне «Картинка в картинке», если только видео не воспроизводит объекты 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. Ознакомьтесь с официальным примером списка воспроизведения аудио .
Примеры, демонстрации и лаборатории кода
Ознакомьтесь с нашим официальным примером «Картинка в картинке», чтобы попробовать веб-API «Картинка в картинке».
Далее последуют демоверсии и кодлабы.
Что дальше
Сначала посетите страницу состояния реализации , чтобы узнать, какие части API в настоящее время реализованы в Chrome и других браузерах.
Вот что вы можете ожидать увидеть в ближайшем будущем:
- Веб-разработчики смогут добавлять собственные элементы управления «Картинка в картинке» .
- Будет предоставлен новый веб-API для отображения произвольных объектов
HTMLElement
в плавающем окне.
Поддержка браузера
Веб-API «картинка в картинке» поддерживается в Chrome, Edge, Opera и Safari. Подробности смотрите в MDN .
Ресурсы
- Статус функции Chrome: https://www.chromestatus.com/feature/5729206566649856.
- Ошибки реализации Chrome: https://crbug.com/?q=comComponent:Blink>Media>PictureInPicture.
- Спецификация веб-API «картинка в картинке»: https://wicg.github.io/picture-in-picture
- Проблемы со спецификациями: https://github.com/WICG/picture-in-picture/issues .
- Пример: https://googlechrome.github.io/samples/picture-in-picture/
- Неофициальный полифил «Картинка в картинке»: https://github.com/gbentaieb/pip-polyfill/
Большое спасибо Муниру Ламури и Дженнифер Апасибл за работу над «Картинка в картинке» и помощь в написании этой статьи. И огромное спасибо всем, кто участвовал в усилиях по стандартизации .