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

François Beaufort
François Beaufort

PIP 모드를 사용하면 사용자가 플로팅 창(항상 다른 창 위에 있음)에서 동영상을 볼 수 있으므로 다른 사이트 또는 애플리케이션과 상호작용하는 동안 보고 있는 내용을 지켜볼 수 있습니다.

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

배경

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

코드 살펴보기

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

Promise가 해결되면 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 모드 창 크기를 조금씩 변경할 때마다 별도의 이벤트가 실행되어 각 크기 조절에 비용이 많이 드는 작업을 실행하는 경우 성능 문제를 일으킬 수 있으므로 크기 조절 이벤트에 직접 연결하지 않는 것이 좋습니다. 즉, 크기 조절 작업은 이벤트를 매우 빠르게 계속 실행합니다. 이 문제를 해결하려면 제한 및 디바운싱과 같은 일반적인 기법을 사용하는 것이 좋습니다.

기능 지원

PIP 모드 웹 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 모드 Web API를 사용해 보세요.

데모와 Codelab이 이어서 진행됩니다.

다음 단계

먼저 구현 상태 페이지에서 현재 Chrome 및 기타 브라우저에서 구현된 API 부분을 확인합니다.

가까운 시일 내에 기대할 수 있는 변화는 다음과 같습니다.

브라우저 지원

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

자료

PIP 모드 작업에 도움을 주시고 이 기사에 도움을 주신 Mounir Lamouri와 Jennifer Apacible님께 감사드립니다. 표준화 노력에 참여해 주신 모든 분께 큰 감사를 드립니다.