1. 简介
本节为非规范性内容。
许多用户希望在与设备上的其他内容、站点或应用程序交互时,继续观看媒体。常见的用户界面工具是画中画(PiP),视频被包含在一个始终位于其他窗口之上的独立小窗口中。即使用户代理不可见,该窗口仍保持可见。画中画是桌面和移动操作系统中常见的平台级功能。
本规范扩展了 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: 根据画中画窗口的宽度和高度调整视频大小。 } </ script >
3. 概念
3.1. 内部槽位定义
一个用户代理拥有:
-
一个活动画中画会话的发起者列表,包含零个或多个来源,该列表初始为空。
注意:如果用户代理支持多个画中画窗口,则该列表允许重复。
如果在活动画中画会话的发起者中的任何来源与原始来源同源域,则该来源被认为有一个活动的画中画会话。
3.2. 请求画中画
当带有video的请求画中画算法被调用时, 用户代理必须运行以下步骤:
-
如果画中画支持为
false
,则抛出NotSupportedError
并中止这些步骤。 -
如果文档不被允许使用名为受政策控制的功能
"picture-in-picture"
,则抛出SecurityError
并中止这些步骤。 -
如果video的
readyState
属性为HAVE_NOTHING
,则抛出InvalidStateError
并中止这些步骤。 -
如果video没有视频轨道,则抛出
InvalidStateError
并中止这些步骤。 -
如果video的
disablePictureInPicture
为true
,则用户代理可以抛出InvalidStateError
并中止这些步骤。 -
如果
pictureInPictureElement
为null
,且相关全局对象的this不具有瞬时激活,则抛出NotAllowedError
并中止这些步骤。 -
如果video是
pictureInPictureElement
,则中止这些步骤。 -
将
pictureInPictureElement
设置为video。 -
将画中画窗口设为一个与
pictureInPictureElement
关联的PictureInPictureWindow
新实例。 -
将相关设置对象的来源附加到活动画中画会话的发起者。
-
排队一个任务以触发一个事件,该事件名为
enterpictureinpicture
,使用PictureInPictureEvent
,其bubbles
属性初始化为true
,pictureInPictureWindow
属性初始化为画中画窗口。
建议不要同时在页面和画中画窗口中渲染视频帧,但如果渲染,则必须保持同步。
当视频在画中画模式中播放时,状态应过渡为类似于内嵌播放。这意味着事件应该同时触发,调用方法应具有相同的行为,等等。然而,当视频元素进入与画中画不兼容的状态时,用户代理可能会退出画中画模式。
应用于video的样式(例如不透明度、可见性、变换等)不得应用于画中画窗口。其纵横比基于视频大小。
还建议画中画窗口具有最大和最小尺寸。例如,它可以被限制为屏幕尺寸的四分之一到二分之一之间。
3.3. 退出画中画
当调用退出画中画算法时, 用户代理必须执行以下步骤:
-
如果
pictureInPictureElement
为null
,则抛出InvalidStateError
并中止这些步骤。 -
使用与
pictureInPictureElement
关联的画中画窗口,运行关闭窗口算法。 -
排队一个任务,以触发一个事件,该事件名为
leavepictureinpicture
,使用PictureInPictureEvent
,并将其bubbles
属性初始化为true
,其pictureInPictureWindow
属性初始化为与pictureInPictureElement
关联的画中画窗口。 -
从列表项中删除一个匹配相关设置对象的来源,从活动画中画会话的发起者列表中移除。
不建议在调用退出画中画算法时更改视频播放状态。如果由网站发起,网站应控制体验。然而,用户代理可以暴露画中画窗口控件以更改视频播放状态(例如暂停)。
3.4. 禁用画中画
某些页面可能希望禁用视频元素的画中画模式;例如,它们可能希望在某些情况下阻止用户代理建议画中画上下文菜单。为了支持这些用例,视频元素的内容属性列表中添加了一个新属性disablePictureInPicture
。
disablePictureInPicture
ID属性必须反映相同名称的内容属性。
如果视频元素上存在disablePictureInPicture
属性,用户代理可以阻止视频元素在画中画模式下播放或阻止任何相关的用户界面。
当disablePictureInPicture
属性被添加到video元素时,用户代理可以运行以下步骤:
-
拒绝
requestPictureInPicture()
方法返回的所有待处理承诺,并抛出InvalidStateError
。 -
如果video是
pictureInPictureElement
,运行退出画中画算法。
3.5. 与全屏的交互
建议在设置pictureInPictureElement
的全屏标志时,运行退出画中画算法。
3.6. 与远程播放的交互
[Remote-Playback]规范定义了本地播放设备和本地播放状态。对于画中画,播放是本地的,无论它是在页面中播放还是在画中画中播放。
3.7. 与媒体会话的交互
该API需要与[MediaSession] API一起使用,以便定制画中画窗口中的可用控件。
3.8. 与页面可见性的交互
当设置了pictureInPictureElement
时,即使文档不在焦点或处于隐藏状态,画中画窗口也必须是可见的。用户代理应提供一种方式让用户手动关闭画中画窗口。
用户代理不得将画中画窗口的可见性考虑在内,用以确定系统可见性状态是否已更改。
3.9. 单个画中画窗口
具有画中画API的操作系统通常将画中画模式限制为仅允许一个窗口。是否只允许一个窗口进入画中画模式将由实现和平台决定。然而,由于单个画中画窗口的限制,本规范假定一个文档
只能拥有一个画中画窗口。
当已经存在一个画中画窗口时发生新的画中画请求时的行为将留给实现细节:当前的画中画窗口可以被关闭,画中画请求可以被拒绝,甚至可以创建两个画中画窗口。不论如何,用户代理必须触发适当的事件,以通知网站画中画状态的变化。
4. API
4.1.
扩展 HTMLVideoElement
partial interface HTMLVideoElement { [NewObject ]Promise <PictureInPictureWindow >();
requestPictureInPicture attribute EventHandler ;
onenterpictureinpicture attribute EventHandler ; [
onleavepictureinpicture CEReactions ]attribute boolean ; };
disablePictureInPicture
requestPictureInPicture()
方法在调用时,必须返回一个新的承诺promise并并行运行以下步骤:
-
让video为调用该方法的视频元素。
-
运行请求画中画算法,使用video。
-
如果上一步抛出异常,使用该异常拒绝promise,并中止这些步骤。
-
使用与
pictureInPictureElement
关联的画中画窗口解析promise。
4.2. 扩展 Document
partial interface Document {readonly attribute boolean ; [
pictureInPictureEnabled NewObject ]Promise <undefined >(); };
exitPictureInPicture
pictureInPictureEnabled
属性的getter必须返回true
,如果画中画支持为true
,且this被允许使用由属性名称picture-in-picture
指示的功能,否则返回false
。
画中画支持为false
,如果存在禁用它的用户偏好或平台限制。否则为true
。
exitPictureInPicture()
方法在调用时,必须返回一个新的承诺promise并并行运行以下步骤:
-
运行退出画中画算法。
-
如果上一步抛出异常,使用该异常拒绝promise,并中止这些步骤。
-
解析promise。
4.3.
扩展 DocumentOrShadowRoot
partial interface mixin DocumentOrShadowRoot {readonly attribute Element ?; };
pictureInPictureElement
pictureInPictureElement
属性的getter必须运行以下步骤:
4.4.
接口 PictureInPictureWindow
[Exposed =Window ]interface :
PictureInPictureWindow EventTarget {readonly attribute long ;
width readonly attribute long ;
height attribute EventHandler ; };
onresize
一个 PictureInPictureWindow
实例表示与 HTMLVideoElement
关联的 画中画窗口。当实例化时,PictureInPictureWindow
实例的 state 被设置为 opened
。
当调用包含 PictureInPictureWindow
实例的 关闭窗口算法 时,其
state 被设置为 closed
。
如果 state 为 opened
,则 width
属性必须返回与 pictureInPictureElement
关联的 画中画窗口 的宽度(单位为 CSS 像素)。否则,必须返回 0。
如果 state 为 opened
,则 height
属性必须返回与 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],因为它为 Web 应用程序提供了用户代理通常在所有媒体上原生提供的功能,无论浏览上下文如何。
5.2. 权限策略
本规范定义了一个名为"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 对本文档的贡献。