Mira el video con la función Pantalla en pantalla

François Beaufort
François Beaufort

La función pantalla en pantalla (PIP) permite a los usuarios mirar videos en una ventana flotante (siempre encima de otras ventanas) para que puedan supervisar lo que están mirando mientras interactúan con otros sitios o aplicaciones.

Con la API web de pantalla en pantalla, puedes iniciar y controlar esta función en los elementos de video de tu sitio web. Pruébala en nuestra muestra de pantalla en pantalla oficial.

Información general

En septiembre de 2016, Safari agregó compatibilidad con la función pantalla en pantalla a través de una API de WebKit en macOS Sierra. Seis meses después, Chrome reprodujo automáticamente videos de pantalla en pantalla en dispositivos móviles con el lanzamiento de Android O mediante una API nativa de Android. Seis meses después, anunciamos nuestra intención de compilar y estandarizar una API web, función compatible con Safari, que permitiría a los desarrolladores web crear y controlar la experiencia completa de la función pantalla en pantalla. ¡Y aquí estamos!

Ingresa al código

Ingresar al modo de pantalla en pantalla

Comencemos solo con un elemento de video y una forma para que el usuario interactúe con él, como un elemento de botón.

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

Solo solicita pantalla en pantalla en respuesta a un gesto del usuario y nunca en la promesa que muestra videoElement.play(). Esto se debe a que las promesas aún no propagan los gestos del usuario. En su lugar, llama a requestPictureInPicture() en un controlador de clics en pipButtonElement, como se muestra a continuación. Es tu responsabilidad controlar lo que sucede si un usuario hace clic dos veces.

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

Cuando se resuelve la promesa, Chrome reduce el video a una pequeña ventana que el usuario puede mover y posicionar sobre otras ventanas.

Eso es todo. ¡Bien hecho! Puedes dejar de leer y tomarte tus merecidos vacaciones. Lamentablemente, no siempre es así. La promesa se puede rechazar por cualquiera de los siguientes motivos:

  • El sistema no admite la función pantalla en pantalla.
  • No se permite que el documento use la función pantalla en pantalla debido a una política de permisos restrictiva.
  • Aún no se cargaron los metadatos del video (videoElement.readyState === 0).
  • El archivo de video es de solo audio.
  • El nuevo atributo disablePictureInPicture está presente en el elemento de video.
  • La llamada no se realizó en un controlador de eventos de gestos del usuario (p.ej., un clic en un botón). A partir de Chrome 74, esto solo se aplica si aún no hay un elemento en la función Pantalla en pantalla.

En la sección Compatibilidad con funciones que aparece a continuación, se muestra cómo habilitar o inhabilitar un botón según estas restricciones.

Agreguemos un bloque try...catch para capturar estos errores potenciales y permitir que el usuario sepa lo que sucede.

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  try {
    await videoElement.requestPictureInPicture();
  } catch (error) {
    // TODO: Show error message to user.
  } finally {
    pipButtonElement.disabled = false;
  }
});

El elemento de video se comporta de la misma manera, ya sea que esté en pantalla en pantalla o no: se activan los eventos y los métodos de llamada funcionan. Refleja los cambios de estado en la ventana pantalla en pantalla (como reproducir, pausar, buscar, etc.) y también es posible cambiar el estado de manera programática en JavaScript.

Salir del modo de pantalla en pantalla

Ahora, activemos el botón de activación para entrar y salir del modo de pantalla en pantalla. Primero, debemos verificar si el objeto de solo lectura document.pictureInPictureElement es nuestro elemento de video. Si no es así, enviaremos una solicitud para activar la función Pantalla en pantalla, como se indicó anteriormente. De lo contrario, llamamos a document.exitPictureInPicture() para salir, lo que significa que el video volverá a aparecer en la pestaña original. Ten en cuenta que este método también devuelve una promesa.

    ...
    try {
      if (videoElement !== document.pictureInPictureElement) {
        await videoElement.requestPictureInPicture();
      } else {
        await document.exitPictureInPicture();
      }
    }
    ...

Cómo escuchar eventos de pantalla en pantalla

Los sistemas operativos suelen restringir la función pantalla en pantalla a una ventana, por lo que la implementación de Chrome sigue este patrón. Esto significa que los usuarios solo pueden reproducir un video de pantalla en pantalla a la vez. Debes esperar que los usuarios salgan del modo de pantalla en pantalla aunque no lo hayas solicitado.

Los nuevos controladores de eventos enterpictureinpicture y leavepictureinpicture nos permiten personalizar la experiencia de los usuarios. Puede ser desde navegar por un catálogo de videos hasta mostrar el chat de una transmisión en vivo.

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

Cómo personalizar la ventana pantalla en pantalla

Chrome 74 admite los botones de reproducción/pausa, la pista anterior y la siguiente en la ventana de pantalla en pantalla que puedes controlar con la API de Media Session.

Controles de reproducción de contenido multimedia en una ventana pantalla en pantalla
Figura 1: Controles de reproducción de contenido multimedia en una ventana pantalla en pantalla

De forma predeterminada, siempre se muestra un botón de reproducción/pausa en la ventana Pantalla en pantalla, a menos que el video reproduzca objetos MediaStream (p.ej., getUserMedia(), getDisplayMedia(), canvas.captureStream()) o que el video tenga una duración de MediaSource establecida en +Infinity (p.ej., el feed en vivo). Para asegurarte de que el botón de reproducción/pausa esté siempre visible, configura los controladores de acciones de la sesión multimedia para los eventos multimedia "Reproducir" y "Pausar", como se muestra a continuación.

// 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.
});

Los controles de la ventana "Pista anterior" y "Pista siguiente" son similares. Si configuras controladores de acciones de sesiones multimedia para estos, se mostrarán en la ventana pantalla en pantalla y podrás controlar estas acciones.

navigator.mediaSession.setActionHandler('previoustrack', function () {
  // User clicked "Previous Track" button.
});

navigator.mediaSession.setActionHandler('nexttrack', function () {
  // User clicked "Next Track" button.
});

Para ver esto en acción, prueba el ejemplo oficial de sesiones multimedia.

Cómo obtener el tamaño de ventana pantalla en pantalla

Si quieres ajustar la calidad del video cuando este ingresa o sale del modo de pantalla en pantalla, debes conocer el tamaño de la ventana y recibir una notificación si un usuario cambia el tamaño de la ventana de forma manual.

En el siguiente ejemplo, se muestra cómo obtener el ancho y el alto de la ventana de pantalla en pantalla cuando la creas o le cambias su tamaño.

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

Te recomendamos que no te vincules directamente al evento de cambio de tamaño, ya que cada cambio pequeño que se realice en el tamaño de la ventana pantalla en pantalla activará un evento separado que puede causar problemas de rendimiento si realizas una operación costosa en cada cambio de tamaño. En otras palabras, la operación de cambio de tamaño activará los eventos con mucha rapidez. Te recomendamos usar técnicas comunes, como la regulación y la devolución para solucionar este problema.

Compatibilidad de características

Es posible que la API web de pantalla en pantalla no sea compatible, por lo que debes detectarla para proporcionar una mejora progresiva. Incluso si es compatible, es posible que el usuario la desactive o que una política de permisos la inhabilite. Afortunadamente, puedes usar el nuevo booleano document.pictureInPictureEnabled para determinar esto.

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.');
}

Así es como te recomendamos que se aplique la visibilidad del botón de pantalla en pantalla a un elemento de botón específico de un video.

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

Compatibilidad con video MediaStream

Los videos que reproducen objetos MediaStream (p.ej., getUserMedia(), getDisplayMedia() y canvas.captureStream()) también admiten la función pantalla en pantalla en Chrome 71. Esto significa que puedes mostrar una ventana de pantalla en pantalla que contenga la transmisión de video por Internet de la cámara web del usuario, la transmisión de video en pantalla o incluso un elemento de lienzo. Ten en cuenta que no es necesario adjuntar el elemento de video al DOM para activar la función Pantalla en pantalla, como se muestra a continuación.

Mostrar la cámara web del usuario en la ventana pantalla en pantalla

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

Mostrar la pantalla en la ventana pantalla en pantalla

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

Mostrar elemento lienzo en la ventana pantalla en pantalla

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();

Si combinas canvas.captureStream() con la API de Media Session, puedes, por ejemplo, crear una ventana de lista de reproducción de audio en Chrome 74. Consulta el ejemplo de playlist de audio oficial.

Playlist de audio en una ventana pantalla en pantalla
Figura 2: Lista de reproducción de audio en una ventana pantalla en pantalla

Muestras, demostraciones y codelabs

Consulta nuestra muestra de pantalla en pantalla oficial para probar la API web de la función.

Más adelante se incluirán las demostraciones y los codelabs.

¿Qué sigue?

Primero, consulta la página del estado de la implementación para saber qué partes de la API están implementadas actualmente en Chrome y otros navegadores.

Esto es lo que puedes esperar en un futuro cercano:

Navegadores compatibles

La API web de pantalla en pantalla es compatible con Chrome, Edge, Opera y Safari. Consulta MDN para obtener más información.

Recursos

Muchas gracias a Mounir Lamouri y Jennifer Apacible por su trabajo con la función Pantalla en pantalla y por su ayuda con este artículo. Agradecemos sinceramente a todos quienes participaron en esta esfuerzo de estandarización.