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. 定义
画中画窗口是一个显示
video
元素的窗口。
-
一个画中画元素,它是一个
Element或null,初始为null。
一个可遍历导航器具有:
-
一个画中画并行队列,它是一个通过并行队列 启动一个新的并行队列创建的队列。
一个用户 代理具有:
如果活动画中画会话的发起者中的任何源 与该源是同一源域,则称该源具有活动的画中画会话。
3.2. 画中画
建议不要同时在页面和画中画窗口中渲染视频帧;但如果同时渲染,则必须保持 同步。
当视频以画中画模式播放时,状态应当像内联播放一样转换。这意味着事件应当在相同 时间触发,调用方法应当具有相同的行为,等等。不过,当 video 元素进入被认为 与画中画不兼容的状态时,用户代理可以退出画中画。
应用于 video 的样式(例如 opacity、visibility、transform 等)不得 应用到画中画窗口中。其宽高比基于视频尺寸。
还建议画中画窗口具有最大和 最小尺寸。例如,可以将其限制在屏幕某一维度的四分之一到 一半之间。
当一个 DocumentOrShadowRoot
的
画中画元素被设置时,
画中画窗口必须可见,即使该
DocumentOrShadowRoot
的
相关全局对象的关联 Document的
可见性状态为 "hidden"。
用户代理应当为用户提供一种手动关闭
画中画窗口的方式。
3.3. 退出画中画
当退出画中画算法被调用时, 用户代理必须运行以下步骤:
-
如果
pictureInPictureElement为null,则抛出一个InvalidStateError并 中止这些步骤。 -
使用与
pictureInPictureElement关联的画中画 窗口运行关闭窗口算法。 -
排入一个任务,以便在 video 上使用
PictureInPictureEvent触发一个 事件,其名称为leavepictureinpicture, 其bubbles属性初始化为true,其pictureInPictureWindow属性初始化为与pictureInPictureElement关联的画中画窗口。 -
取消设置
pictureInPictureElement。 -
从活动 画中画会话的发起者中移除一个与相关设置对象的源匹配的项目。
不建议在退出 画中画算法被调用时改变视频播放状态。如果这是由网站发起的, 则该体验应当由网站控制。不过,用户代理可以公开会改变视频播放状态 的画中画窗口控件(例如 暂停)。
3.4. 禁用画中画
某些页面可能希望禁用某个 video 元素的画中画模式;例如,
在某些情况下,它们可能希望阻止用户代理建议使用
画中画上下文菜单。
为了支持这些用例,一个新的 disablePictureInPicture
属性被添加到 video 元素的内容属性列表中。
disablePictureInPicture
IDL 属性必须反映同名的内容
属性。
如果 video 元素上存在 disablePictureInPicture
属性,
用户代理可以阻止该 video 元素以画中画
模式播放,或阻止显示任何用于这样做的 UI。
当 disablePictureInPicture
属性被添加到 video 元素时,
用户代理可以运行以下步骤:
-
用
InvalidStateError拒绝由requestPictureInPicture()方法返回的任何待处理 promise。 -
如果 video 是
pictureInPictureElement, 则运行退出 画中画算法。
3.5. 与全屏的交互
建议在
pictureInPictureElement
的全屏标志被设置时,运行退出画中画算法。
3.6. 与页面可见性的交互
用户代理在判定 系统可见性状态 是否已发生变化时, 不得将画中画窗口的可见性纳入考虑, 无论该 可遍历可导航对象 的状态如何。
3.7. 唯一的画中画窗口
具有画中画 API 的操作系统通常限制画中画模式只能有一个窗口。是否只允许一个窗口处于画中画模式将由实现和平台决定。但由于仅有一个画中画窗口的限制,本规范假设每个 Document
只会拥有一个画中画窗口。
当已经有一个窗口处于画中画模式时再次发起画中画请求的行为,将由具体实现决定:可能会关闭当前的画中画窗口,拒绝新的画中画请求,甚至允许创建两个画中画窗口。无论哪种情况,用户代理都必须触发相应事件,以便通知网站画中画状态的变化。
4. API
4.1.
扩展 HTMLVideoElement
partial interface HTMLVideoElement { [NewObject ]Promise <PictureInPictureWindow >();requestPictureInPicture attribute EventHandler ;onenterpictureinpicture attribute EventHandler ; [onleavepictureinpicture CEReactions ]attribute boolean ; };disablePictureInPicture
requestPictureInPicture()
方法步骤请求画中画:
-
如果画中画支持为
false, 则返回一个被拒绝的 promise,其拒绝原因为NotSupportedErrorDOMException。 -
如果 doc 未被允许使用名为
"picture-in-picture"的策略控制特性, 则返回一个被拒绝的 promise,其拒绝原因为NotAllowedErrorDOMException。 -
如果 this 的
readyState属性为HAVE_NOTHING, 则返回一个被拒绝的 promise,其拒绝原因为InvalidStateErrorDOMException。 -
如果 this 没有视频轨道,则返回一个被拒绝的 promise,其拒绝原因为
InvalidStateErrorDOMException。 -
如果 this 的
disablePictureInPicture为 true,用户代理可以返回一个被拒绝的 promise,其拒绝原因为InvalidStateErrorDOMException。 -
如果 doc 的画中画元素为
null: -
-
返回一个兑现的 promise,其兑现值为与 doc 的画中画元素关联的画中画窗口。
-
-
-
如果上一步失败:
-
给定 global,在媒体元素事件任务 源上排入一个全局任务,以用
InvalidStateErrorDOMException拒绝 p。 -
中止这些步骤。
-
-
令 pipWindow 为
PictureInPictureWindow的一个新实例,它表示 this 的关联画中画窗口。 -
给定 global,在媒体元素事件任务源上排入一个全局任务,以执行 以下步骤:
-
如果
pictureInPictureElement不是null,则运行退出画中画 算法。 -
如果 this 是 fullscreenElement,则退出全屏。
-
在 this 上使用
PictureInPictureEvent触发一个事件,其名称为enterpictureinpicture, 其bubbles属性初始化为true,其pictureInPictureWindow属性初始化为 画中画窗口。 -
用 pipWindow 兑现 p。
-
4.2. 扩展 Document
partial interface Document {readonly attribute boolean ; [pictureInPictureEnabled NewObject ]Promise <undefined >(); };exitPictureInPicture
pictureInPictureEnabled
属性的 getter 必须在
画中画支持为 true 且 this
被允许使用由属性名
picture-in-picture 指示的特性时,返回 true;否则返回 false。
画中画
支持在存在禁用它的用户偏好
或平台限制时为 false。否则为 true。
exitPictureInPicture()
方法在被调用时,必须
返回一个新的 promise promise,并并行运行以下步骤:
4.3.
扩展 DocumentOrShadowRoot
partial interface mixin DocumentOrShadowRoot {readonly attribute Element ?; };pictureInPictureElement
pictureInPictureElement
属性的 getter 必须运行以下步骤:
-
返回
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。
当带有
PictureInPictureWindow
实例的关闭窗口
算法被调用时,其 state 会被设置为 closed。
width
属性必须返回与 pictureInPictureElement
关联的画中画窗口的以 CSS 像素表示的宽度,
如果
state 为 opened。否则,它必须返回 0。
height
属性必须返回与 pictureInPictureElement
关联的画中画窗口的以 CSS 像素表示的高度,
如果
state 为 opened。否则,它必须返回 0。
当一个画中画窗口 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],因为它为 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 对本文档的贡献。