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. リモート再生との相互作用
[Remote-Playback]仕様はローカル再生デバイスおよびローカル再生状態を定義しています。ピクチャ・イン・ピクチャの目的においては、 再生はローカルであり、ページ内で再生されていてもピクチャ・イン・ピクチャであっても同様です。
3.7. メディアセッションとの相互作用
このAPIは[MediaSession] APIと組み合わせて利用し、ピクチャ・イン・ピクチャウィンドウ上の利用可能なコントロールをカスタマイズする必要があります。
3.8. ページ可視性との相互作用
pictureInPictureElement
が設定されている場合、ピクチャ・イン・ピクチャウィンドウは
Documentがフォーカスされていなくても、非表示でも必ず表示されていなければなりません。ユーザーエージェントはユーザーがピクチャ・イン・ピクチャウィンドウを手動で閉じる手段を提供するべきです。
ピクチャ・イン・ピクチャウィンドウの可視性は、ユーザーエージェントがシステム可視性状態が traversable navigableで変更されたかどうかを判断する材料として利用してはなりません。
3.9. ピクチャ・イン・ピクチャウィンドウは一つのみ
ピクチャ・イン・ピクチャAPIを持つOSでは、通常ピクチャ・イン・ピクチャモードは一つのウィンドウに制限されています。ピクチャ・イン・ピクチャモードが一つのウィンドウのみ許可されるかどうかは実装・プラットフォームの判断に委ねられます。
ただし、ピクチャ・イン・ピクチャウィンドウが一つであるという制限のため、
本仕様では、Document
毎に一つだけピクチャ・イン・ピクチャウィンドウが持てるものとしています。
既にピクチャ・イン・ピクチャウィンドウがある状態でリクエストが発生した場合の挙動は実装依存です。現在のピクチャ・イン・ピクチャウィンドウを閉じる、リクエストを拒否する、あるいは二つのウィンドウを作成することもできます。いずれの場合も、ユーザーエージェントはウェブサイトにピクチャ・イン・ピクチャ状態の変化を通知するために適切なイベントを発火しなければなりません。
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の皆様には、この文書へのご貢献に感謝いたします。