画中画

W3C 工作草案,

更多关于本文档的详细信息
此版本:
https://www.w3.org/TR/2024/WD-picture-in-picture-20241216/
最新发布版本:
https://www.w3.org/TR/picture-in-picture/
编辑草案:
https://w3c.github.io/picture-in-picture/
之前版本:
历史记录:
https://www.w3.org/standards/history/picture-in-picture/
反馈:
GitHub
编辑:
(Google LLC)
前任编辑:
(Google LLC)
Web 平台测试:
permissions-policy/
picture-in-picture/

简介

本规范提供了 API,允许网站创建始终位于其他窗口之上的浮动视频窗口, 以便用户在与设备上的其他内容站点或应用程序交互时,继续观看媒体内容。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 出版物和该技术报告的最新修订版可在 W3C 技术报告索引中找到,网址为 https://www.w3.org/TR/。

欢迎对本规范提供反馈和意见。建议通过 GitHub Issues 讨论此规范。 或者,您可以向媒体工作组的邮件列表发送意见,public-media-wg@w3.org (档案)。 本草案突出了工作组仍需讨论的一些悬而未决的问题。 尚未对这些问题的结果做出决定,包括它们是否有效。

本文档由 媒体工作组 作为工作草案发布,使用了 推荐 路径。 本文档旨在成为 W3C 推荐标准。

作为工作草案发布不意味着 W3C 及其成员的认可。

这是一个草案文档,可能会随时更新、替换或被其他文档取代。 将此文档引用为非工作进展以外的内容是不合适的。

本文档由一个在 W3C 专利政策下运作的小组制定。 W3C 维护着一份与该小组交付成果相关的任何专利披露的公开列表;该页面还包含披露专利的说明。任何实际知晓某项专利包含其认为是基本权利要求的个人,必须根据 W3C 专利政策第 6 节披露该信息。

本文档受 2023年11月3日 W3C 流程文档 管辖。

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. 内部槽位定义

一个用户代理拥有:

  1. 一个活动画中画会话的发起者列表,包含零个或多个来源,该列表初始为空。

注意:如果用户代理支持多个画中画窗口,则该列表允许重复。

如果在活动画中画会话的发起者中的任何来源与原始来源同源域,则该来源被认为有一个活动的画中画会话。

3.2. 请求画中画

当带有video请求画中画算法被调用时, 用户代理必须运行以下步骤:

  1. 如果画中画支持false,则抛出NotSupportedError并中止这些步骤。

  2. 如果文档不被允许使用名为受政策控制的功能"picture-in-picture",则抛出SecurityError并中止这些步骤。

  3. 如果videoreadyState属性为HAVE_NOTHING,则抛出InvalidStateError并中止这些步骤。

  4. 如果video没有视频轨道,则抛出InvalidStateError并中止这些步骤。

  5. 如果videodisablePictureInPicturetrue,则用户代理可以抛出InvalidStateError并中止这些步骤。

  6. 如果pictureInPictureElementnull,且相关全局对象this不具有瞬时激活,则抛出NotAllowedError并中止这些步骤。

  7. 如果videopictureInPictureElement,则中止这些步骤。

  8. pictureInPictureElement设置为video

  9. 画中画窗口设为一个与pictureInPictureElement关联的PictureInPictureWindow新实例。

  10. 相关设置对象来源附加到活动画中画会话的发起者

  11. 排队一个任务触发一个事件,该事件名为enterpictureinpicture,使用PictureInPictureEvent,其bubbles属性初始化为truepictureInPictureWindow属性初始化为画中画窗口

  12. 如果pictureInPictureElement全屏元素,建议退出全屏

建议不要同时在页面和画中画窗口中渲染视频帧,但如果渲染,则必须保持同步。

当视频在画中画模式中播放时,状态应过渡为类似于内嵌播放。这意味着事件应该同时触发,调用方法应具有相同的行为,等等。然而,当视频元素进入与画中画不兼容的状态时,用户代理可能会退出画中画模式。

应用于video的样式(例如不透明度、可见性、变换等)不得应用于画中画窗口。其纵横比基于视频大小。

还建议画中画窗口具有最大和最小尺寸。例如,它可以被限制为屏幕尺寸的四分之一到二分之一之间。

3.3. 退出画中画

当调用退出画中画算法时, 用户代理必须执行以下步骤:

  1. 如果pictureInPictureElementnull,则抛出InvalidStateError并中止这些步骤。

  2. 使用与pictureInPictureElement关联的画中画窗口,运行关闭窗口算法

  3. 排队一个任务,以触发一个事件,该事件名为leavepictureinpicture,使用PictureInPictureEvent,并将其bubbles属性初始化为true,其pictureInPictureWindow属性初始化为与pictureInPictureElement关联的画中画窗口

  4. 取消设置pictureInPictureElement

  5. 列表项中删除一个匹配相关设置对象来源,从活动画中画会话的发起者列表中移除。

不建议在调用退出画中画算法时更改视频播放状态。如果由网站发起,网站应控制体验。然而,用户代理可以暴露画中画窗口控件以更改视频播放状态(例如暂停)。

作为卸载文档清理步骤的一部分,运行退出画中画算法

3.4. 禁用画中画

某些页面可能希望禁用视频元素的画中画模式;例如,它们可能希望在某些情况下阻止用户代理建议画中画上下文菜单。为了支持这些用例,视频元素的内容属性列表中添加了一个新属性disablePictureInPicture

disablePictureInPictureID属性必须反映相同名称的内容属性。

如果视频元素上存在disablePictureInPicture属性,用户代理可以阻止视频元素在画中画模式下播放或阻止任何相关的用户界面。

disablePictureInPicture属性被添加到video元素时,用户代理可以运行以下步骤:

  1. 拒绝requestPictureInPicture()方法返回的所有待处理承诺,并抛出InvalidStateError

  2. 如果videopictureInPictureElement,运行退出画中画算法

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并并行运行以下步骤:

  1. video为调用该方法的视频元素。

  2. 运行请求画中画算法,使用video

  3. 如果上一步抛出异常,使用该异常拒绝promise,并中止这些步骤。

  4. 使用与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并并行运行以下步骤:

  1. 运行退出画中画算法

  2. 如果上一步抛出异常,使用该异常拒绝promise,并中止这些步骤。

  3. 解析promise

4.3. 扩展 DocumentOrShadowRoot

partial interface mixin DocumentOrShadowRoot {
  readonly attribute Element? pictureInPictureElement;
};

pictureInPictureElement 属性的getter必须运行以下步骤:

  1. 如果this是一个影子根,并且它的宿主没有连接,则返回null并中止这些步骤。

  2. candidate设为重定向画中画元素对this的结果。

  3. 如果candidatethis在相同的中,返回candidate并中止这些步骤。

  4. 返回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

如果 stateopened,则 width 属性必须返回与 pictureInPictureElement 关联的 画中画窗口 的宽度(单位为 CSS 像素)。否则,必须返回 0。

如果 stateopened,则 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,以及pictureInPictureEnabledtrue还是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 对本文档的贡献。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。 在本文档的规范部分中,关键字“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。 然而,为了提高可读性,本规范中这些词不会全部大写。

除非明确标记为非规范性的章节、示例和注释,本规范的所有文本都是规范性的。[RFC2119]

本规范中的示例以“例如”开头或通过 class="example" 与规范文本区分开,如下所示:

这是一个说明性示例。

说明性注释以“注”字开头,并通过 class="note" 与规范文本区分开,如下所示:

注,这是一个说明性注释。

一致性算法

作为算法一部分的命令式要求(例如“去除任何前导空格字符”或“返回 false 并中止这些步骤”) 应按照引入算法时使用的关键字(如“必须”、“应该”、“可以”等)的含义进行解释。

以算法或特定步骤措辞的一致性要求可以通过任何方式实现,只要最终结果等效即可。 特别是,本规范中定义的算法旨在易于理解,而非高效执行。鼓励实现者进行优化。

索引

本规范定义的术语

通过引用定义的术语

参考文献

规范性引用

[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS 值和单位模块 第4版. 2024年3月12日. WD. URL: https://www.w3.org/TR/css-values-4/
[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[FULLSCREEN]
Philip Jägenstedt. 全屏 API 标准. 现行标准. URL: https://fullscreen.spec.whatwg.org/
[HTML]
Anne van Kesteren; 等. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. 权限策略. 2024年9月25日. WD. URL: https://www.w3.org/TR/permissions-policy-1/
[Remote-Playback]
Mark Foltz. 远程播放 API. 2024年4月30日. CR. URL: https://www.w3.org/TR/remote-playback/
[RFC2119]
S. Bradner. RFC 中指示要求级别的关键字. 1997年3月. 最佳现行实践. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. 选择器 第4版. 2022年11月11日. WD. URL: https://www.w3.org/TR/selectors-4/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 现行标准. URL: https://webidl.spec.whatwg.org/

参考性引用

[MediaSession]
Thomas Steimel; youenn fablet. 媒体会话. 2024年10月4日. WD. URL: https://www.w3.org/TR/mediasession/
[SECURE-CONTEXTS]
Mike West. 安全上下文. 2023年11月10日. CR. URL: https://www.w3.org/TR/secure-contexts/

IDL 索引

partial interface HTMLVideoElement {
  [NewObject] Promise<PictureInPictureWindow> requestPictureInPicture();

  attribute EventHandler onenterpictureinpicture;
  attribute EventHandler onleavepictureinpicture;

  [CEReactions] attribute boolean disablePictureInPicture;
};

partial interface Document {
  readonly attribute boolean pictureInPictureEnabled;

  [NewObject] Promise<undefined> exitPictureInPicture();
};

partial interface mixin DocumentOrShadowRoot {
  readonly attribute Element? pictureInPictureElement;
};

[Exposed=Window]
interface PictureInPictureWindow : EventTarget {
  readonly attribute long width;
  readonly attribute long height;

  attribute EventHandler onresize;
};

[Exposed=Window]
interface PictureInPictureEvent : Event {
    constructor(DOMString type, PictureInPictureEventInit eventInitDict);
    [SameObject] readonly attribute PictureInPictureWindow pictureInPictureWindow;
};

dictionary PictureInPictureEventInit : EventInit {
    required PictureInPictureWindow pictureInPictureWindow;
};