Использование интерфейса Screen Capture API

В этой статье изучается использование программного интерфейса Screen Capture и его метода getDisplayMedia() (en-US) для захвата потока экрана (всего или его части), его записи или передачи через сессию WebRTC .

Примечание: Полезно отметить, что последние версии библиотеки WebRTC adapter.js включают реализацию метода getDisplayMedia() для обмена изображениями с экрана на браузерах, которые его поддерживают, но ещё не реализуют текущий стандартный интерфейс, который реализован в последних версиях Chrome, Edge, и Firefox.

Захват содержимого экрана

Захват содержимого экрана, как живого потока MediaStream запускается вызовом метода navigator.mediaDevices.getDisplayMedia(), и возвращает экземпляр объекта промиса , который разрешается объектом потока, текущих медиаданных с экрана.

Запуск захвата с экрана : в стиле async/await

js
async function startCapture(displayMediaOptions) {
  let captureStream = null;

  try {
    captureStream =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
  } catch (err) {
    console.error("Error: " + err);
  }
  return captureStream;
}

Можно написать этот код, используя асинхронную функцию и оператор await , как показано выше, или использовать тип Promise непосредственно, пример ниже.

Запуска захвата с экрана: в стиле Promise

js
function startCapture(displayMediaOptions) {
  let captureStream = null;

  return navigator.mediaDevices
    .getDisplayMedia(displayMediaOptions)
    .catch((err) => {
      console.error("Error:" + err);
      return null;
    });
}

В любом случае user agent ответить отображением интерфейса диалога, запрашивающий у пользователя размер области захвата экрана. Обе реализации функции startCapture() возвращают объект типа MediaStream , содержащий захваченное с экрана изображение (съёмку ?).

Смотрим Options and constraints, ниже, подробнее о том, как указать желаемый тип поверхности, а также о других способах настройки результирующего потока.

Пример окна, позволяющего пользователю выбрать поверхность дисплея для захвата

Screenshot of Chrome's window for picking a source surface

Затем можно использовать захваченный поток captureStream, везде, где принимается тип потока в качестве входных параметров. Пример examples ниже показывает несколько способов использования полученного типа потока

Видимые или логические поверхности отображения

Для целей интерфейса Screen Capture API, поверхность отображения - это любой объект контента, который может быть выбран API для целей совместного (общего) использования.Поверхности общего доступа включают в себя содержимое вкладки браузера, полное окно, все приложения окна, объединённые в одну поверхность, и монитор (или группу мониторов, объединённых в одну поверхность).

Есть два типа поверхности дисплея. Видимая поверхность отображения - это поверхность, которая полностью видна на экране, например, переднее окно или вкладка или весь экран.

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

Браузер может разрешить захват всего содержимого скрытого окна после получения разрешения от пользователя на это. В этом случае браузер может содержать затушёванный контент, либо путём получения текущего содержимого скрытой части окна, либо путём предоставления самого последнего видимого содержимого, если текущее содержимое недоступно.

Свойства и ограничения

Объект ограничений, передающийся в метод getDisplayMedia() (en-US) является объектом типа DisplayMediaStreamConstraints (en-US) , который используется для конфигурации получаемого объекта потока.

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

Существуют три новых ограничения, добавленные в объект типа MediaTrackConstraints (а так же в MediaTrackSupportedConstraints (en-US) и MediaTrackSettings (en-US)) для конфигурирования потока захвата экрана:

cursor (en-US)

Указывает, следует ли захватывать курсор мыши и, если да, делать это постоянно или только во время движения мыши. Возможные значения:

always

(всегда) Курсор мыши всегда захватывается в результирующий поток.

motion

(в движении) Курсор должен быть видимым при его движении, и (на усмотрение user agent ) на короткое время до и после движения. В покое курсор удаляется из потока.

never

(никогда) Курсор не появляется в результирующем потоке..

logicalSurface (en-US)

Тип Boolean , при истинном значении определяет, что захват должен включать область за пределами экрана, если имеется.

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

К примеру, если определить ограничение width (en-US) для видео, оно применится как масштабирование видео, после того, как пользователь выберет область, и не устанавливает ограничение на размер самого источника.

Примечание: Ограничения никогда не вызывают изменений в списке источников, доступных для захвата API Sharing Screen. Это гарантирует, что веб-приложения не могут заставить пользователя делиться определённым контентом, ограничивая исходный список, пока не останется только один элемент.

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

Примечание: Из соображений конфиденциальности и безопасности источники совместного использования экрана не перечисляются с использованием метода enumerateDevices(). По той-же причине, событие devicechange никогда не вызывается, когда есть изменения в доступных источниках при выполнении getDisplayMedia().

Захват передаваемого аудио

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

До запуска скрипта, который будет запрашивать возможность обмена аудио, проверьте реализацию Browser compatibility (en-US), для понимания браузерной совместимости с функциональностью захвата аудио в поток захвата экрана.

Чтобы запросить доступ к экрану с включённым звуком, параметры ниже передаются в метод getDisplayMedia():

js
const gdmOptions = {
  video: true,
  audio: true,
};

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

js
const gdmOptions = {
  video: {
    cursor: "always",
  },
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    sampleRate: 44100,
  },
};

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

Захват аудио всегда необязателен, и даже когда веб-контент запрашивает поток с аудио и видео, возвращаемый MediaStream может по-прежнему иметь только одну видеодорожку без звука.

Примечание: Некоторые свойства не реализованы широко и могут не использоваться движком. К примеру, cursor имеет ограниченную поддержку (en-US).

Using the captured stream

The promise returned by getDisplayMedia() (en-US) resolves to a MediaStream that contains at least one video stream that contains the screen or screen area, and which is adjusted or filtered based upon the constraints specifed when getDisplayMedia() was called.

Potential risks

Privacy and security issues surrounding screen sharing are usually not overly serious, but they do exist. The largest potential issue is users inadvertently sharing content they did not wish to share.

For example, privacy and/or security violations can easily occur if the user is sharing their screen and a visible background window happens to contain personal information, or if their password manager is visible in the shared stream. This effect can be amplified when capturing logical display surfaces, which may contain content that the user doesn't know about at all, let alone see.

User agents which take privacy seriously should obfuscate content that is not actually visible onscreen, unless authorization has been given to share that content specifically.

Authorizing capture of display contents

Before streaming of captured screen contents can begin, the user agent will ask the user to confirm the sharing request, and to select the content to share.

Examples

Simple screen capture

In this example, the contents of the captured screen area are simply streamed into a <video> element on the same page.

JavaScript

There isn't all that much code needed in order to make this work, and if you're familiar with using getUserMedia() to capture video from a camera, you'll find getDisplayMedia() (en-US) to be very familiar.

Setup

First, some constants are set up to reference the elements on the page to which we'll need access: the <video> into which the captured screen contents will be streamed, a box into which logged output will be drawn, and the start and stop buttons that will turn on and off capture of screen imagery.

The object displayMediaOptions contains the MediaStreamConstraints (en-US) to pass into getDisplayMedia(); here, the cursor (en-US) property is set to always, indicating that the mouse cursor should always be included in the captured media.

Примечание: Some properties are not widely implemented and might not be used by the engine. cursor, for example, has limited support (en-US).

Finally, event listeners are established to detect user clicks on the start and stop buttons.

js
const videoElem = document.getElementById("video");
const logElem = document.getElementById("log");
const startElem = document.getElementById("start");
const stopElem = document.getElementById("stop");

// Options for getDisplayMedia()

var displayMediaOptions = {
  video: {
    cursor: "always",
  },
  audio: false,
};

// Set event listeners for the start and stop buttons
startElem.addEventListener(
  "click",
  function (evt) {
    startCapture();
  },
  false,
);

stopElem.addEventListener(
  "click",
  function (evt) {
    stopCapture();
  },
  false,
);
Logging content

To make logging of errors and other issues easy, this example overrides certain Console methods to output their messages to the <pre> block whose ID is log.

js
console.log = (msg) => (logElem.innerHTML += `${msg}<br>`);
console.error = (msg) =>
  (logElem.innerHTML += `<span class="error">${msg}</span><br>`);
console.warn = (msg) =>
  (logElem.innerHTML += `<span class="warn">${msg}<span><br>`);
console.info = (msg) =>
  (logElem.innerHTML += `<span class="info">${msg}</span><br>`);

This allows us to use the familiar console.log(), console.error(), and so on to log information to the log box in the document.

Starting display capture

The startCapture() method, below, starts the capture of a MediaStream whose contents are taken from a user-selected area of the screen. startCapture() is called when the "Start Capture" button is clicked.

js
async function startCapture() {
  logElem.innerHTML = "";

  try {
    videoElem.srcObject =
      await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
    dumpOptionsInfo();
  } catch (err) {
    console.error("Error: " + err);
  }
}

After clearing the contents of the log in order to get rid of any leftover text from the previous attempt to connect, startCapture() calls getDisplayMedia() (en-US), passing into it the constraints object defined by displayMediaOptions. Using await, the following line of code does not get executed until after the promise returned by getDisplayMedia() resolves. Upon resolution, the promise returns a MediaStream, which will stream the contents of the screen, window, or other region selected by the user.

The stream is connected to the <video> element by storing the returned MediaStream into the element's srcObject (en-US).

The dumpOptionsInfo() function—which we will look at in a moment—dumps information about the stream to the log box for educational purposes.

If any of that fails, the catch() clause outputs an error message to the log box.

Stopping display capture

The stopCapture() method is called when the "Stop Capture" button is clicked. It stops the stream by getting its track list using MediaStream.getTracks() (en-US), then calling each track's {domxref("MediaStreamTrack.stop, "stop()")}} method. Once that's done, srcObject is set to null to make sure it's understood by anyone interested that there's no stream connected.

js
function stopCapture(evt) {
  let tracks = videoElem.srcObject.getTracks();

  tracks.forEach((track) => track.stop());
  videoElem.srcObject = null;
}
Dumping configuration information

For informational purposes, the startCapture() method shown above calls a method named dumpOptions(), which outputs the current track settings as well as the consrtaints that were placed upon the stream when it was created.

js
function dumpOptionsInfo() {
  const videoTrack = videoElem.srcObject.getVideoTracks()[0];

  console.info("Track settings:");
  console.info(JSON.stringify(videoTrack.getSettings(), null, 2));
  console.info("Track constraints:");
  console.info(JSON.stringify(videoTrack.getConstraints(), null, 2));
}

The track list is obtained by calling getVideoTracks() (en-US) on the capture'd screen's MediaStream. The settings currentoly in effect are obtained using getSettings() (en-US) and the established constraints are gotten with getConstraints() (en-US)

HTML

The HTML starts with a simple introductory paragraph, then gets into the meat of things.

html
<p>
  This example shows you the contents of the selected part of your display.
  Click the Start Capture button to begin.
</p>

<p>
  <button id="start">Start Capture</button>&nbsp;<button id="stop">
    Stop Capture
  </button>
</p>

<video id="video" autoplay></video>
<br />

<strong>Log:</strong>
<br />
<pre id="log"></pre>

The key parts of the HTML are:

  1. A <button> labeled "Start Capture" which, when clicked, calls the startCapture() function to request access to, and begin capturing, screen contents.
  2. A second button, "Stop Capture", which upon being clicked calls stopCapture() to terminate capture of screen contents.
  3. A <video> into which the captured screen contents are streamed.
  4. A <pre> block into which logged text is placed by the intercepted Consolemethod.

CSS

The CSS is entirely cosmetic in this example. The video is given a border, and its width is set to occupy nearly the entire available horizontal space (width: 98%). max-width is set to 860px to set an absolute upper limit on the video's size,

The error, warn, and info classes are used to style the corresponding console output types.

css
#video {
  border: 1px solid #999;
  width: 98%;
  max-width: 860px;
}

.error {
  color: red;
}

.warn {
  color: orange;
}

.info {
  color: darkgreen;
}

Result

The final product looks like this. If your browser supports Screen Capture API, clicking "Start Capture" will present the user agent's interface for selecting a screen, window, or tab to share.

Security

In order to function when Feature Policy is enabled, you will need the display-capture permission. This can be done using the Feature-Policy (en-US) HTTP header or—if you're using the Screen Capture API in an <iframe> (en-US), the <iframe> element's allow (en-US) attribute.

For example, this line in the HTTP headers will enable Screen Capture API for the document and any embedded <iframe> (en-US) elements that are loaded from the same origin:

Feature-Policy: display-capture 'self'

If you're performing screen capture within an <iframe>, you can request permission just for that frame, which is clearly more secure than requesting a more general permission:

html
<iframe src="https://mycode.example.net/etc" allow="display-capture"> </iframe>

See also