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 모드를 종료할 수 있습니다.
새로운 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 모드 창 크기를 조금씩 변경할 때마다 별도의 이벤트가 실행되어 각 크기 조절에 비용이 많이 드는 작업을 실행하는 경우 성능 문제를 일으킬 수 있으므로 크기 조절 이벤트에 직접 연결하지 않는 것이 좋습니다. 즉, 크기 조절 작업은 이벤트를 매우 빠르게 계속 실행합니다. 이 문제를 해결하려면 제한 및 디바운싱과 같은 일반적인 기법을 사용하는 것이 좋습니다.
기능 지원
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에서 오디오 재생목록 창을 만들 수 있습니다. 공식 오디오 재생목록 샘플을 확인하세요.
샘플, 데모, Codelab
공식 PIP 모드 샘플을 확인하여 PIP 모드 Web API를 사용해 보세요.
데모와 Codelab이 이어서 진행됩니다.
다음 단계
먼저 구현 상태 페이지에서 현재 Chrome 및 기타 브라우저에서 구현된 API 부분을 확인합니다.
가까운 시일 내에 기대할 수 있는 변화는 다음과 같습니다.
- 웹 개발자는 맞춤 PIP 모드 컨트롤을 추가할 수 있게 됩니다.
- 플로팅 창에 임의의
HTMLElement
객체를 표시하기 위한 새 Web API가 제공됩니다.
브라우저 지원
PIP 모드 웹 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님께 감사드립니다. 표준화 노력에 참여해 주신 모든 분께 큰 감사를 드립니다.