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. 内部スロット定義
ユーザーエージェントは以下を持ちます:
-
アクティブなピクチャ・イン・ピクチャセッションの開始元のリスト(ゼロ個以上のオリジンを含み、初期値は空です)。
注: ユーザーエージェントが複数のピクチャ・イン・ピクチャウィンドウをサポートする場合、このリストには重複が許されます。
あるオリジンがアクティブなピクチャ・イン・ピクチャセッションを持つとは、アクティブなピクチャ・イン・ピクチャセッションの開始元のいずれかが、そのオリジンと同一オリジンドメインである場合を指します。
3.2. ピクチャ・イン・ピクチャのリクエスト
request Picture-in-Picture algorithmが videoで呼び出された場合、 ユーザーエージェントは以下の手順を実行しなければなりません:
-
ピクチャ・イン・ピクチャサポートが
falseなら、NotSupportedErrorを投げて、これらの手順を中断します。 -
ドキュメントがallowed to useの状態でなく、policy-controlled feature
"picture-in-picture"を利用できない場合、SecurityErrorを投げてこれらの手順を中断します。 -
videoの
readyState属性がHAVE_NOTHINGなら、InvalidStateErrorを投げて、これらの手順を中断します。 -
videoにビデオトラックが存在しない場合、
InvalidStateErrorを投げてこれらの手順を中断します。 -
videoの
disablePictureInPictureがtrueの場合、 ユーザーエージェントはInvalidStateErrorを投げてこれらの手順を中断してもよいです。 -
pictureInPictureElementがnullかつ、relevant global object がthisに対し transient activationを持たない場合、NotAllowedErrorを投げてこれらの手順を中断します。 -
videoが
pictureInPictureElementの場合、 これらの手順を中断します。 -
pictureInPictureElementをvideoに設定します。 -
ピクチャ・イン・ピクチャウィンドウを
PictureInPictureWindowの新しいインスタンス(pictureInPictureElementに関連付ける)として作成します。 -
relevant settings objectのoriginを アクティブなピクチャ・イン・ピクチャセッションの開始元リストに追加します。
-
タスクをキューイングしてイベントを発火します。 イベント名は
enterpictureinpictureで、PictureInPictureEventを使用し、 videoでbubbles属性はtrue、pictureInPictureWindow属性は ピクチャ・イン・ピクチャウィンドウで初期化します。 -
pictureInPictureElementがfullscreenElementの場合、 フルスクリーンから完全に退出することが推奨されます。
ビデオフレームはページとピクチャ・イン・ピクチャウィンドウで同時描画しないことが推奨されますが、もし同時描画されるなら同期を保たなければなりません。
ビデオがピクチャ・イン・ピクチャモードで再生される際、状態遷移はインライン再生と同じになるべきです。つまり、イベントは同時に発火し、メソッド呼び出しの振る舞いも同じになるべきです。ただし、ユーザーエージェントはビデオ要素がピクチャ・イン・ピクチャと非互換な状態になった場合、ピクチャ・イン・ピクチャを終了させても構いません。
videoに適用されているスタイル(opacity, visibility, transform等)はピクチャ・イン・ピクチャウィンドウには適用されてはなりません。ウィンドウのアスペクト比は動画サイズに基づきます。
また、ピクチャ・イン・ピクチャウィンドウには最大・最小サイズを設けることが推奨されます。例えば画面の一辺の1/4から1/2の範囲に制限することができます。
3.3. ピクチャ・イン・ピクチャの終了
exit Picture-in-Picture algorithmが呼び出された場合、 ユーザーエージェントは以下の手順を実行しなければなりません:
-
pictureInPictureElementがnullなら、InvalidStateErrorを投げてこれらの手順を中断します。 -
close window algorithmを、
pictureInPictureElementに関連付けられたピクチャ・イン・ピクチャウィンドウで実行します。 -
タスクをキューイングしてイベントを発火します。 イベント名は
leavepictureinpictureで、PictureInPictureEventを使用し、 videoでbubbles属性はtrue、pictureInPictureWindow属性は ピクチャ・イン・ピクチャウィンドウ(pictureInPictureElementに関連付けられたもの)で初期化します。 -
pictureInPictureElementを解除します。 -
itemのうちrelevant settings objectのoriginと一致するものを アクティブなピクチャ・イン・ピクチャセッションの開始元リストから1件削除します。
exit Picture-in-Picture algorithmが呼び出されたとき、ビデオの再生状態を変更することは推奨されません。ウェブサイトが開始した場合はウェブサイトが操作を制御するべきです。ただし、ユーザーエージェントはピクチャ・イン・ピクチャウィンドウのコントロール(例:一時停止)で再生状態を変更することができます。
ドキュメントのアンロード時クリーンアップ手順の一部として、exit Picture-in-Picture algorithmを実行します。
3.4. ピクチャ・イン・ピクチャの無効化
一部のページでは、ビデオ要素のピクチャ・イン・ピクチャモードを無効化したい場合があります。たとえば、ユーザーエージェントがピクチャ・イン・ピクチャのコンテキストメニューを提示することを防ぎたい場合などです。
これらのユースケースをサポートするために、ビデオ要素のコンテンツ属性リストに新しいdisablePictureInPicture
属性が追加されました。
disablePictureInPicture
IDL属性は同名のコンテンツ属性を反映しなければなりません。
ビデオ要素にdisablePictureInPicture
属性が存在する場合、
ユーザーエージェントはそのビデオ要素をピクチャ・イン・ピクチャモードで再生できなくしたり、関連UIを表示しないようにしてもよいです。
disablePictureInPicture
属性がvideo要素に追加されたとき、
ユーザーエージェントは以下の手順を実行してもよいです:
-
requestPictureInPicture()メソッドから返された未解決のpromiseをInvalidStateErrorで拒否します。 -
videoが
pictureInPictureElementの場合、 exit Picture-in-Picture algorithmを実行します。
3.5. フルスクリーンとの相互作用
pictureInPictureElement
のfullscreen flagが設定された場合は、
ピクチャ・イン・ピクチャ終了アルゴリズムを実行することが推奨されます。
3.6. メディアセッションとの連携
このAPIは、ピクチャー・イン・ピクチャーウィンドウ上の利用可能なコントロールをカスタマイズするために、[MediaSession] APIと併用する必要があります。
3.7. ページの可視性との連携
pictureInPictureElement
が設定されている場合、
たとえDocumentがフォーカスを失ったり非表示になっている場合でも、ピクチャー・イン・ピクチャーウィンドウは必ず表示されなければなりません。ユーザーエージェントは、ユーザーがピクチャー・イン・ピクチャーウィンドウを手動で閉じられる方法を提供するべきです。
ピクチャー・イン・ピクチャーウィンドウの可視性は、システムの可視性状態がtraversable navigableで変更されたかどうかを判定する際にユーザーエージェントが考慮してはなりません。
3.8. ピクチャー・イン・ピクチャーウィンドウは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()
メソッドが呼び出された場合、新しいpromise promiseを返し、以下の手順を並列で実行しなければなりません:
-
videoはこのメソッドが呼び出されたビデオ要素とします。
-
ピクチャ・イン・ピクチャリクエストアルゴリズムをvideoで実行します。
-
前のステップで例外が投げられた場合、promiseをその例外で拒否し、これらの手順を中断します。
-
promiseを解決し、
pictureInPictureElementに関連付けられたピクチャ・イン・ピクチャウィンドウで返します。
4.2. Documentへの拡張
partial interface Document {readonly attribute boolean ; [pictureInPictureEnabled NewObject ]Promise <undefined >(); };exitPictureInPicture
pictureInPictureEnabled
属性のgetterは、ピクチャ・イン・ピクチャサポートがtrueであり、thisが
属性名picture-in-pictureで示される機能をallowed to
use状態である場合はtrue、それ以外はfalseを返さなければなりません。
ピクチャ・イン・ピクチャサポートは、ユーザーの設定で無効化されている場合やプラットフォームの制限がある場合はfalseです。それ以外はtrueです。
exitPictureInPicture()
メソッドが呼び出された場合、新しいpromise promiseを返し、以下の手順を並列で実行しなければなりません:
-
ピクチャ・イン・ピクチャ終了アルゴリズムを実行します。
-
前のステップで例外が投げられた場合、promiseをその例外で拒否し、これらの手順を中断します。
-
promiseを解決します。
4.3.
DocumentOrShadowRootへの拡張
partial interface mixin DocumentOrShadowRoot {readonly attribute Element ?; };pictureInPictureElement
pictureInPictureElement
属性のgetterは以下の手順を実行します:
-
thisがshadow rootであり、そのhost がconnectedでない場合は
nullを返し、これらの手順を中断します。 -
candidateをretargeting Picture-in-Picture element against thisの結果とします。
-
nullを返します。
4.4.
インターフェース PictureInPictureWindow
[Exposed =Window ]interface :PictureInPictureWindow EventTarget {readonly attribute long ;width readonly attribute long ;height attribute EventHandler ; };onresize
PictureInPictureWindow
インスタンスは、ピクチャ・イン・ピクチャウィンドウ(HTMLVideoElement
に関連付けられたもの)を表します。インスタンス化されると、PictureInPictureWindow
の state は opened に設定されます。
close window
algorithmがPictureInPictureWindowのインスタンスで呼ばれた場合、stateはclosedに設定されます。
width
属性は、stateがopenedのとき、pictureInPictureElementに関連付けられたCSSピクセル単位のピクチャ・イン・ピクチャウィンドウの幅を返します。それ以外の場合は0を返します。
height
属性は、stateがopenedのとき、pictureInPictureElementに関連付けられたCSSピクセル単位のピクチャ・イン・ピクチャウィンドウの高さを返します。それ以外の場合は0を返します。
pictureInPictureElementに関連付けられたピクチャ・イン・ピクチャウィンドウのサイズが変化したとき、ユーザーエージェントはタスクをキューイングして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がピクチャ・イン・ピクチャに入ったときに発火されます。 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"という名前のポリシー管理機能を定義します。
これは、ピクチャ・イン・ピクチャリクエストアルゴリズムが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の皆様には、この文書へのご貢献に感謝いたします。