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 세션의 시작자 0개 이상의 origin 목록으로, 처음에는 비어 있습니다.
참고: 사용자 에이전트가 여러 Picture-in-Picture 창을 지원하는 경우, 목록에 중복 항목이 허용됩니다.
어떤 origin이 활성 Picture-in-Picture 세션의 시작자 목록에 있는 origin 중 하나와 동일 origin-domain인 경우, 해당 origin은 활성 Picture-in-Picture 세션을 가지고 있다고 합니다.
3.2. 픽처 인 픽처 요청
request Picture-in-Picture 알고리즘이 video로 호출될 때, 사용자 에이전트는 다음 단계를 반드시 진행해야 합니다:
-
Picture-in-Picture 지원이
false인 경우,NotSupportedError를 throw하고 이 단계를 중단합니다. -
문서가 사용 허가됨 상태가 아니면서 정책 제어 기능 이름이
"picture-in-picture"인 경우,SecurityError를 throw하고 이 단계를 중단합니다. -
video의
readyState속성이HAVE_NOTHING인 경우,InvalidStateError를 throw하고 이 단계를 중단합니다. -
video에 비디오 트랙이 없는 경우,
InvalidStateError를 throw하고 이 단계를 중단합니다. -
video의
disablePictureInPicture가 true인 경우, 사용자 에이전트는InvalidStateError를 throw하고 이 단계를 중단할 수 있습니다. -
pictureInPictureElement가null이고 관련 글로벌 객체가 this 에 일시적 활성화가 없는 경우,NotAllowedError를 throw하고 이 단계를 중단합니다. -
video가
pictureInPictureElement인 경우, 이 단계를 중단합니다. -
pictureInPictureElement를 video로 설정합니다. -
Picture-in-Picture 창을
PictureInPictureWindow의 새 인스턴스로 생성하고,pictureInPictureElement와 연관시킵니다. -
관련 설정 객체의 origin을 활성 Picture-in-Picture 세션의 시작자 목록에 추가합니다.
-
태스크를 큐에 추가하여 이벤트를 발생 시키고,
enterpictureinpicture이름을 가진PictureInPictureEvent를 video에 bubbles 속성은true로, pictureInPictureWindow 속성은 Picture-in-Picture 창으로 초기화하여 사용합니다. -
pictureInPictureElement가 fullscreenElement인 경우, 전체화면 종료를 권장합니다.
비디오 프레임이 페이지와 Picture-in-Picture 창에서 동시에 렌더링되지 않는 것이 권장되지만, 만약 렌더링된다면 반드시 동기화되어야 합니다.
비디오가 Picture-in-Picture 모드에서 재생될 때, 상태는 인라인 재생과 동일하게 전환되어야 합니다. 즉, 이벤트가 동시에 발생하고, 메서드 호출은 동일하게 동작해야 합니다. 하지만, 사용자 에이전트는 비디오 요소가 Picture-in-Picture와 호환되지 않는 상태로 진입할 경우 Picture-in-Picture에서 나갈 수 있습니다.
video에 적용된 스타일(opacity, visibility, transform 등)은 Picture-in-Picture 창에는 적용되지 않아야 합니다. 창의 종횡비는 비디오 크기를 기반으로 합니다.
Picture-in-Picture 창은 최대/최소 크기가 있는 것이 권장됩니다. 예를 들어 화면 한 변의 1/4에서 1/2 사이로 제한할 수 있습니다.
3.3. 픽처 인 픽처 종료
exit Picture-in-Picture 알고리즘이 호출되면, 사용자 에이전트는 다음 단계를 반드시 진행해야 합니다:
-
pictureInPictureElement가null이면InvalidStateError를 throw하고 이 단계를 중단합니다. -
close window 알고리즘을 Picture-in-Picture 창에 대해 (pictureInPictureElement와 연관된) 실행합니다.
-
태스크를 큐에 추가하여 이벤트를 발생 시키고,
leavepictureinpicture이름을 가진PictureInPictureEvent를 video에 bubbles 속성은true로, pictureInPictureWindow 속성은 Picture-in-Picture 창 (pictureInPictureElement와 연관된)으로 초기화하여 사용합니다. -
pictureInPictureElement를 unset합니다. -
item 중 관련 설정 객체의 origin과 일치하는 항목 하나를 활성 Picture-in-Picture 세션의 시작자 목록에서 제거합니다.
exit Picture-in-Picture 알고리즘이 호출될 때 비디오 재생 상태가 변경되는 것은 권장되지 않습니다. 웹사이트가 직접 시작한 경우에는 웹사이트가 경험을 제어해야 합니다. 하지만 사용자 에이전트는 비디오 재생 상태(예: 일시정지)를 변경할 수 있는 Picture-in-Picture 창 컨트롤을 제공할 수 있습니다.
문서 언로드 정리 단계 중 하나로, exit Picture-in-Picture 알고리즘을 실행합니다.
3.4. 픽처 인 픽처 비활성화
일부 페이지에서는 비디오 요소에 대해 Picture-in-Picture 모드를 비활성화하고 싶을 수 있습니다. 예를 들어, 특정 상황에서 사용자 에이전트가 Picture-in-Picture
컨텍스트 메뉴를 제안하지 않도록 하고 싶은 경우가 있습니다.
이러한 사용 사례를 지원하기 위해 비디오 요소의 콘텐츠 속성 목록에 disablePictureInPicture
속성이 추가됩니다.
disablePictureInPicture
IDL 속성은 반드시 동일한 이름의 콘텐츠 속성을 반영해야 합니다.
비디오 요소에 disablePictureInPicture
속성이 있으면,
사용자 에이전트는 비디오 요소가 Picture-in-Picture 모드로 재생되거나 이를 위한 UI를 표시하는 것을 막을 수 있습니다.
disablePictureInPicture
속성이 video 요소에 추가될 때,
사용자 에이전트는 다음 단계를 진행할 수 있습니다:
-
requestPictureInPicture()메서드가 반환한 대기 중인 promise를InvalidStateError로 거절합니다. -
video가
pictureInPictureElement인 경우, exit Picture-in-Picture 알고리즘을 실행합니다.
3.5. 전체화면과의 상호작용
pictureInPictureElement
fullscreen flag가 설정되면
exit Picture-in-Picture 알고리즘을 실행하는 것이 권장됩니다.
3.6. 미디어 세션과의 상호작용
이 API는 Picture-in-Picture 창에서 사용할 수 있는 컨트롤을 사용자 정의하기 위해 [MediaSession] API와 함께 사용되어야 합니다.
3.7. 페이지 가시성과의 상호작용
pictureInPictureElement
가 설정된 경우, Document가 포커스를 가지지 않거나 숨겨져 있어도 Picture-in-Picture 창은 반드시 보여야 합니다.
사용자 에이전트는 사용자가 Picture-in-Picture 창을 수동으로 닫을 수 있는 방법을 제공해야 합니다.
Picture-in-Picture 창의 가시성은 사용자 에이전트가 system visibility state가 traversable navigable에서 변경되었는지 판단할 때 고려되어서는 안 됩니다.
3.8. 하나의 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()
메서드는 호출되면 새로운 promise promise를 반환해야 하며, 다음 단계를 병렬로 실행해야 합니다:
-
video는 메서드가 호출된 비디오 요소입니다.
-
request Picture-in-Picture 알고리즘을 video로 실행합니다.
-
이전 단계에서 예외가 발생했다면, promise를 해당 예외로 거절하고 이후 단계를 중단합니다.
-
promise를 pictureInPictureElement와 연관된
Picture-in-Picture 창으로 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를 반환해야 하며, 다음 단계를 병렬로 실행해야 합니다:
-
exit Picture-in-Picture 알고리즘을 실행합니다.
-
이전 단계에서 예외가 발생했다면, promise를 해당 예외로 거절하고 이후 단계를 중단합니다.
-
해결 promise.
4.3.
DocumentOrShadowRoot 확장
partial interface mixin DocumentOrShadowRoot {readonly attribute Element ?; };pictureInPictureElement
pictureInPictureElement
속성 getter는 다음 단계를 실행해야 합니다:
-
this가 shadow root이고, 그 host 가 connected 상태가 아니면
null을 반환하고 이후 단계를 중단합니다. -
candidate를 Picture-in-Picture 요소를 this에 대해 리타겟팅(retargeting)한 결과로 합니다.
-
null을 반환합니다.
4.4.
인터페이스 PictureInPictureWindow
[Exposed =Window ]interface :PictureInPictureWindow EventTarget {readonly attribute long ;width readonly attribute long ;height attribute EventHandler ; };onresize
PictureInPictureWindow
인스턴스는 Picture-in-Picture
창을 HTMLVideoElement와
연관하여 나타냅니다.
인스턴스가 생성될 때, PictureInPictureWindow
의 state는 opened로 설정됩니다.
close window
알고리즘이 PictureInPictureWindow
인스턴스에 대해 호출되면, 해당 state는 closed로 설정됩니다.
width
속성은 state가 opened일 때 pictureInPictureElement와
연관된 Picture-in-Picture 창의 CSS 픽셀 단위 너비를
반환해야 합니다. 그렇지 않으면 0을 반환해야 합니다.
height
속성은 state가 opened일 때 pictureInPictureElement와
연관된 Picture-in-Picture 창의 CSS 픽셀 단위 높이를
반환해야 합니다. 그렇지 않으면 0을 반환해야 합니다.
pictureInPictureElement와
연관된 Picture-in-Picture 창의 크기가 변경되면, 사용자 에이전트는 태스크를 큐에 추가하여 resize
이벤트를 pictureInPictureElement에서
발생시켜야 합니다.
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. 권한 정책
이 명세는 "picture-in-picture"라는 이름의 정책 제어 기능을 정의하며,
request 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 님께 감사드립니다.