1. 소개
이 섹션은 규범적이지 않습니다.
많은 사용자는 장치에서 다른 콘텐츠, 사이트 또는 애플리케이션과 상호작용하면서 미디어를 계속 소비하고 싶어합니다. 이러한 활동을 위한 일반적인 UI 편의 기능은 Picture-in-Picture(PiP)로, 비디오가 항상 다른 창 위에 표시되는 별도의 작은 창에 포함됩니다. 이 창은 사용자 에이전트가 표시되지 않아도 계속 보입니다. Picture-in-Picture는 데스크톱 및 모바일 운영체제에서 흔히 볼 수 있는 플랫폼 수준 기능입니다.
이 명세는 HTMLVideoElement
를 확장하여 웹사이트가 다음과 같은 속성 집합을 통해 이 동작을 시작하고 제어할 수 있도록 합니다:
-
웹사이트에 Picture-in-Picture 모드로 진입하거나 나갈 때 알림을 제공합니다.
-
웹사이트에서 비디오 요소에 대한 사용자 제스처를 통해 Picture-in-Picture 모드를 트리거할 수 있도록 허용합니다.
-
웹사이트가 Picture-in-Picture 창의 크기를 알 수 있고, 변경 시 알림을 받을 수 있도록 합니다.
-
웹사이트에서 Picture-in-Picture 모드에서 나갈 수 있게 합니다.
-
웹사이트에서 Picture-in-Picture 모드를 트리거할 수 있는지 확인할 수 있게 합니다.
2. 예시
2.1. 커스텀 Picture-in-Picture 버튼 추가
< video id = "video" src = "https://example.com/file.mp4" ></ video > < button id = "togglePipButton" ></ button > < script > const video= document. getElementById( "video" ); const togglePipButton= document. getElementById( "togglePipButton" ); // Picture-in-Picture가 지원되지 않거나 비활성화된 경우 버튼을 숨깁니다. togglePipButton. hidden= ! document. pictureInPictureEnabled|| video. disablePictureInPicture; togglePipButton. addEventListener( "click" , async () => { // 아직 Picture-in-Picture에 있는 요소가 없으면 비디오에 대해 요청합니다. // 그렇지 않으면 나갑니다. try { if ( document. pictureInPictureElement) { await document. exitPictureInPicture(); } else { await video. requestPictureInPicture(); } } catch ( err) { // 비디오가 Picture-in-Picture 모드로 진입/종료에 실패했습니다. } }); </ script >
2.2. 비디오 Picture-in-Picture 변경 모니터링
< video id = "video" src = "https://example.com/file.mp4" ></ video > < script > const video= document. getElementById( "video" ); video. addEventListener( "enterpictureinpicture" , ( event) => { // 비디오가 Picture-in-Picture 모드로 진입했습니다. const pipWindow= event. pictureInPictureWindow; console. log( `Picture-in-Picture 창 너비: ${ pipWindow. width} ` ); console. log( `Picture-in-Picture 창 높이: ${ pipWindow. height} ` ); }); video. addEventListener( "leavepictureinpicture" , () => { // 비디오가 Picture-in-Picture 모드를 종료했습니다. }); </ script >
2.3. Picture-in-Picture 창 크기 변경에 따른 비디오 크기 업데이트
< video id = "video" src = "https://example.com/file.mp4" ></ video > < button id = "pipButton" ></ button > < script > const video= document. getElementById( "video" ); const pipButton= document. getElementById( "pipButton" ); pipButton. addEventListener( "click" , async () => { try { await video. requestPictureInPicture(); } catch ( error) { // 비디오가 Picture-in-Picture 모드로 진입하지 못했습니다. } }); video. addEventListener( "enterpictureinpicture" , ( event) => { // 비디오가 Picture-in-Picture 모드로 진입했습니다. const pipWindow= event. pictureInPictureWindow; updateVideoSize( pipWindow. width, pipWindow. height); pipWindow. addEventListener( "resize" , onPipWindowResize); }); video. addEventListener( "leavepictureinpicture" , ( event) => { // 비디오가 Picture-in-Picture 모드를 종료했습니다. const pipWindow= event. pictureInPictureWindow; pipWindow. removeEventListener( "resize" , onPipWindowResize); }); function onPipWindowResize( event) { // Picture-in-Picture 창이 크기 조정되었습니다. const { width, height} = event. target; updateVideoSize( width, height); } function updateVideoSize( width, height) { // TODO: pip 창의 너비와 높이에 따라 비디오 크기를 업데이트합니다. } </ script >
3. 개념
3.1. 정의
Picture-in-Picture
창은
video
요소를 표시하는 창이다.
DocumentOrShadowRoot는
다음을 가진다:
-
Picture-in-Picture 요소. 이는
Element또는null이며, 초깃값은null이다.
traversable navigable은 다음을 가진다:
-
picture-in-picture parallel queue. 이는 parallel queue이며, 새 parallel queue를 시작하여 생성된다.
사용자 에이전트는 다음을 가진다:
-
0개 이상의 origin으로 이루어진 활성 Picture-in-Picture 세션의 개시자 목록. 초깃값은 비어 있다.
참고: 사용자 에이전트가 여러 Picture-in-Picture 창을 지원하는 경우, 이 목록은 중복을 허용한다.
어떤 origin이 활성 Picture-in-Picture 세션을 가진다고 함은, 활성 Picture-in-Picture 세션의 개시자에 있는 origin 중 하나라도 그 origin과 same origin-domain인 경우를 말한다.
3.2. Picture-in-Picture
비디오 프레임은 페이지와 Picture-in-Picture 창에서 동시에 렌더링되지 않는 것이 RECOMMENDED되지만, 동시에 렌더링되는 경우에는 반드시 동기화 상태를 유지해야 한다.
비디오가 Picture-in-Picture 모드로 재생될 때, 상태는 인라인으로 재생되는 것처럼 전이되는 것이 SHOULD이다. 이는 이벤트가 같은 시점에 발생하고, 메서드 호출이 같은 동작을 가져야 한다는 등의 의미이다. 그러나 사용자 에이전트는 video 요소가 Picture-in-Picture와 호환되지 않는 것으로 간주되는 상태에 들어가면 Picture-in-Picture에서 벗어나도록 전이할 MAY 수 있다.
video에 적용된 스타일(예: opacity, visibility, transform 등)은 Picture-in-Picture 창에 적용되어서는 MUST NOT 된다. 그 종횡비는 비디오 크기에 기반한다.
Picture-in-Picture 창에는 최대 및 최소 크기가 있는 것도 RECOMMENDED된다. 예를 들어, 화면 한 차원의 1/4에서 1/2 사이로 제한될 수 있다.
DocumentOrShadowRoot의
Picture-in-Picture 요소가 설정되어 있으면,
Picture-in-Picture 창은 반드시 보여야 하며, 이는
DocumentOrShadowRoot의
관련 전역 객체의 연관된 Document의
visibility state가 "hidden"인 경우에도 마찬가지이다.
사용자 에이전트는 사용자가 Picture-in-Picture 창을 수동으로 닫을 방법을 제공하는 것이 SHOULD이다.
3.3. 픽처 인 픽처 종료
Picture-in-Picture 종료 알고리즘이 호출되면, 사용자 에이전트는 다음 단계를 반드시 실행해야 한다:
-
pictureInPictureElement가null이면,InvalidStateError를 throw하고 이 단계를 중단한다. -
pictureInPictureElement와 연관된 Picture-in-Picture 창으로 창 닫기 알고리즘을 실행한다. -
leavepictureinpicture라는 이름의 이벤트를 발생시키도록 task를 큐에 추가한다. 이 이벤트는PictureInPictureEvent를 사용하여 video에서 발생시키며, 그bubbles속성은true로 초기화하고, 그pictureInPictureWindow속성은pictureInPictureElement와 연관된 Picture-in-Picture 창으로 초기화한다. -
pictureInPictureElement를 unset한다. -
relevant settings object의 origin과 일치하는 item 하나를 활성 Picture-in-Picture 세션의 개시자에서 제거한다.
Picture-in-Picture 종료 알고리즘이 호출될 때 비디오 재생 상태가 변경되는 것은 NOT RECOMMENDED이다. 웹사이트에서 시작한 경우, 그 경험은 웹사이트가 제어해야 한다. 그러나 사용자 에이전트는 비디오 재생 상태를 변경하는 Picture-in-Picture 창 컨트롤(예: 일시정지)을 노출할 MAY 수 있다.
unloading document cleanup steps 중 하나로, Picture-in-Picture 종료 알고리즘을 실행한다.
3.4. 픽처 인 픽처 비활성화
일부 페이지는 video 요소에 대해 Picture-in-Picture 모드를 비활성화하고자 할 수 있다. 예를 들어,
어떤 경우에는 사용자 에이전트가 Picture-in-Picture 컨텍스트 메뉴를 제안하지 못하게
하려 할 수 있다.
이러한 사용 사례를 지원하기 위해, 새 disablePictureInPicture
속성이 video 요소의 콘텐츠 속성 목록에 추가된다.
disablePictureInPicture
IDL 속성은 같은 이름의 콘텐츠 속성을 반드시 반영해야 한다.
disablePictureInPicture
속성이 video 요소에 존재하는 경우,
사용자 에이전트는 video 요소가 Picture-in-Picture
모드로 재생되는 것을 막거나, 이를 위한 UI를 표시할 MAY 수 있다.
disablePictureInPicture
속성이 video 요소에 추가되면,
사용자 에이전트는 다음 단계를 실행할 MAY 수 있다:
-
requestPictureInPicture()메서드가 반환한 보류 중인 모든 promise를InvalidStateError로 reject한다. -
video가
pictureInPictureElement이면, Picture-in-Picture 종료 알고리즘을 실행한다.
3.5. 전체화면과의 상호작용
pictureInPictureElement의
fullscreen flag가 설정된 경우,
Picture-in-Picture 종료 알고리즘을 실행하는 것이 RECOMMENDED된다.
3.6. 페이지 가시성과의 상호작용
Picture-in-Picture 창의 가시성은 사용자 에이전트가 시스템 가시성 상태가 트래버서블 내비게이션(traversable navigable)에서 변경되었는지 판단할 때 고려되어서는 안 됩니다.
3.7. 하나의 Picture-in-Picture 창
Picture-in-Picture API가 있는 운영체제는 일반적으로 Picture-in-Picture 모드가 한 개의 창만 허용되도록 제한합니다. Picture-in-Picture 모드에서 오직
하나의 창만 허용할지는 구현 및 플랫폼에 따라 결정됩니다. 그러나 Picture-in-Picture 창의 개수 제한 때문에, 이 명세서는 특정 Document
가 오직 하나의 Picture-in-Picture 창만 가질 수 있다고 가정합니다.
이미 Picture-in-Picture 창이 열려 있을 때 Picture-in-Picture 요청이 들어오면 어떻게 처리할지는 구현에 맡겨집니다. 현재 Picture-in-Picture 창을 닫을 수도 있고, Picture-in-Picture 요청을 거부할 수도 있으며, 심지어 두 개의 Picture-in-Picture 창이 생성될 수도 있습니다. 어떤 상황이든, User Agent는 웹사이트에 Picture-in-Picture 상태 변경을 알리기 위한 적절한 이벤트를 반드시 발생시켜야 합니다.
4. API
4.1.
HTMLVideoElement 확장
partial interface HTMLVideoElement { [NewObject ]Promise <PictureInPictureWindow >();requestPictureInPicture attribute EventHandler ;onenterpictureinpicture attribute EventHandler ; [onleavepictureinpicture CEReactions ]attribute boolean ; };disablePictureInPicture
requestPictureInPicture()
메서드 단계 Picture-in-Picture 요청:
-
Picture-in-Picture 지원이
false이면,NotSupportedErrorDOMException으로 거부된 promise를 반환한다. -
doc을 this의 node document로 둔다.
-
doc이
"picture-in-picture"라는 이름의 policy-controlled feature를 사용하도록 허용되지 않았으면,NotAllowedErrorDOMException으로 거부된 promise를 반환한다. -
this의
readyState속성이HAVE_NOTHING이면,InvalidStateErrorDOMException으로 거부된 promise를 반환한다. -
this에 비디오 트랙이 없으면,
InvalidStateErrorDOMException으로 거부된 promise를 반환한다. -
this의
disablePictureInPicture가 true이면, 사용자 에이전트는InvalidStateErrorDOMException으로 거부된 promise를 반환할 수 있다. -
doc의 Picture-in-Picture 요소가
null이면:-
this의 관련 전역 객체에 transient activation이 없으면,
NotAllowedErrorDOMException으로 거부된 promise를 반환한다. -
this의 관련 전역 객체가 주어졌을 때 사용자 활성화를 소비한다.
-
-
this가 doc의 Picture-in-Picture 요소이면:
-
doc의 Picture-in-Picture 요소와 연관된 Picture-in-Picture 창으로 해결된 promise를 반환한다.
-
-
p를 반환하고, doc의 picture-in-picture parallel queue에 다음 단계를 enqueue한다:
-
this가 doc의 Picture-in-Picture 요소이면:
-
global이 주어졌을 때 media element event task source에서 전역 task를 큐에 추가하여, this와 연관된 Picture-in-Picture 창으로 p를 resolve한다.
-
이 단계를 중단한다.
-
-
this와 Picture-in-Picture 창을 연결하려고 시도한다.
-
이전 단계가 실패했으면:
-
global이 주어졌을 때 media element event task source에서 전역 task를 큐에 추가하여,
InvalidStateErrorDOMException으로 p를 reject한다. -
이 단계를 중단한다.
-
-
pipWindow를 this의 연관된 Picture-in-Picture 창을 나타내는
PictureInPictureWindow의 새 인스턴스로 둔다. -
global이 주어졌을 때 media element event task source에서 전역 task를 큐에 추가하여, 다음 단계를 수행한다:
-
pictureInPictureElement가null이 아니면, Picture-in-Picture 종료 알고리즘을 실행한다. -
doc의 Picture-in-Picture 요소를 this로 설정한다.
-
relevant settings object의 origin을 활성 Picture-in-Picture 세션의 개시자에 append한다.
-
this가 fullscreenElement이면, fullscreen을 종료한다.
-
enterpictureinpicture라는 이름의 이벤트를 발생시킨다. 이 이벤트는PictureInPictureEvent를 사용하여 this에서 발생시키며, 그bubbles속성은true로 초기화하고, 그pictureInPictureWindow속성은 Picture-in-Picture 창으로 초기화한다. -
pipWindow로 p를 resolve한다.
-
-
4.2. Document 확장
partial interface Document {readonly attribute boolean ; [pictureInPictureEnabled NewObject ]Promise <undefined >(); };exitPictureInPicture
pictureInPictureEnabled
속성의 getter는
Picture-in-Picture 지원이 true이고 this가
picture-in-picture 속성 이름으로 표시되는 기능을
사용하도록 허용된 경우 true를 반환해야 하며,
그렇지 않으면 false를 반환해야 한다.
Picture-in-Picture
지원은 이를 비활성화하는 사용자 환경설정이나 플랫폼 제한이 있으면
false이다. 그렇지 않으면 true이다.
exitPictureInPicture()
메서드는 호출되면 반드시
새
promise promise를 반환하고 다음 단계를 병렬로
실행해야 한다:
-
Picture-in-Picture 종료 알고리즘을 실행한다.
-
이전 단계에서 예외가 throw되었으면, 그 예외로 promise를 reject하고 이 단계를 중단한다.
-
promise를 resolve한다.
4.3.
DocumentOrShadowRoot 확장
partial interface mixin DocumentOrShadowRoot {readonly attribute Element ?; };pictureInPictureElement
pictureInPictureElement
속성의 getter는 다음 단계를 실행해야 한다:
-
this가 shadow root이고 그 host가 connected되어 있지 않으면,
null을 반환하고 이 단계를 중단한다. -
candidate를 retargeting의 결과로 둔다. 이는 Picture-in-Picture 요소를 this에 대해 retargeting한 결과이다.
-
candidate와 this가 같은 tree 안에 있으면, candidate를 반환하고 이 단계를 중단한다.
-
null을 반환한다.
4.4.
인터페이스 PictureInPictureWindow
[Exposed =Window ]interface :PictureInPictureWindow EventTarget {readonly attribute long ;width readonly attribute long ;height attribute EventHandler ; };onresize
PictureInPictureWindow
인스턴스는 HTMLVideoElement와
연관된 Picture-in-Picture
창을 나타낸다.
인스턴스화될 때,
PictureInPictureWindow
인스턴스는 그 state가 opened로 설정된다.
PictureInPictureWindow
인스턴스로 창 닫기
알고리즘이 호출되면, 그 state는 closed로 설정된다.
width
속성은 state가 opened이면, pictureInPictureElement와
연관된 Picture-in-Picture 창의 너비를
CSS
픽셀 단위로
반환해야 한다. 그렇지 않으면 0을 반환해야 한다.
height
속성은 state가 opened이면, pictureInPictureElement와
연관된 Picture-in-Picture 창의 높이를
CSS
픽셀 단위로
반환해야 한다. 그렇지 않으면 0을 반환해야 한다.
Picture-in-Picture 창 pipWindow의 크기가 변경되면,
사용자 에이전트는 반드시
pipWindow에서 resize라는
이름의 이벤트를
발생시키도록 task를 큐에 추가해야 한다.
4.5. 이벤트 타입
[Exposed =Window ]interface :PictureInPictureEvent Event {(constructor DOMString ,type PictureInPictureEventInit ); [eventInitDict SameObject ]readonly attribute PictureInPictureWindow ; };pictureInPictureWindow dictionary :PictureInPictureEventInit EventInit {required PictureInPictureWindow ; };pictureInPictureWindow
enterpictureinpicture-
HTMLVideoElement가 Picture-in-Picture로 진입할 때 발생합니다. leavepictureinpicture-
HTMLVideoElement가 Picture-in-Picture 모드를 종료할 때 발생합니다. resize-
PictureInPictureWindow의 크기가 변경될 때 발생합니다.
4.6. 태스크 소스
이 명세에서 큐에 추가되는 모든 태스크의 태스크 소스는 해당 비디오 요소의 미디어 요소 이벤트 태스크 소스입니다.
4.7. CSS 의사 클래스
:picture-in-picture 의사 클래스는 반드시
Picture-in-Picture
요소와 매치되어야 합니다. 이는 pictureInPictureElement
와는 다르며,
shadow host chain에는 적용되지 않습니다.
5. 보안 고려사항
이 섹션은 규범적이지 않습니다.
스푸핑을 통한 잠재적 남용을 제한하기 위해, 이 API는
HTMLVideoElement
에만 적용됩니다.
Picture-in-Picture 창과의 사용자 상호작용은 의도적으로 제한되어 있으며, 오직 Picture-in-Picture 창 자체 또는 재생 중인 미디어에만 영향을 미칩니다.
5.1. 보안 컨텍스트
이 API는 [SECURE-CONTEXTS]에 한정되지 않습니다. 그 이유는 사용자 에이전트가 일반적으로 미디어에 대해 브라우징 컨텍스트와 관계없이 모든 웹 애플리케이션에 이 기능을 네이티브로 제공하기 때문입니다.
5.2. 권한 정책
이 명세는 policy-controlled feature인
"picture-in-picture"를 정의한다. 이는 Picture-in-Picture
요청이
SecurityError를
반환하는지 여부와
pictureInPictureEnabled가
true인지 false인지를 제어한다.
이 기능의 기본 허용 목록은 *입니다.
6. 감사의 글
이 문서에 기여해 주신 Jennifer Apacible, Zouhir Chahoud, Marcos Cáceres, Philip Jägenstedt, Jeremy Jones, Chris Needham, Jer Noble, Justin Uberti, Yoav Weiss, Eckhart Wörner 님께 감사드립니다.