<동영상> 뿐만 아니라 모든 요소에 대한 PIP 모드

François Beaufort
François Beaufort

브라우저 지원

  • 116
  • 116
  • x
  • x

소스

Document Picture-in-Picture API를 사용하면 임의의 HTML 콘텐츠로 채울 수 있는 상시 사용 설정 창을 열 수 있습니다. HTML <video> 요소만 PIP 창에 배치할 수 있도록 하는 기존 <video>용 PIP 모드 API를 확장합니다.

Document Picture-in-Picture API의 PIP 창은 window.open()를 통해 열리는 빈 동일 출처 창과 비슷하지만 몇 가지 차이점이 있습니다.

  • PIP 모드 창이 다른 창 위에 떠 있습니다.
  • PIP 모드 창은 항상 열린 창보다 오래 지속됩니다.
  • PIP 창은 탐색할 수 없습니다.
  • PIP 모드 창 위치는 웹사이트에서 설정할 수 없습니다.
Sintel 트레일러 동영상이 재생되는 PIP 모드 창입니다.
Document Picture-in-Picture API로 만든 PIP 모드 창 (데모).

현재 상태

단계 상태
1. 설명 만들기 완전함
2. 사양의 초기 초안 만들기 진행 중
3. 의견 수집 및 디자인 반복 진행 중
4. 오리진 트라이얼 완전함
5. 출시 완료 (데스크톱)

사용 사례

맞춤 동영상 플레이어

웹사이트에서 기존의 <video>용 PIP 모드 API를 사용하여 PIP 모드 동영상 환경을 제공할 수 있지만 매우 제한적입니다. 기존의 PIP 모드 창에는 몇 가지 입력만 허용되며 스타일 지정 기능이 제한됩니다. PIP 모드의 전체 문서를 통해 웹사이트에서는 맞춤 컨트롤 및 입력 (예: 자막, 재생목록, 타임 스크러버, 동영상에 좋아요 및 싫어요 표시)을 제공하여 사용자의 PIP 동영상 경험을 개선할 수 있습니다.

화상 회의

사용자가 통화 중 다른 탭을 발표하거나 멀티태스킹하는 등 다양한 이유로 화상 회의 세션 중에 브라우저 탭을 나가는 동시에 통화를 계속 원하는 경우도 많으므로 PIP 모드가 가장 많이 사용됩니다. 다시 한번 말씀드리지만, 현재 화상 회의 웹사이트에서 <video>용 PIP 모드 API를 통해 제공할 수 있는 환경은 스타일과 입력 면에서 제한되어 있습니다. PIP 모드의 전체 문서를 사용하면 웹사이트에서 캔버스 해킹을 할 필요 없이 여러 동영상 스트림을 하나의 PIP 창으로 결합하고 메시지 보내기, 다른 사용자 음소거, 손들기 등의 맞춤 컨트롤을 제공할 수 있습니다.

생산성

연구에 따르면 사용자는 웹에서 생산성을 높일 수 있는 방법이 더 많이 필요합니다. PIP 모드로 문서는 웹 앱에서 더 많은 작업을 할 수 있는 유연성을 제공합니다. 이제 웹 앱에서 텍스트 편집, 메모 작성, 작업 목록, 메시지 및 채팅, 디자인 및 개발 도구 등 어떤 작업에든 언제든지 콘텐츠에 액세스할 수 있습니다.

인터페이스

속성

documentPictureInPicture.window
현재 PIP 모드 창이 있는 경우 반환합니다. 그렇지 않으면 null를 반환합니다.

방법

documentPictureInPicture.requestWindow(options)

PIP 모드 창이 열릴 때 확인되는 프로미스를 반환합니다. 프로미스는 사용자 동작 없이 호출되면 거부됩니다. options 사전에는 다음 멤버(선택사항)가 포함되어 있습니다.

width
PIP 모드 창의 초기 너비를 설정합니다.
height
PIP 모드 창의 초기 높이를 설정합니다.
disallowReturnToOpener
true인 경우 PIP 모드 창에서 '탭으로 돌아가기' 버튼을 숨깁니다. 기본적으로 false입니다.

이벤트

documentPictureInPicture.onenter
documentPictureInPicture에 PIP 모드 창이 열릴 때 실행됩니다.

다음 HTML은 맞춤 동영상 플레이어와 버튼 요소를 설정하여 PIP 모드에서 동영상 플레이어를 엽니다.

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

PIP 모드 창 열기

다음 JavaScript는 사용자가 버튼을 클릭하여 빈 PIP 모드 창을 열 때 documentPictureInPicture.requestWindow()를 호출합니다. 반환된 프로미스는 PIP 모드 창 JavaScript 객체로 확인됩니다. 동영상 플레이어가 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);
});

PIP 모드 창의 크기 설정

PIP 모드 창의 크기를 설정하려면 documentPictureInPicture.requestWindow()widthheight 옵션을 원하는 PIP 모드 창 크기로 설정합니다. 옵션 값이 너무 크거나 작으면 사용자 친화적인 창 크기에 맞지 않을 경우 Chrome에서 해당 옵션 값을 고정할 수 있습니다.

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

PIP 모드 창의 '탭으로 돌아가기' 버튼 숨기기

PIP 모드에서 사용자가 시작 탭으로 돌아갈 수 있는 버튼을 숨기려면 documentPictureInPicture.requestWindow()disallowReturnToOpener 옵션을 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,
  });
});

PIP 모드 창에 스타일 시트 복사

원래 창에서 모든 CSS 스타일시트를 복사하려면 문서에 명시적으로 연결되었거나 문서에 삽입되어 있는 styleSheets를 반복하여 PIP 모드 창에 추가합니다. 이는 일회성 사본입니다.

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

PIP 모드 창이 닫힐 때 처리

"pagehide" 이벤트를 수신하여 PIP 모드 창이 닫히는 시점 (웹사이트에서 시작했거나 사용자가 수동으로 닫았기 때문에)을 파악합니다. 이벤트 핸들러는 아래와 같이 PIP 모드 창 밖으로 요소를 다시 가져오기에 좋은 위치입니다.

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

close() 메서드를 사용하여 프로그래매틱 방식으로 PIP 모드 창을 닫습니다.

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

웹사이트에서 PIP 모드로 전환되면 듣습니다.

documentPictureInPicture에서 "enter" 이벤트를 수신하여 PIP 모드 창이 언제 열렸는지 확인합니다. 이벤트에는 PIP 모드 창에 액세스하기 위한 window 객체가 포함됩니다.

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

PIP 모드 창에서 요소에 액세스

아래와 같이 documentPictureInPicture.requestWindow()에서 반환하거나 documentPictureInPicture.window를 사용하여 PIP 모드 창의 요소에 액세스합니다.

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

PIP 모드 창에서 이벤트 처리

JavaScript에서 일반적으로 하는 것처럼 버튼과 컨트롤을 만들고 "click"와 같은 사용자 입력 이벤트에 응답합니다.

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

PIP 모드 창 크기 조절

resizeBy()resizeTo() 창 메서드를 사용하여 PIP 모드 창 크기를 조절합니다. 두 메서드 모두 사용자 동작이 필요합니다.

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

시작 창에 포커스

focus() 창 메서드를 사용하여 PIP 모드 창에서 오프너 창에 포커스를 맞춥니다. 이 메서드에는 사용자 동작이 필요합니다.

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

CSS PIP 모드 표시 모드

CSS picture-in-picture 표시 모드를 사용하여 웹 앱이 PIP 모드로 표시될 때만 적용되는 특정 CSS 규칙을 작성할 수 있습니다.

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

기능 감지

Document Picture-in-Picture API가 지원되는지 확인하려면 다음을 사용하세요.

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

데모

VideoJS 플레이어

Document PIP 모드 API VideoJS 플레이어 데모로 재생할 수 있습니다. 소스 코드를 확인합니다.

포모도로

pomodoro 웹 앱인 Tomodoro도 가능한 경우 Document Picture-in-Picture API를 활용하고 있습니다 (GitHub pull 요청 참고).

pomodoro 웹 앱 Tomodoro의 스크린샷
도모도로의 PIP 모드 창

의견

제안사항과 질문이 있으시면 GitHub에서 문제를 제출해 주세요.

감사의 말

Jakob Owens의 히어로 이미지