Picture-in-picture para qualquer elemento, não apenas <video>

François Beaufort
François Beaufort

Compatibilidade com navegadores

  • 116
  • 116
  • x
  • x

Origem

Com a API Document Picture-in-Picture, é possível abrir uma janela sempre ativa que pode ser preenchida com conteúdo HTML arbitrário. Ela estende a API Picture-in-Picture existente para <video> que permite apenas que um elemento HTML <video> seja colocado em uma janela Picture-in-Picture.

A janela Picture-in-Picture na API Document Picture-in-Picture é semelhante a uma janela em branco de mesma origem aberta pelo window.open(), com algumas diferenças:

  • A janela picture-in-picture flutua sobre as outras janelas.
  • A janela picture-in-picture nunca tem duração maior que a janela de abertura.
  • Não é possível navegar pela janela picture-in-picture.
  • A posição da janela picture-in-picture não pode ser definida pelo site.
Janela picture-in-picture mostrando o vídeo do trailer da Sintel.
Uma janela Picture-in-Picture criada com a API Document Picture-in-Picture (demonstração).

Status atual

Step Status
1. Criar explicação Concluído
2. Criar rascunho inicial da especificação Em andamento
3. Reunir feedbacks e iterar no design Em andamento
4. Teste de origem Concluído
5. lançamento Concluído (computador)

Casos de uso

Player de vídeo personalizado

Um site pode oferecer uma experiência de vídeo picture-in-picture com a API Picture-in-Picture existente do <video>, mas ela é muito limitada. A janela picture-in-picture atual aceita algumas entradas e tem capacidade limitada de estilização. Com um documento picture-in-picture completo, o site pode oferecer controles e entradas personalizados (por exemplo, legendas, playlists, barra de tempo, marcações "Gostei" e "Não gostei" em vídeos) para melhorar a experiência do usuário com o recurso.

Videoconferência

É comum que os usuários saiam da guia do navegador durante uma sessão de videoconferência por vários motivos (por exemplo, apresentando outra guia para a chamada ou realizando várias tarefas ao mesmo tempo) enquanto ainda querem ver a chamada. Esse é um dos principais casos de uso para o picture-in-picture. Mais uma vez, a experiência atual que um site de videoconferência pode oferecer com a API Picture-in-Picture para <video> é limitada em estilo e entrada. Com um documento no modo picture-in-picture completo, o site pode combinar vários streams de vídeo em uma única janela picture-in-picture sem precisar de hacks de tela e oferecer controles personalizados, como enviar uma mensagem, silenciar outro usuário ou levantar a mão.

Produtividade

Pesquisas demonstram que os usuários precisam de mais formas de serem produtivos na Web. O recurso "Document in picture-in-picture" dá aos aplicativos da Web flexibilidade para realizar mais. Seja edição de texto, anotações, listas de tarefas, mensagens e bate-papo ou ferramentas de design e desenvolvimento, os aplicativos da web agora podem manter seu conteúdo sempre acessível.

Interface

Propriedades

documentPictureInPicture.window
Retorna a janela picture-in-picture atual, se houver. Caso contrário, retorna null.

Métodos

documentPictureInPicture.requestWindow(options)

Retorna uma promessa que é resolvida quando uma janela Picture-in-Picture é aberta. A promessa vai ser rejeitada se for chamada sem um gesto do usuário. O dicionário options contém os seguintes membros opcionais:

width
Define a largura inicial da janela picture-in-picture.
height
Define a altura inicial da janela picture-in-picture.
disallowReturnToOpener
Oculta o botão "Voltar à guia" na janela picture-in-picture se verdadeiro. É falso por padrão.

Eventos

documentPictureInPicture.onenter
Acionado em documentPictureInPicture quando uma janela Picture-in-Picture é aberta.

Exemplos

O HTML a seguir configura um player de vídeo personalizado e um elemento de botão para abrir o player em uma janela Picture-in-Picture.

<div id="playerContainer">
  <div id="player">
    <video id="video"></video>
  </div>
</div>
<button id="pipButton">Open Picture-in-Picture window</button>

Abrir uma janela picture-in-picture

O JavaScript a seguir chama documentPictureInPicture.requestWindow() quando o usuário clica no botão para abrir uma janela picture-in-picture em branco. A promessa retornada é resolvida com um objeto JavaScript de janela picture-in-picture. O player de vídeo é movido para essa janela usando append().

pipButton.addEventListener('click', async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

Definir o tamanho da janela picture-in-picture

Para definir o tamanho da janela picture-in-picture, defina as opções width e height de documentPictureInPicture.requestWindow() como o tamanho desejado. O Google Chrome poderá limitar os valores de opção se eles forem muito grandes ou muito pequenos para se ajustarem a um tamanho de janela que seja fácil de usar.

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window whose size is
  // the same as the player's.
  const pipWindow = await documentPictureInPicture.requestWindow({
    width: player.clientWidth,
    height: player.clientHeight,
  });

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

Oculte o botão "Voltar à guia" da janela picture-in-picture

Para ocultar o botão na janela picture-in-picture que permite ao usuário voltar para a guia que o abre, defina a opção disallowReturnToOpener de documentPictureInPicture.requestWindow() como true.

pipButton.addEventListener("click", async () => {
  // Open a Picture-in-Picture window which hides the "back to tab" button.
  const pipWindow = await documentPictureInPicture.requestWindow({
    disallowReturnToOpener: true,
  });
});

Copiar folhas de estilo para a janela Picture-in-Picture

Para copiar todas as folhas de estilo CSS da janela de origem, passe por styleSheets explicitamente vinculado ou incorporado ao documento e anexe-as à janela Picture-in-Picture. Esta cópia é única.

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Copy style sheets over from the initial document
  // so that the player looks the same.
  [...document.styleSheets].forEach((styleSheet) => {
    try {
      const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
      const style = document.createElement('style');

      style.textContent = cssRules;
      pipWindow.document.head.appendChild(style);
    } catch (e) {
      const link = document.createElement('link');

      link.rel = 'stylesheet';
      link.type = styleSheet.type;
      link.media = styleSheet.media;
      link.href = styleSheet.href;
      pipWindow.document.head.appendChild(link);
    }
  });

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);
});

Processar o fechamento da janela picture-in-picture

Ouça o evento de janela "pagehide" para saber quando a janela picture-in-picture é fechada, seja porque o site a iniciou ou o usuário a fechou manualmente. O manipulador de eventos é um bom lugar para retirar os elementos da janela picture-in-picture, como mostrado abaixo.

pipButton.addEventListener("click", async () => {
  const player = document.querySelector("#player");

  // Open a Picture-in-Picture window.
  const pipWindow = await documentPictureInPicture.requestWindow();

  // Move the player to the Picture-in-Picture window.
  pipWindow.document.body.append(player);

  // Move the player back when the Picture-in-Picture window closes.
  pipWindow.addEventListener("pagehide", (event) => {
    const playerContainer = document.querySelector("#playerContainer");
    const pipPlayer = event.target.querySelector("#player");
    playerContainer.append(pipPlayer);
  });
});

Feche a janela picture-in-picture de forma programática usando o método close().

// Close the Picture-in-Picture window programmatically. 
// The "pagehide" event will fire normally.
pipWindow.close();

Ouvir quando o site entra no modo picture-in-picture

Ouça o evento "enter" no documentPictureInPicture para saber quando uma janela picture-in-picture é aberta. O evento contém um objeto window para acessar a janela picture-in-picture.

documentPictureInPicture.addEventListener("enter", (event) => {
  const pipWindow = event.window;
});

Acessar elementos na janela picture-in-picture

Acesse elementos na janela picture-in-picture a partir do objeto retornado por documentPictureInPicture.requestWindow() ou com documentPictureInPicture.window, conforme mostrado abaixo.

const pipWindow = documentPictureInPicture.window;
if (pipWindow) {
  // Mute video playing in the Picture-in-Picture window.
  const pipVideo = pipWindow.document.querySelector("#video");
  pipVideo.muted = true;
}

Processar eventos da janela picture-in-picture

Crie botões e controles e responda aos eventos de entrada do usuário, como "click", da mesma forma que você faria normalmente em JavaScript.

// Add a "mute" button to the Picture-in-Picture window.
const pipMuteButton = pipWindow.document.createElement("button");
pipMuteButton.textContent = "Mute";
pipMuteButton.addEventListener("click", () => { 
  const pipVideo = pipWindow.document.querySelector("#video");
  pipVideo.muted = true;
});
pipWindow.document.body.append(pipMuteButton);

Redimensionar a janela picture-in-picture

Use os métodos Janela resizeBy() e resizeTo() para redimensionar a janela picture-in-picture. Os dois métodos exigem um gesto do usuário.

const resizeButton = pipWindow.document.createElement('button');
resizeButton.textContent = 'Resize';
resizeButton.addEventListener('click', () => {
  // Expand the Picture-in-Picture window's width by 20px and height by 30px.
  pipWindow.resizeBy(20, 30);
});
pipWindow.document.body.append(resizeButton);

Focar na janela que abre

Use o método focus() de janela para focar a janela de abertura em "Picture-in-picture". Esse método exige um gesto do usuário.

const returnToTabButton = pipWindow.document.createElement("button");
returnToTabButton.textContent = "Return to opener tab";
returnToTabButton.addEventListener("click", () => {
  window.focus();
});
pipWindow.document.body.append(returnToTabButton);

Modo de exibição picture-in-picture do CSS

Use o modo de exibição CSS picture-in-picture para criar regras CSS específicas que são aplicadas somente quando (parte do) o app da Web é mostrado no modo picture-in-picture.

@media all and (display-mode: picture-in-picture) {
  body {
    margin: 0;
  }
  h1 {
    font-size: 0.8em;
  }
}

Detecção de recursos

Para verificar se a API Document Picture-in-Picture é compatível, use:

if ('documentPictureInPicture' in window) {
  // The Document Picture-in-Picture API is supported.
}

Demonstrações

Player de VideoJS

É possível jogar com a demonstração do player VideoJS da API Document Picture-in-Picture. Confira o código-fonte.

Pomodoro

O Tomodoro, um app da Web pomodoro, também usa a API Document Picture-in-Picture quando disponível (consulte a solicitação de envio do GitHub).

Captura de tela do Tomodoro, um app da Web pomodoro.
Uma janela picture-in-picture no Tomodoro.

Feedback

Registre problemas no GitHub (em inglês) com sugestões e perguntas.

Agradecimentos

Imagem principal de Jakob Owens.