Прокрутите и увеличьте захваченную вкладку,Прокрутите и увеличьте захваченную вкладку

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

Совместное использование вкладок, окон и экранов уже возможно на веб-платформе с помощью API захвата экрана . Когда веб-приложение вызывает getDisplayMedia() , Chrome предлагает пользователю поделиться вкладкой, окном или экраном с веб-приложением в виде видео MediaStreamTrack .

Многие веб-приложения, использующие getDisplayMedia() показывают пользователю предварительный просмотр захваченной поверхности. Например, приложения для видеоконференций часто передают это видео в потоковом режиме удаленным пользователям, а также визуализируют его в локальный HTMLVideoElement , чтобы локальный пользователь постоянно видел предварительный просмотр того, чем они делятся.

В этой документации представлен новый API Captured Surface Control в Chrome, который позволяет вашему веб-приложению прокручивать захваченную вкладку, а также считывать и записывать уровень масштабирования захваченной вкладки.

Пользователь прокручивает и масштабирует захваченную вкладку ( демо ).

Зачем использовать Captured Surface Control?

Все приложения для видеоконференций страдают от одного и того же недостатка: если пользователь хочет взаимодействовать с захваченной вкладкой или окном, он должен переключиться на эту поверхность, убрав их из приложения для видеоконференций. Это создает некоторые проблемы:

  • Пользователь не может одновременно видеть захваченное приложение и видео удаленных пользователей, если он не использует режим «Картинка в картинке» или отдельные параллельные окна для вкладок «Видеоконференция» и «Общий доступ». На маленьком экране это может быть сложно.
  • Пользователь отягощен необходимостью переключаться между приложением для видеоконференций и захваченной поверхностью.
  • Пользователь теряет доступ к элементам управления, предоставляемым приложением для видеоконференций, пока он находится вдали от него; например, встроенное приложение чата, реакции с помощью смайликов, уведомления о пользователях, предлагающих присоединиться к звонку, элементы управления мультимедиа и макетом, а также другие полезные функции видеоконференций.
  • Докладчик не может делегировать управление удаленным участникам. Это приводит к слишком знакомому сценарию, когда удаленные пользователи просят докладчика сменить слайд, немного прокрутить вверх и вниз или отрегулировать уровень масштабирования.

API Captured Surface Control решает эти проблемы.

Как использовать Captured Surface Control?

Для успешного использования Captured Surface Control требуется несколько шагов, таких как явный захват вкладки браузера и получение разрешения от пользователя, прежде чем он сможет прокручивать и масштабировать захваченную вкладку.

Захват вкладки браузера

Начните с предложения пользователю выбрать поверхность для совместного использования с помощью getDisplayMedia() и в процессе свяжите объект CaptureController с сеансом захвата. Вскоре мы будем использовать этот объект для управления захваченной поверхностью.

const controller = new CaptureController();
const stream = await navigator.mediaDevices.getDisplayMedia({ controller });

Далее создайте локальный предварительный просмотр захваченной поверхности в виде элемента <video> :

const previewTile = document.querySelector('video');
previewTile.srcObject = stream;

Если пользователь решит поделиться окном или экраном, на данный момент это выходит за рамки, но если он решил поделиться вкладкой, мы можем продолжить.

const [track] = stream.getVideoTracks();

if (track.getSettings().displaySurface !== 'browser') {
  // Bail out early if the user didn't pick a tab.
  return;
}

Запрос разрешения

Первый вызов sendWheel() или setZoomLevel() для данного объекта CaptureController вызывает запрос разрешения. Если пользователь предоставляет разрешение, дальнейшие вызовы этих методов для этого объекта CaptureController разрешены. Если пользователь отказывает в разрешении, возвращенное обещание отклоняется.

Обратите внимание, что объекты CaptureController однозначно связаны с определенным сеансом захвата , не могут быть связаны с другим сеансом захвата и не сохраняются при навигации по странице, где они определены. Однако сеансы захвата сохраняются при навигации по захваченной странице.

Для отображения запроса на разрешение пользователю требуется жест пользователя. Только вызовы sendWheel() и setZoomLevel() требуют жеста пользователя и только в том случае, если необходимо отобразить приглашение. Если пользователь нажимает кнопку увеличения или уменьшения масштаба в веб-приложении, этот жест пользователя является заданным; но если приложение хочет сначала предложить управление прокруткой, разработчикам следует иметь в виду, что прокрутка не является жестом пользователя. Одна из возможностей — сначала предложить пользователю кнопку «начать прокрутку», как показано в следующем примере:

const startScrollingButton = document.querySelector('button');

startScrollingButton.addEventListener('click', async () => {
  try {
    const noOpWheelAction = {};

    await controller.sendWheel(noOpWheelAction);
    // The user approved the permission prompt.
    // You can now scroll and zoom the captured tab as shown later in the article.
  } catch (error) {
    return; // Permission denied. Bail.
  }
});

Прокрутка

Используя sendWheel() , приложение захвата может доставлять события колеса выбранной величины по выбранным им координатам в области просмотра вкладки. Событие неотличимо для захваченного приложения от прямого взаимодействия с пользователем.

Предполагая, что приложение захвата использует элемент <video> под названием "previewTile" , следующий код показывает, как ретранслировать события колеса отправки на захваченную вкладку:

const previewTile = document.querySelector('video');

previewTile.addEventListener('wheel', async (event) => {
  // Translate the offsets into coordinates which sendWheel() can understand.
  // The implementation of this translation is explained further below.
  const [x, y] = translateCoordinates(event.offsetX, event.offsetY);
  const [wheelDeltaX, wheelDeltaY] = [-event.deltaX, -event.deltaY];

  try {
    // Relay the user's action to the captured tab.
    await controller.sendWheel({ x, y, wheelDeltaX, wheelDeltaY });
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

Метод sendWheel() принимает словарь с двумя наборами значений:

  • x и y : координаты, по которым должно быть доставлено событие колеса.
  • wheelDeltaX и wheelDeltaY : величина прокрутки в пикселях для горизонтальной и вертикальной прокрутки соответственно. Обратите внимание, что эти значения инвертированы по сравнению с исходным событием колеса.

Возможная реализация translateCoordinates() :

function translateCoordinates(offsetX, offsetY) {
  const previewDimensions = previewTile.getBoundingClientRect();
  const trackSettings = previewTile.srcObject.getVideoTracks()[0].getSettings();

  const x = trackSettings.width * offsetX / previewDimensions.width;
  const y = trackSettings.height * offsetY / previewDimensions.height;

  return [Math.floor(x), Math.floor(y)];
}

Обратите внимание, что в приведенном выше коде задействованы три разных размера:

  • Размер элемента <video> .
  • Размер захваченных кадров (представленный здесь как trackSettings.width и trackSettings.height ).
  • Размер вкладки.

Размер элемента <video> полностью находится в пределах области действия приложения захвата и неизвестен браузеру. Размер вкладки полностью находится в пределах домена браузера и неизвестен веб-приложению.

Веб-приложение использует translateCoordinates() для перевода смещений относительно элемента <video> в координаты в собственном пространстве координат видеодорожки. Браузер также будет преобразовывать размер захваченных кадров в размер вкладки и доставлять событие прокрутки со смещением, соответствующим ожиданиям веб-приложения.

Промис, возвращаемый sendWheel() может быть отклонен в следующих случаях:

  • Если сеанс захвата еще не начался или уже остановлен, включая асинхронную остановку, пока действие sendWheel() обрабатывается браузером.
  • Если пользователь не предоставил приложению разрешение на использование sendWheel() .
  • Если приложение захвата пытается доставить событие прокрутки в координатах, находящихся за пределами [trackSettings.width, trackSettings.height] . Обратите внимание, что эти значения могут изменяться асинхронно, поэтому рекомендуется обнаружить ошибку и проигнорировать ее. (Обратите внимание, что 0, 0 обычно не выходят за пределы допустимого, поэтому их можно безопасно использовать для запроса разрешения у пользователя.)

Увеличить

Взаимодействие с уровнем масштабирования захваченной вкладки осуществляется с помощью следующих поверхностей CaptureController :

  • getSupportedZoomLevels() возвращает список уровней масштабирования, поддерживаемых браузером, представленных в процентах от «уровня масштабирования по умолчанию», который определяется как 100%. Этот список монотонно увеличивается и содержит значение 100.
  • getZoomLevel() возвращает текущий уровень масштабирования вкладки.
  • setZoomLevel() устанавливает уровень масштабирования вкладки на любое целочисленное значение, присутствующее в getSupportedZoomLevels() , и возвращает обещание в случае успеха. Обратите внимание, что уровень масштабирования не сбрасывается в конце сеанса захвата.
  • oncapturedzoomlevelchange позволяет прослушивать изменения уровня масштабирования захваченной вкладки, поскольку пользователи могут изменять уровень масштабирования либо через приложение для захвата, либо посредством прямого взаимодействия с захваченной вкладкой.

Вызовы setZoomLevel() ограничиваются разрешением; вызовы других методов масштабирования, доступных только для чтения, «бесплатны», как и прослушивание событий.

В следующем примере показано, как увеличить уровень масштабирования захваченной вкладки в существующем сеансе захвата:

const zoomIncreaseButton = document.getElementById('zoomInButton');

zoomIncreaseButton.addEventListener('click', async (event) => {
  const levels = CaptureController.getSupportedZoomLevels();
  const index = levels.indexOf(controller.getZoomLevel());
  const newZoomLevel = levels[Math.min(index + 1, levels.length - 1)];

  try {
    await controller.setZoomLevel(newZoomLevel);
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

В следующем примере показано, как реагировать на изменения уровня масштабирования захваченной вкладки:

controller.addEventListener('capturedzoomlevelchange', (event) => {
  const zoomLevel = controller.getZoomLevel();
  document.querySelector('#zoomLevelLabel').textContent = `${zoomLevel}%`;
});

Обнаружение функций

Чтобы проверить, поддерживается ли отправка событий колеса, используйте:

if (!!window.CaptureController?.prototype.sendWheel) {
  // CaptureController sendWheel() is supported.
}

Чтобы проверить, поддерживается ли управление масштабированием, используйте:

if (!!window.CaptureController?.prototype.setZoomLevel) {
  // CaptureController setZoomLevel() is supported.
}

Включить контроль захваченной поверхности

API Captured Surface Control доступен в Chrome на рабочем столе под флагом Captured Surface Control и может быть включен по адресу chrome://flags/#captured-surface-control .

Эта функция также проходит пробную версию, начиная с Chrome 122 для настольных компьютеров , что позволяет разработчикам включить эту функцию для посетителей своих сайтов для сбора данных от реальных пользователей. Дополнительную информацию о пробных версиях происхождения и о том, как они работают, см. в разделе Начало работы с пробными версиями происхождения .

Безопасность и конфиденциальность

Политика разрешений "captured-surface-control" позволяет вам управлять тем, как ваше приложение для захвата и встроенные сторонние iframe имеют доступ к Captured Surface Control. Чтобы понять компромиссы в области безопасности, ознакомьтесь с разделом «Вопросы конфиденциальности и безопасности» в пояснении «Контроль захваченных поверхностей».

Демо

Вы можете поиграть с Captured Surface Control, запустив демо-версию на Glitch. Обязательно ознакомьтесь с исходным кодом .

Изменения по сравнению с предыдущими версиями Chrome

Вот некоторые ключевые поведенческие различия в использовании Captured Surface Control, о которых вам следует знать:

  • В Chrome 124 и более ранних версиях:
    • Разрешение — если оно предоставлено — распространяется на сеанс захвата, связанный с этим CaptureController , а не на источник захвата.
  • В Chrome 122:
    • getZoomLevel() возвращает обещание с текущим уровнем масштабирования вкладки.
    • sendWheel() возвращает обещание, отклоненное с сообщением об ошибке "No permission." если пользователь не предоставил приложению разрешение на использование. Тип ошибки — "NotAllowedError" в Chrome 123 и более поздних версиях.
    • oncapturedzoomlevelchange недоступен. Вы можете заполнить эту функцию, используя setInterval() .

Обратная связь

Команда Chrome и сообщество веб-стандартистов хотят услышать о вашем опыте использования Captured Surface Control.

Расскажите о дизайне

Есть ли что-то в Captured Surface Capture, что работает не так, как вы ожидали? Или вам не хватает методов или свойств, необходимых для реализации вашей идеи? У вас есть вопрос или комментарий по модели безопасности? Сообщите о проблеме спецификации в репозитории GitHub или добавьте свои мысли к существующей проблеме.

Проблема с реализацией?

Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на https://new.crbug.com . Обязательно включите как можно больше деталей, а также инструкции по воспроизведению. Glitch отлично подходит для обмена воспроизводимыми ошибками.