Mira el video con la función Pantalla en pantalla

François Beaufort
François Beaufort

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

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

Segundo plano

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 comenzó a reproducir automáticamente videos en pantalla en miniatura en dispositivos móviles con el lanzamiento de Android O a través de una API nativa de Android. Seis meses después, anunciamos nuestra intención de compilar y estandarizar una API web, compatible con la de Safari, que permitiera a los desarrolladores web crear y controlar la experiencia completa de la función Pantalla en pantalla. ¡Y listo!

Cómo ingresar al código

Ingresar al modo de pantalla en pantalla

Comencemos 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>

Solicita el modo Picture-in-Picture solo 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 ventana pequeña que el usuario puede mover y posicionar sobre otras ventanas.

Eso es todo. ¡Bien hecho! Puedes dejar de leer y tomarte unas vacaciones más que necesarias. Lamentablemente, no siempre es así. La promesa puede rechazarse por cualquiera de los siguientes motivos:

  • El sistema no admite la función Pantalla en pantalla.
  • El documento no puede usar 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 solo de 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 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 posibles errores y avisarle al usuario 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 el modo de pantalla en pantalla o no: se activan los eventos y funcionan los métodos de llamada. Refleja los cambios de estado en la ventana de pantalla en pantalla (como reproducir, pausar, buscar, etcétera) y también es posible cambiar el estado de forma programática en JavaScript.

Cómo salir del modo de pantalla en pantalla

Ahora, hagamos que nuestro botón active y desactive la pantalla en pantalla. Primero, debemos verificar si el objeto de solo lectura document.pictureInPictureElement es nuestro elemento de video. De lo contrario, enviamos una solicitud para ingresar al modo Imagen en imagen 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 muestra 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 en pantalla en pantalla a la vez. Debes esperar que los usuarios salgan del modo Picture-in-Picture incluso cuando no lo solicites.

Los nuevos controladores de eventos enterpictureinpicture y leavepictureinpicture nos permiten adaptar la experiencia para los usuarios. Puede ser cualquier cosa, desde explorar un catálogo de videos hasta mostrar un chat de 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, pista anterior y pista siguiente en la ventana de pantalla en pantalla que puedes controlar con la API de Media Session.

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

De forma predeterminada, siempre se muestra un botón de reproducción/pausa en la ventana de PiP, a menos que el video esté reproduciendo objetos MediaStream (p.ej., getUserMedia(), getDisplayMedia(), canvas.captureStream()) o tenga una duración de MediaSource establecida en +Infinity (p.ej., feed en vivo). Para asegurarte de que un botón de reproducción o pausa siempre esté visible, establece algunos controladores de acciones de la sesión multimedia para los eventos multimedia "Reproducir" y "Pausar" como se indica 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.
});

Mostrar los controles de la ventana "Pista anterior" y "Pista siguiente" es similar. Si configuras los controladores de acciones de Media Session para ellos, se mostrarán en la ventana de 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 Media Session.

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

Si quieres ajustar la calidad del video cuando entra y sale de la pantalla en pantalla, debes conocer el tamaño de la ventana de la pantalla en pantalla 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 la altura de la ventana de PiP cuando se crea o se cambia 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 sugiero que no crees una vinculación directa al evento de cambio de tamaño, ya que cada pequeño cambio que se realice en el tamaño de la ventana de la función Pantalla en pantalla activará un evento independiente 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 una y otra vez con mucha rapidez. Te recomiendo usar técnicas comunes, como la limitación y el control de rebote, para abordar este problema.

Compatibilidad de características

Es posible que la API web de Picture-in-Picture no sea compatible, por lo que debes detectar esto para proporcionar una mejora progresiva. Incluso cuando es compatible, el usuario puede desactivarlo o inhabilitarlo con una política de permisos. Afortunadamente, puedes usar el nuevo valor booleano document.pictureInPictureEnabled para determinarlo.

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

Si se aplica a un elemento de botón específico para un video, esta es la forma en que puedes controlar la visibilidad del botón de pantalla en pantalla.

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 de MediaStream

Los objetos MediaStream que reproducen videos (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 de la cámara web del usuario, la transmisión de video de la pantalla o incluso un elemento de lienzo. Ten en cuenta que el elemento de video no tiene que adjuntarse al DOM para ingresar al modo Picture-in-Picture, como se muestra a continuación.

Cómo 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();

Cómo mostrar la pantalla en la ventana de 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();

Cómo mostrar el elemento del 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, por ejemplo, puedes crear una ventana de playlist de audio en Chrome 74. Consulta el ejemplo oficial de playlist de audio.

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

Muestras, demostraciones y codelabs

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

Luego, se agregarán demostraciones y codelabs.

¿Qué sigue?

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

Esto es lo que puedes esperar ver en un futuro cercano:

Navegadores compatibles

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

Recursos

Muchas gracias a Mounir Lamouri y Jennifer Apacible por su trabajo en la función Pantalla en pantalla y por ayudar con este artículo. Y muchas gracias a todos los que participaron en el esfuerzo de estandarización.