Uso de la API de captura de pantalla

En este artículo, examinaremos cómo utilizar la API de captura de pantalla y su método getDisplayMedia() para capturar parte o la totalidad de una pantalla para transmitir, grabar o compartir durante una sesión de conferencia WebRTC.

Nota: Puede ser útil tener en cuenta que las versiones recientes adapter.js shim de WebRTC incluyen implementaciones de getDisplayMedia() para habilitar el uso compartido de pantalla en navegadores que lo admiten pero no implementar la API estándar actual. Esto funciona al menos con Chrome, Edge y Firefox.

Captura de contenido de la pantalla

La captura del contenido de la pantalla como MediaStream en vivo se inicia llamando a navigator.mediaDevices.getDisplayMedia(), que devuelve una promesa que resuelve una transmisión que contiene el contenido de la pantalla en vivo.

Captura de pantalla inicial: estilo 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;
}

Puede escribir este código usando una función asíncrona y el operador await, como se muestra arriba, o usando una Promesa directamente, como se ve a continuación.

Captura de pantalla inicial: estilo Promise

js
function startCapture(displayMediaOptions) {
  return navigator.mediaDevices
    .getDisplayMedia(displayMediaOptions)
    .catch((err) => {
      console.error(err);
      return null;
    });
}

De cualquier manera, el user agent responde presentando una interfaz de usuario que solicita al usuario que elija el área de la pantalla para compartir. Ambas implementaciones de startCapture() devuelven un MediaStream que contiene las imágenes de visualización capturadas.

Consulte Opciones y restricciones, a continuación, para obtener más información sobre cómo especificar el tipo de superficie que desea y otras formas de ajustar el flujo resultante.

Ejemplo de una ventana que permite al usuario seleccionar una superficie de visualización para capturar

Captura de pantalla de la ventana de Chrome para elegir una superficie de origen

Luego puede usar el flujo capturado, captureStream, para cualquier cosa que acepte un flujo como entrada. Los ejemplos a continuación muestran algunas formas de utilizar la transmisión.

Superficies de visualización visibles y lógicas

A los efectos de la API de captura de pantalla, una superficie de visualización es cualquier objeto de contenido que la API puede seleccionar para compartir. Las superficies compartidas incluyen el contenido de una pestaña del navegador, una ventana completa y un monitor (o un grupo de monitores combinados en una sola superficie).

Hay dos tipos de superficie de visualización. Una superficie de visualización visible es una superficie que es completamente visible en la pantalla, como la ventana o pestaña más frontal, o la pantalla completa.

Una superficie de visualización lógica es aquella que está parcialmente o completamente oculta, ya sea porque otro objeto la superpone hasta cierto punto, o porque está completamente oculta o fuera de la pantalla. La forma en que la API de captura de pantalla los maneja varía. En general, el navegador proporcionará una imagen que oscurecerá la parte oculta de la superficie de visualización lógica de alguna manera, por ejemplo, difuminándola o reemplazándola con un color o patrón. Esto se hace por razones de seguridad, ya que el contenido que el usuario no puede ver puede contener datos que no desea compartir.

Un user agent podría permitir la captura de todo el contenido de una ventana oculta después de obtener el permiso del usuario para hacerlo. En este caso, el user agent puede incluir el contenido oculto, ya sea obteniendo el contenido actual de la parte oculta de la ventana o presentando el contenido visible más recientemente si el contenido actual no está disponible.

Opciones y restricciones

El objeto de opciones pasado a getDisplayMedia() se usa para establecer opciones para la transmisión resultante.

Los objetos video y audio pasados al objeto de opciones también pueden contener restricciones adicionales particulares a esas pistas de medios. Consulte Propiedades de las pistas de pantalla compartidas para obtener detalles sobre restricciones adicionales para configurar un flujo de captura de pantalla que se agregan a MediaTrackConstraints, MediaTrackSupportedConstraints y MediaTrackSettings).

Ninguna de las restricciones se aplica de ninguna manera hasta que se haya seleccionado el contenido a capturar. Las restricciones alteran lo que ve en el flujo resultante. Por ejemplo, si especifica una restricción width para el video, se aplica escalando el video después de que el usuario seleccione el área para compartir. No establece una restricción sobre el tamaño de la fuente en sí.

Nota: Las restricciones nunca causan cambios en la lista de fuentes disponibles para la captura por parte de la API de uso compartido de pantalla. Esto garantiza que las aplicaciones web no puedan obligar al usuario a compartir contenido específico restringiendo la lista de fuentes hasta que solo quede un elemento.

Mientras la captura de pantalla está en uso, la máquina que comparte el contenido de la pantalla mostrará algún tipo de indicador para que el usuario sepa que se está compartiendo.

Nota: Por motivos de privacidad y seguridad, las fuentes de pantalla compartida no se pueden enumerar mediante enumerateDevices(). Relacionado con esto, el evento devicechange nunca se envía cuando hay cambios en las fuentes disponibles para getDisplayMedia().

Captura de audio compartido

getDisplayMedia() se usa más comúnmente para capturar video de la pantalla de un usuario (o partes de la misma). Sin embargo, el user agent puede permitir la captura de audio junto con el contenido de video. La fuente de este audio puede ser la ventana seleccionada, todo el sistema de audio de la computadora o el micrófono del usuario (o una combinación de todos los anteriores).

Antes de comenzar un proyecto que requerirá compartir audio, asegúrese de verificar la compatibilidad del navegador para getDisplayMedia() para ver si los navegadores que desea compatibilidad con soporte para audio en secuencias de pantalla capturadas.

Para solicitar que la pantalla se comparta con el audio incluido, las opciones que se pasan a getDisplayMedia() podrían verse así:

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

Esto le permite al usuario total libertad para seleccionar lo que quiera, dentro de los límites de lo que admite el user agent. Esto podría refinarse aún más especificando opciones adicionales y restricciones dentro de los objetos audio y video:

js
const gdmOptions = {
  video: {
    displaySurface: "window",
  },
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    sampleRate: 44100,
    suppressLocalAudioPlayback: true,
  },
  surfaceSwitching: "include",
  selfBrowserSurface: "exclude",
  systemAudio: "exclude",
};

En este ejemplo, la superficie de visualización capturada será la ventana completa. Idealmente, la pista de audio debería tener habilitadas las funciones de supresión de ruido y cancelación de eco, así como una frecuencia de muestreo de audio ideal de 44,1 kHz y la supresión de la reproducción de audio local.

Además, la aplicación le está insinuando al user agent que debería:

  • Proporcionar un control durante el uso compartido de la pantalla para permitir que el usuario cambie dinámicamente la pestaña compartida.
  • Ocultar la pestaña actual de la lista de opciones presentadas al usuario cuando se solicita la captura.
  • No incluir el audio del sistema entre las posibles fuentes de audio ofrecidas al usuario.

La captura de audio siempre es opcional, e incluso cuando el contenido web solicita una transmisión con audio y video, el MediaStream devuelto puede tener solo una pista de video, sin audio.

Uso de la transmisión capturada

La promesa devuelta por getDisplayMedia() se resuelve en un MediaStream que contiene al menos un flujo de video que contiene la pantalla o el área de la pantalla, y que se ajusta o filtra según las restricciones especificadas cuando se llamó a getDisplayMedia().

Riesgos potenciales

Los problemas de privacidad y seguridad relacionados con el uso compartido de la pantalla no suelen ser demasiado graves, pero existen. El mayor problema potencial es que los usuarios compartan sin darse cuenta contenido que no deseaban compartir.

Por ejemplo, las violaciones de privacidad y/o seguridad pueden ocurrir fácilmente si el usuario está compartiendo su pantalla y una ventana de fondo visible contiene información personal, o si su administrador de contraseñas está visible en la transmisión compartida. Este efecto se puede amplificar al capturar superficies de visualización lógica, que pueden contener contenido que el usuario no conoce en absoluto, y mucho menos ver.

Los user agent que se toman en serio la privacidad deben ofuscar el contenido que no es realmente visible en la pantalla, a menos que se haya otorgado autorización para compartir ese contenido específicamente.

Autorización de captura de contenido de visualización

Antes de que pueda comenzar la transmisión del contenido de la pantalla capturada, el user agent le pedirá al usuario que confirme la solicitud para compartir y que seleccione el contenido para compartir.

Ejemplos

Captura de pantalla de transmisión

En este ejemplo, el contenido del área de la pantalla capturada se transmite a un elemento <video> en la misma página.

JavaScript

No se necesita tanto código para que esto funcione, y si está familiarizado con el uso de getUserMedia() para capturar video de una cámara, encontrará getDisplayMedia() muy familiar.

Configuración

En primer lugar, se configuran algunas constantes para hacer referencia a los elementos de la página a los que necesitaremos acceder: el <video> en el que se transmitirán los contenidos de la pantalla capturados, un cuadro en el que se registrará la salida se dibujará, y los botones de inicio y detención que activarán y desactivarán la captura de imágenes de pantalla.

El objeto displayMediaOptions contiene las opciones para pasar a getDisplayMedia(); aquí, la propiedad displaySurface se establece en window, lo que indica que se debe capturar toda la ventana.

Finalmente, se establecen detectores de eventos para detectar los clics de los usuarios en los botones de inicio y detención.

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

// Opciones para getDisplayMedia()

const displayMediaOptions = {
  video: {
    displaySurface: "window",
  },
  audio: false,
};

// Establecer detectores de eventos para los botones de inicio y detención
startElem.addEventListener(
  "click",
  (evt) => {
    startCapture();
  },
  false,
);

stopElem.addEventListener(
  "click",
  (evt) => {
    stopCapture();
  },
  false,
);
Registro de contenido

Este ejemplo anula ciertos métodos console para enviar sus mensajes al bloque <pre> cuyo ID es log.

js
console.log = (msg) => (logElem.textContent = `${logElem.textContent}\n${msg}`);
console.error = (msg) =>
  (logElem.textContent = `${logElem.textContent}\nError: ${msg}`);

Esto nos permite usar console.log() y console.error() para registrar información en el cuadro de registro del documento.

Iniciar captura de pantalla

El método startCapture(), a continuación, inicia la captura de un MediaStream cuyos contenidos se toman de un área de la pantalla seleccionada por el usuario. startCapture() se llama cuando se hace clic en el botón "Iniciar captura".

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

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

Después de borrar el contenido del registro para deshacerse de cualquier texto sobrante del intento anterior de conexión, startCapture() llama a getDisplayMedia(), pasando en él, el objeto de restricciones definido por displayMediaOptions. Usando await, la siguiente línea de código no se ejecuta hasta que se resuelve la promesa devuelta por getDisplayMedia(). Tras la resolución, la promesa devuelve un MediaStream, que transmitirá el contenido de la pantalla, ventana u otra región seleccionada por el usuario.

La transmisión se conecta al elemento <video> almacenando el MediaStream devuelto en el srcObject del elemento.

La función dumpOptionsInfo(), que veremos en un momento, descarga información sobre la transmisión en el cuadro de registro con fines educativos.

Si algo de eso falla, la cláusula catch() genera un mensaje de error en el cuadro de registro.

Deteniendo la captura de pantalla

El método stopCapture() se llama cuando se hace clic en el botón "Detener captura". Detiene la transmisión al obtener su lista de pistas usando MediaStream.getTracks(), luego llama al método stop() de cada pista. Una vez hecho esto, srcObject se establece en null para asegurarse de que cualquier persona interesada entienda que no hay una transmisión conectada.

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

  tracks.forEach((track) => track.stop());
  videoElem.srcObject = null;
}
Volcar información de configuración

Para fines informativos, el método startCapture() que se muestra arriba llama a un método llamado dumpOptions(), que genera la configuración actual de la pista, así como las restricciones que se impusieron a la transmisión cuando se creó.

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

  console.log("Ajustes de pista:");
  console.log(JSON.stringify(videoTrack.getSettings(), null, 2));
  console.log("Restricciones de pista:");
  console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
}

La lista de pistas se obtiene llamando a getVideoTracks() en el MediaStream de la pantalla capturada. Los ajustes actualmente en vigor se obtienen usando getSettings() y las restricciones establecidas se obtienen con getConstraints()

HTML

El HTML comienza con un párrafo introductorio, luego entra en el meollo de las cosas.

html
<p>
  Este ejemplo le muestra el contenido de la parte seleccionada de su pantalla.
  Haga clic en el botón Iniciar captura para comenzar.
</p>

<p>
  <button id="start">Iniciar captura</button>&nbsp;<button id="stop">
    Detener captura
  </button>
</p>

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

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

Las partes clave del HTML son:

  1. Un <button> etiquetado como "Iniciar captura" que, cuando se hace clic, llama a la función startCapture() para solicitar acceso y comenzar a capturar el contenido de la pantalla.
  2. Un segundo botón, "Detener captura", que al hacer clic llama a stopCapture() para finalizar la captura del contenido de la pantalla.
  3. Un <video> en el que se transmiten los contenidos de la pantalla capturados.
  4. Un bloque <pre> en el que el método interceptado console coloca el texto registrado.

CSS

El CSS es completamente cosmético en este ejemplo. El video tiene un borde y su ancho se establece para ocupar casi todo el espacio horizontal disponible (ancho: 98%). max-width se establece en 860px para establecer un límite superior absoluto en el tamaño del video.

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

#log {
  width: 25rem;
  height: 15rem;
  border: 1px solid black;
  padding: 0.5rem;
  overflow: scroll;
}

Resultado

El producto final se ve así. Si su navegador es compatible con la API de captura de pantalla, al hacer clic en "Iniciar captura" se presentará la interfaz del user agent para seleccionar una pantalla, ventana o pestaña para compartir.

Seguridad

Para poder funcionar cuando la Política de permisos está habilitada, necesitarás el permiso display-capture. Esto se puede hacer usando el encabezado HTTP Permissions-Policy o, si está usando la API de captura de pantalla en un <iframe>, el atributo allow del elemento <iframe>.

Por ejemplo, esta línea en los encabezados HTTP habilitará la API de captura de pantalla para el documento y cualquier elemento <iframe> incrustado que se cargue desde el mismo origen:

http
Permissions-Policy: display-capture=(self)

Si está realizando una captura de pantalla dentro de un <iframe>, puede solicitar permiso solo para ese marco, lo que claramente es más seguro que solicitar permiso en general:

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

Compatibilidad con navegadores

BCD tables only load in the browser

Véase también