PIP 모드를 사용하여 동영상 보기

François Beaufort
François Beaufort

PIP 모드를 사용하면 사용자가 플로팅 창(항상 다른 창 위에 표시)에서 동영상을 시청할 수 있으므로 다른 사이트 또는 애플리케이션과 상호작용하는 동안 시청 중인 동영상을 계속 볼 수 있습니다.

PIP 모드 웹 API를 사용하면 웹사이트에서 동영상 요소의 PIP 모드를 시작하고 제어할 수 있습니다. 공식 PIP 모드 샘플에서 사용해 보세요.

배경

2016년 9월, Safari는 macOS Sierra에서 WebKit API를 통해 PIP 모드 지원을 추가했습니다. 6개월 후 Chrome은 Android O 출시와 함께 네이티브 Android API를 사용하여 모바일에서 PIP 동영상을 자동으로 재생했습니다. 6개월 후 Google은 웹 개발자가 PIP와 관련된 전체 환경을 만들고 제어할 수 있도록 Safari와 호환되는 Web API를 빌드하고 표준화하겠다는 의도를 발표했습니다. 이제 준비되었습니다.

코드 살펴보기

PIP 모드 시작

동영상 요소와 사용자가 상호작용할 수 있는 방법(예: 버튼 요소)부터 간단하게 시작해 보겠습니다.

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

사용자 동작에 대한 응답으로만 PIP 모드를 요청하고 videoElement.play()에서 반환된 프라미스에서는 절대 요청하지 않습니다. 이는 프라미스가 아직 사용자 동작을 전파하지 않기 때문입니다. 대신 아래와 같이 pipButtonElement의 클릭 핸들러에서 requestPictureInPicture()를 호출합니다. 사용자가 두 번 클릭하면 어떻게 되는지 처리하는 것은 개발자의 책임입니다.

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

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

약속이 해결되면 Chrome은 동영상을 사용자가 이동하고 다른 창 위에 배치할 수 있는 작은 창으로 축소합니다.

완료되었습니다. 훌륭합니다. 이제 읽기를 중지하고 기다려온 휴가를 떠나세요. 안타깝게도 항상 그런 것은 아닙니다. 다음과 같은 이유로 약속이 거부될 수 있습니다.

  • 시스템에서 PIP 모드를 지원하지 않습니다.
  • 제한적인 권한 정책으로 인해 문서에서 PIP 모드를 사용할 수 없습니다.
  • 동영상 메타데이터가 아직 로드되지 않았습니다 (videoElement.readyState === 0).
  • 동영상 파일이 오디오 전용입니다.
  • 동영상 요소에 새 disablePictureInPicture 속성이 있습니다.
  • 사용자 동작 이벤트 핸들러 (예: 버튼 클릭)에서 호출되지 않았습니다. Chrome 74부터 이 속성은 PIP에 아직 요소가 없는 경우에 만 적용됩니다.

아래의 기능 지원 섹션에서는 이러한 제한사항에 따라 버튼을 사용 설정/중지하는 방법을 보여줍니다.

이러한 잠재적 오류를 포착하고 사용자에게 상황을 알리는 try...catch 블록을 추가해 보겠습니다.

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

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

동영상 요소는 PIP 모드인지 여부와 상관없이 동일하게 작동합니다. 이벤트가 실행되고 호출 메서드가 작동합니다. PIP 모드 창의 상태 변경사항 (예: 재생, 일시중지, 탐색 등)을 반영하며 JavaScript에서 프로그래매틱 방식으로 상태를 변경할 수도 있습니다.

PIP 모드 종료

이제 버튼을 사용하여 PIP 모드로 전환하고 종료해 보겠습니다. 먼저 읽기 전용 객체 document.pictureInPictureElement가 동영상 요소인지 확인해야 합니다. 그렇지 않은 경우 위와 같이 PIP를 시작하도록 요청이 전송됩니다. 그렇지 않으면 document.exitPictureInPicture()를 호출하여 나가도록 요청합니다. 즉, 동영상이 원래 탭에 다시 표시됩니다. 이 메서드는 약속도 반환합니다.

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

PIP 이벤트 수신

운영체제는 일반적으로 PIP를 하나의 창으로 제한하므로 Chrome의 구현은 이 패턴을 따릅니다. 즉, 사용자는 한 번에 하나의 PIP 동영상만 재생할 수 있습니다. 개발자가 요청하지 않았더라도 사용자가 PIP를 종료할 수 있습니다.

새로운 enterpictureinpictureleavepictureinpicture 이벤트 핸들러를 사용하면 사용자에게 맞게 환경을 조정할 수 있습니다. 동영상 카탈로그를 둘러보는 것부터 라이브 스트림 채팅을 표시하는 것까지 다양합니다.

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

PIP 모드 창 맞춤설정

Chrome 74에서는 Media Session API를 사용하여 제어할 수 있는 PIP 창의 재생/일시중지, 이전 트랙, 다음 트랙 버튼을 지원합니다.

PIP 모드 창의 미디어 재생 컨트롤
그림 1. PIP 모드 창의 미디어 재생 컨트롤

기본적으로 동영상에서 MediaStream 객체 (예: getUserMedia(), getDisplayMedia(), canvas.captureStream())를 재생 중이거나 동영상의 MediaSource 길이가 +Infinity로 설정된 경우 (예: 라이브 피드)를 제외하고 PIP 창에 항상 재생/일시중지 버튼이 표시됩니다. 재생/일시중지 버튼이 항상 표시되도록 하려면 아래와 같이 '재생' 및 '일시중지' 미디어 이벤트 모두에 대한 미디어 세션 작업 핸들러를 설정하세요.

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

'이전 트랙' 및 '다음 트랙' 창 컨트롤을 표시하는 방법도 비슷합니다. 이에 대한 미디어 세션 작업 핸들러를 설정하면 PIP 모드 창에 표시되며 이러한 작업을 처리할 수 있습니다.

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

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

실제 동작을 보려면 공식 미디어 세션 샘플을 사용해 보세요.

PIP 모드 창 크기 가져오기

동영상이 PIP 모드로 전환되고 종료될 때 동영상 화질을 조정하려면 PIP 모드 창 크기를 알고 있어야 하며 사용자가 창 크기를 수동으로 조정하면 알림을 받아야 합니다.

아래 예는 PIP 모드를 만들거나 크기를 조절할 때 이 창의 너비와 높이를 가져오는 방법을 보여줍니다.

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

크기 조절 이벤트에 직접 연결하지 않는 것이 좋습니다. PIP 모드 창 크기에 대한 작은 변경사항마다 별도의 이벤트가 실행되므로 크기 조절마다 비용이 많이 드는 작업을 실행하는 경우 성능 문제를 일으킬 수 있습니다. 즉, 크기 조절 작업은 이벤트를 매우 빠르게 반복적으로 실행합니다. 이 문제를 해결하려면 제한 및 튀김 방지와 같은 일반적인 기법을 사용하는 것이 좋습니다.

기능 지원

Picture-in-Picture Web API가 지원되지 않을 수 있으므로 점진적으로 개선을 제공하려면 이를 감지해야 합니다. 지원되는 경우에도 사용자가 사용 중지하거나 권한 정책에 의해 사용 중지될 수 있습니다. 다행히 새 불리언 document.pictureInPictureEnabled를 사용하여 이를 확인할 수 있습니다.

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

동영상의 특정 버튼 요소에 적용하면 PIP 버튼 공개 상태를 처리할 수 있습니다.

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

MediaStream 동영상 지원

MediaStream 객체 (예: getUserMedia(), getDisplayMedia(), canvas.captureStream())를 재생하는 동영상도 Chrome 71에서 PIP를 지원합니다. 즉, 사용자의 웹캠 동영상 스트림, 디스플레이 동영상 스트림 또는 캔버스 요소가 포함된 PIP 창을 표시할 수 있습니다. 참고로 아래와 같이 동영상 요소를 DOM에 연결하지 않아도 PIP 모드를 시작할 수 있습니다.

PIP 모드 창에 사용자의 웹캠 표시

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

// Later on, video.requestPictureInPicture();

PIP 모드 창에 디스플레이 표시

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

// Later on, video.requestPictureInPicture();

PIP 모드 창에 캔버스 요소 표시

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

예를 들어 canvas.captureStream()Media Session API와 결합하면 Chrome 74에서 오디오 재생목록 창을 만들 수 있습니다. 공식 오디오 재생목록 샘플을 확인하세요.

PIP 모드 창의 오디오 재생목록
그림 2. PIP 모드 창의 오디오 재생목록

샘플, 데모, Codelab

공식 PIP 모드 샘플을 확인하여 PIP 모드 웹 API를 사용해 보세요.

데모와 Codelab도 제공될 예정입니다.

다음 단계

먼저 구현 상태 페이지를 확인하여 현재 Chrome 및 다른 브라우저에 구현된 API의 부분을 알아봅니다.

가까운 시일 내에 다음과 같은 기능이 제공될 예정입니다.

브라우저 지원

PiP Web API는 Chrome, Edge, Opera, Safari에서 지원됩니다. 자세한 내용은 MDN을 참고하세요.

리소스

이 기사에 도움을 주신 Mounir Lamouri와 Jennifer Apacible에게 PIP 모드 작업에 진심으로 감사드립니다. 표준화 작업에 참여한 모든 분께도 감사드립니다.