PIP 모드를 사용하면 사용자가 플로팅 창(항상 다른 창 위에 표시)에서 동영상을 시청할 수 있으므로 다른 사이트 또는 애플리케이션과 상호작용하는 동안 시청 중인 동영상을 계속 볼 수 있습니다.
Picture-in-Picture Web 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를 종료할 수 있습니다.
새로운 enterpictureinpicture
및 leavepictureinpicture
이벤트 핸들러를 사용하면 사용자에게 맞게 환경을 조정할 수 있습니다. 동영상 카탈로그를 둘러보는 것부터 라이브 스트림 채팅을 표시하는 것까지 다양합니다.
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 창의 재생/일시중지, 이전 트랙, 다음 트랙 버튼을 지원합니다.
기본적으로 동영상에서 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 창을 표시할 수 있습니다. 아래와 같이 PIP 모드로 전환하기 위해 동영상 요소를 DOM에 연결할 필요는 없습니다.
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에서 오디오 재생목록 창을 만들 수 있습니다. 공식 오디오 재생목록 샘플을 확인하세요.
샘플, 데모, Codelab
공식 PIP 모드 샘플을 확인하여 PIP 모드 웹 API를 사용해 보세요.
데모와 Codelab도 제공될 예정입니다.
다음 단계
먼저 구현 상태 페이지를 확인하여 현재 Chrome 및 다른 브라우저에 구현된 API의 부분을 알아봅니다.
가까운 시일 내에 다음과 같은 기능이 제공될 예정입니다.
- 웹 개발자는 맞춤 PIP 컨트롤을 추가할 수 있습니다.
- 플로팅 창에 임의의
HTMLElement
객체를 표시하는 새로운 Web API가 제공됩니다.
브라우저 지원
PiP Web API는 Chrome, Edge, Opera, Safari에서 지원됩니다. 자세한 내용은 MDN을 참고하세요.
리소스
- Chrome 기능 상태: https://www.chromestatus.com/feature/5729206566649856
- Chrome 구현 버그: https://crbug.com/?q=component:Blink>Media>PictureInPicture
- PIP 웹 API 사양: https://wicg.github.io/picture-in-picture
- 사양 문제: https://github.com/WICG/picture-in-picture/issues
- 샘플: https://googlechrome.github.io/samples/picture-in-picture/
- 비공식 PIP 모드 폴리필: https://github.com/gbentaieb/pip-polyfill/
PiP에 대한 작업과 이 도움말 작성에 도움을 주신 Mounir Lamouri와 Jennifer Apacible님께 감사드립니다. 표준화 작업에 참여한 모든 분께도 감사드립니다.