1. 導入
このセクションは規範的ではありません。
多くのユーザーは、他のコンテンツやサイト、またはデバイス上のアプリケーションを操作している間も、メディアの視聴を続けたいと考えています。このような活動のための一般的なUI手段が「ピクチャ・イン・ピクチャ(PiP)」です。これは、ビデオが他のウィンドウの上に常に表示される独立した小型ウィンドウ内に表示されるものです。このウィンドウはユーザーエージェントが表示されていなくても画面上に残ります。ピクチャ・イン・ピクチャは、デスクトップやモバイルOSにおける一般的なプラットフォーム機能です。
この仕様は HTMLVideoElement
を拡張し、
ウェブサイトが以下のプロパティ群を通じてこの動作を開始・制御できるようにします:
-
ウェブサイトに対して、ピクチャ・イン・ピクチャモードへの移行および離脱を通知します。
-
ウェブサイトが、ビデオ要素上でのユーザー操作によってピクチャ・イン・ピクチャモードを開始できるようにします。
-
ウェブサイトがピクチャ・イン・ピクチャウィンドウのサイズを取得し、サイズ変更時に通知できるようにします。
-
ウェブサイトがピクチャ・イン・ピクチャモードを終了できるようにします。
-
ウェブサイトがピクチャ・イン・ピクチャモードの開始可否を確認できるようにします。
2. 例
2.1. カスタムピクチャ・イン・ピクチャボタンの追加
< 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" ); // ピクチャ・イン・ピクチャがサポートされていない、または無効の場合はボタンを非表示にします。 togglePipButton. hidden= ! document. pictureInPictureEnabled|| video. disablePictureInPicture; togglePipButton. addEventListener( "click" , async () => { // まだピクチャ・イン・ピクチャ要素がなければリクエストし、既にあれば終了します。 try { if ( document. pictureInPictureElement) { await document. exitPictureInPicture(); } else { await video. requestPictureInPicture(); } } catch ( err) { // ビデオのピクチャ・イン・ピクチャモード開始/終了に失敗しました。 } }); </ script >
2.2. ビデオのピクチャ・イン・ピクチャ変更の監視
< video id = "video" src = "https://example.com/file.mp4" ></ video > < script > const video= document. getElementById( "video" ); video. addEventListener( "enterpictureinpicture" , ( event) => { // ビデオがピクチャ・イン・ピクチャモードに入りました。 const pipWindow= event. pictureInPictureWindow; console. log( `ピクチャ・イン・ピクチャウィンドウの幅: ${ pipWindow. width} ` ); console. log( `ピクチャ・イン・ピクチャウィンドウの高さ: ${ pipWindow. height} ` ); }); video. addEventListener( "leavepictureinpicture" , () => { // ビデオがピクチャ・イン・ピクチャモードから離脱しました。 }); </ script >
2.3. ピクチャ・イン・ピクチャウィンドウサイズ変更に応じたビデオサイズの更新
< 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) { // ビデオのピクチャ・イン・ピクチャモード開始に失敗しました。 } }); video. addEventListener( "enterpictureinpicture" , ( event) => { // ビデオがピクチャ・イン・ピクチャモードに入りました。 const pipWindow= event. pictureInPictureWindow; updateVideoSize( pipWindow. width, pipWindow. height); pipWindow. addEventListener( "resize" , onPipWindowResize); }); video. addEventListener( "leavepictureinpicture" , ( event) => { // ビデオがピクチャ・イン・ピクチャモードから離脱しました。 const pipWindow= event. pictureInPictureWindow; pipWindow. removeEventListener( "resize" , onPipWindowResize); }); function onPipWindowResize( event) { // ピクチャ・イン・ピクチャウィンドウがリサイズされました。 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 並列キュー。これは 並列キューであり、 新しい並列キューを開始することによって作成される。
ユーザー エージェントは次を持つ:
-
0 個以上のオリジンからなる アクティブな Picture-in-Picture セッションの開始元 リスト。これは初期状態では空である。
注:ユーザーエージェントが 複数の Picture-in-Picture ウィンドウをサポートする場合、このリストは重複を許す。
あるオリジンは、 アクティブな Picture-in-Picture セッションの開始元内のいずれかのオリジンが、そのオリジンと 同一 origin-domainである場合、アクティブな Picture-in-Picture セッションを持つという。
3.2. Picture-in-Picture
動画フレームはページ内と Picture-in-Picture ウィンドウ内で同時にレンダリングされないことが推奨されるが、同時にレンダリングされる場合は、 同期を保たなければならない。
動画が Picture-in-Picture モードで再生される場合、状態は インラインで再生されているかのように遷移するべきである。つまり、イベントは同じ 時点で発火するべきであり、メソッド呼び出しは同じ挙動を持つべきである、などである。ただし、 ユーザーエージェントは、video 要素が Picture-in-Picture と互換性がないとみなされる 状態に入ったとき、Picture-in-Picture から遷移してもよい。
video に適用されるスタイル(opacity、visibility、transform など)は、 Picture-in-Picture ウィンドウ内では適用してはならない。そのアスペクト比は 動画サイズに基づく。
Picture-in-Picture ウィンドウには最大サイズと 最小サイズがあることも推奨される。たとえば、画面の一方の寸法の 4 分の 1 から 半分の間に制限され得る。
DocumentOrShadowRoot
の
Picture-in-Picture 要素が設定されている場合、
Picture-in-Picture ウィンドウは、たとえ
DocumentOrShadowRoot
の
関連するグローバルオブジェクトの関連付けられた Document の
可視性状態が "hidden" であっても、表示されていなければならない。
ユーザーエージェントは、ユーザーが Picture-in-Picture ウィンドウを手動で閉じる方法を
提供するべきである。
3.3. ピクチャ・イン・ピクチャの終了
Picture-in-Picture 終了アルゴリズムが呼び出されたとき、 ユーザーエージェントは以下の手順を実行しなければならない:
-
pictureInPictureElementがnullである場合、InvalidStateErrorを投げ、 これらの手順を中止する。 -
pictureInPictureElementに関連付けられたPicture-in-Picture ウィンドウで、ウィンドウを閉じるアルゴリズムを実行する。 -
タスクをキューに入れ、video において
leavepictureinpictureという名前のイベントを発火する。 その際、PictureInPictureEventを使用し、そのbubbles属性をtrueに初期化し、そのpictureInPictureWindow属性を、pictureInPictureElementに関連付けられたPicture-in-Picture ウィンドウに初期化する。 -
pictureInPictureElementの設定を解除する。 -
関連設定オブジェクトのオリジンに一致する項目を 1 つ、 アクティブな Picture-in-Picture セッションの開始元から削除する。
Picture-in-Picture 終了アルゴリズムが呼び出されたときに、動画の再生状態が変更されることは推奨されない。 ウェブサイトによって開始された場合、その体験はウェブサイトが制御するべきである。 ただし、ユーザーエージェントは、動画の再生状態を変更する Picture-in-Picture ウィンドウのコントロール(例: 一時停止)を公開してもよい。
文書のアンロード時クリーンアップ手順の 1 つとして、Picture-in-Picture 終了アルゴリズムを実行する。
3.4. ピクチャ・イン・ピクチャの無効化
一部のページは、video 要素に対して Picture-in-Picture モードを無効にしたい場合がある。たとえば、
場合によっては、ユーザーエージェントが
Picture-in-Picture コンテキストメニューを提案することを防ぎたい場合がある。
これらのユースケースをサポートするため、新しい disablePictureInPicture
属性が、video 要素の内容属性のリストに追加される。
disablePictureInPicture
IDL 属性は、同じ名前の内容
属性を反映しなければならない。
disablePictureInPicture
属性が video 要素に存在する場合、
ユーザーエージェントは、その video 要素が Picture-in-Picture
モードで再生されること、またはそれを行うための UI を提示することを防止してもよい。
disablePictureInPicture
属性が video 要素に追加されたとき、
ユーザーエージェントはこれらの手順を実行してもよい:
-
requestPictureInPicture()メソッドによって返された保留中の promise を、InvalidStateErrorで拒否する。 -
video が
pictureInPictureElementである場合、Picture-in-Picture 終了アルゴリズムを実行する。
3.5. フルスクリーンとの相互作用
pictureInPictureElement
のfullscreen flagが設定されたとき、
Picture-in-Picture
終了アルゴリズムを実行することが推奨される。
3.6. ページの可視性との連携
ピクチャインピクチャウィンドウの可視性は、システム可視状態が トラバーサブル・ナビゲーブルで変更されたかどうかを 判定するためにユーザーエージェントが考慮してはなりません。
3.7. ピクチャー・イン・ピクチャーウィンドウは1つのみ
ピクチャー・イン・ピクチャーAPIを持つオペレーティングシステムは、通常ピクチャー・イン・ピクチャーモードを一つのウィンドウに制限します。ピクチャー・イン・ピクチャーモードで一つのウィンドウのみ許可されるかどうかは、実装とプラットフォームに委ねられます。しかし、ピクチャー・イン・ピクチャーウィンドウが一つしか持てないという制限があるため、この仕様は一つのDocument
でピクチャー・イン・ピクチャーウィンドウは一つだけであると仮定します。
すでにウィンドウがピクチャー・イン・ピクチャー状態のときに新たなピクチャー・イン・ピクチャーのリクエストが来た場合にどうするかは実装依存です。現在のピクチャー・イン・ピクチャーウィンドウを閉じる、リクエストを拒否する、あるいは2つのピクチャー・イン・ピクチャーウィンドウを作る、といった挙動になり得ます。いずれにしても、ユーザーエージェントはウェブサイトにピクチャー・イン・ピクチャー状態の変化を通知するために、適切なイベントを発火しなければなりません。
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 が
"picture-in-picture"という名前の ポリシー制御機能を 使用することを許可されていない場合、NotAllowedErrorDOMExceptionで拒否された promiseを返す。 -
this の
readyState属性がHAVE_NOTHINGである場合、InvalidStateErrorDOMExceptionで拒否された promiseを返す。 -
this に 映像トラックがない場合、
InvalidStateErrorDOMExceptionで拒否された promiseを返す。 -
this の
disablePictureInPictureが true である場合、ユーザーエージェントはInvalidStateErrorDOMExceptionで拒否された promiseを返してもよい。 -
doc のPicture-in-Picture 要素が
nullである場合:-
this の関連するグローバルオブジェクトが一過性のアクティベーションを持たない場合、
NotAllowedErrorDOMExceptionで拒否された promiseを返す。 -
ユーザーアクティベーションを消費する。対象は this の関連するグローバルオブジェクトである。
-
-
this が doc のPicture-in-Picture 要素である場合:
-
doc のPicture-in-Picture 要素に関連付けられた Picture-in-Picture ウィンドウで 解決された promiseを返す。
-
-
global を this の関連するグローバルオブジェクトとする。
-
p を、this の関連するレルム内で作成された 新しい promiseとする。
-
p を返し、doc のpicture-in-picture 並列キューに 以下の手順をエンキューする:
-
this が doc のPicture-in-Picture 要素である場合:
-
global を対象として、メディア要素イベントタスクソース上に グローバルタスクをキューに入れ、 this に関連付けられた Picture-in-Picture ウィンドウで p を解決する。
-
これらの手順を中止する。
-
-
Picture-in-Picture ウィンドウを this に関連付けることを試みる。
-
直前の手順が失敗した場合:
-
global を対象として、メディア要素イベントタスク ソース上にグローバルタスクをキューに入れ、
InvalidStateErrorDOMExceptionで p を拒否する。 -
これらの手順を中止する。
-
-
pipWindow を、this に関連付けられたPicture-in-Picture ウィンドウを表す、
PictureInPictureWindowの新しいインスタンスとする。 -
global を対象として、メディア要素イベントタスクソース上に グローバルタスクをキューに入れ、 以下の手順を実行する:
-
pictureInPictureElementがnullでない場合、Picture-in-Picture 終了 アルゴリズムを実行する。 -
doc のPicture-in-Picture 要素を this に設定する。
-
this がfullscreenElementである場合、 fullscreen を終了する。
-
this において、
enterpictureinpictureという名前のイベントを発火する。 その際、PictureInPictureEventを使用し、そのbubbles属性をtrueに初期化し、そのpictureInPictureWindow属性を Picture-in-Picture ウィンドウに初期化する。 -
pipWindow で p を解決する。
-
-
4.2. Documentへの拡張
partial interface Document {readonly attribute boolean ; [pictureInPictureEnabled NewObject ]Promise <undefined >(); };exitPictureInPicture
pictureInPictureEnabled
属性の取得子は、Picture-in-Picture サポートが true であり、かつ
this が
属性名 picture-in-picture によって示される機能を
使用することを許可されている場合は true を返し、
そうでなければ false を返さなければならない。
Picture-in-Picture
サポートは、それを無効にするユーザー設定またはプラットフォーム上の制限がある場合、
false である。そうでなければ true である。
exitPictureInPicture()
メソッドは、呼び出されたとき、
新しい
promise promise を返し、以下の手順を並列に実行しなければならない:
-
直前の手順が例外を投げた場合、その例外で promise を拒否し、 これらの手順を中止する。
-
promise を解決する。
4.3.
DocumentOrShadowRootへの拡張
partial interface mixin DocumentOrShadowRoot {readonly attribute Element ?; };pictureInPictureElement
pictureInPictureElement
属性の取得子は、これらの手順を実行しなければならない:
-
candidate を、Picture-in-Picture 要素をthis に対して リターゲットした結果とする。
-
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
という名前のイベントを発火しなければならない。
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がピクチャ・イン・ピクチャに入ったときに発火されます。 leavepictureinpicture-
HTMLVideoElementがピクチャ・イン・ピクチャモードから離脱したときに発火されます。 resize-
PictureInPictureWindowのサイズが変更されたときに発火されます。
4.6. タスクソース
本仕様でキューイングされるすべてのタスクのタスクソースは、 当該ビデオ要素のメディア要素イベントタスクソースです。
4.7. CSS疑似クラス
:picture-in-picture 疑似クラスは
ピクチャ・イン・ピクチャ要素に一致しなければなりません。これはpictureInPictureElement
とは異なり、
シャドウホストチェーンには適用されません。
5. セキュリティに関する考慮事項
このセクションは規範的ではありません。
なりすまし等による悪用を防ぐため、APIはHTMLVideoElementのみに適用されます。
ピクチャ・イン・ピクチャウィンドウへのユーザー操作は意図的に制限されており、影響はピクチャ・イン・ピクチャウィンドウ自身または再生中のメディアのみに限定されます。
5.1. セキュアコンテキスト
このAPIは[SECURE-CONTEXTS]に限定されていません。なぜなら、ユーザーエージェントが通常すべてのメディアに対してネイティブに提供している機能をウェブアプリケーションへ公開するためであり、ブラウジングコンテキストに関係なく利用可能です。
5.2. Permissions Policy
この仕様は、ポリシー制御機能として
"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の皆様には、この文書へのご貢献に感謝いたします。