Yujie

Picture in Picture

Picture-in-Picture 画中画

这个 API 允许网页创建一个浮动的视频播放小窗口,它会一直显示在其他窗口的最前面,这样用户就可以继续观看视频,同时他们也可以继续浏览其他网页或继续操作其他应用程序。

现在正统浏览器已经内置这个功能了,而且还有配套的 API 给开发者使用。

WICG,全名为 Web Incubator Community Group(Web 孵化器社区小组),主要成员为 Chrome 的工程师们,他们标准化了这个能让视频弹窗播放的特性,起名为 Picture-in-Picture(画中画),缩写为 PiP。

下面将主要讲两方面:Chrome 当前实现的 PiP 交互是怎么样的,以及有哪些 API 让我们开发者使用。

Chrome 中的 PiP 交互

我简单的弄了一个 在线Demo 页面。

在演示 Demo 中可以看到,Chrome 在原生的 video 控件中提供了开启画中画模式的菜单项,一旦开启画中画模式,原本页面中内联(inline)展现的那个 video 还占据着原来的位置,各种控件也都在,但最重要的视频画面已经被无缝的转移到了新的弹出窗口中了,只剩下一张 poster 图片和一层灰色的遮罩。

我们把弹出的那个视频窗口叫做 PiP 窗口,PiP 窗口只有视频画面,没有标题栏和地址栏(chrome-less),这一点和用 window.open()打开的弹窗不一样。同时,PiP 窗口也没有完整的视频控件,只有开始/暂停/关闭/返回到标签页四个按钮,时间、进度条、音量这些控件都没有。PiP 窗口还支持拖拽改变大小(不能大于一个象限),以及支持拖拽到任意位置。还有最重要的一点是,PiP 窗口在所有窗口中是置顶的(always on top)。

但是,在同一时刻只能允许有一个 PiP 窗口。同一个页面里如果为多个视频开启画中画模式,前面开启的那个会自动退出画中画模式,同一个浏览器窗口下的多个 Tab 里的视频亦如此,同一个浏览器打开的多个浏览器窗口亦如此。

PiP 相关 API

1、video 元素新增的方法 requestPictureInPicture()

请求让该 video 元素进入画中画模式,返回一个 promise,如果没有异常,这个 promise 包的值会是一个 PictureInPictureWindow 对象,这个对象就代表弹出的那个 PiP 窗口,后面会单独讲它的 API。

async function openPiP(video) {
  try {
    const pipWindow = await video.requestPictureInPicture() // 进入画中画模式
    ...
  } catch (e) {
    console.error(e) // 处理异常
  }
})

哪些情况下进入画中画模式会失败?一共有 5 种情况:

2、video 元素新增的属性 disablePictureInPicture

通过该属性可以禁用 video 元素的画中画特性,右键菜单中的“画中画”选项会被禁用。

通过 HTML 属性:

<video src="..." disablePictureInPicture>

通过 DOM 属性:

video.disablePictureInPicture = true

3、video 元素新增的事件 enterpictureinpicture 和 leavepictureinpicture

video.addEventListener('enterpictureinpicture', function(pipWindow) {
  // 进入了画中画模式,可以拿到 pipWindow 对象
})

video.addEventListener('leavepictureinpicture', function() {
  // 退出了画中画模式
})

4、 document 上新增的方法 exitPictureInPicture()

因为一个页面只能打开一个 PiP 窗口,所以让 video 元素退出画中画模式的方法不在 video 元素自己身上,而在 document 上。

这个方法也返回一个 promise,不过 promise 包的值是个 undefined。

5、 document 上新增的属性 pictureInPictureElement和 pictureInPictureEnabled

类似于 document.pointerLockElement 和 document.fullscreenElement, document.pictureInPictureElement 会返回当前页面中处于画中画模式的 video 元素,如果没有的话,返回 null

document.pictureInPictureEnabled 上面已经提到过了,在当前页面不支持或被禁用画中画模式的情况下会返回 false,否则返回 true。

这两个属性都是只读的。

6、 PictureInPictureWindow对象的 API

从 requestPictureInPicture() 方法的返回值和 enterpictureinpicture 事件的回调参数中可以拿到 pipWindow 对象,该对象有两个属性 width 和 height,还支持一个 resize 事件,在用户改变 PiP 窗口大小时会触发。

async function openPiP(video) {
  const pipWindow = await video.requestPictureInPicture()
  console.log(pipWindow.width, pipWindow.height) // 打印了默认的窗口大小

  pipWindow.addEventListener('resize', function() {
    console.log(pipWindow.width, pipWindow.height) // 用户改变 PiP 窗口大小时触发
  })
}

注意这里的 width 和 height 是只读的,你不能通过给他们赋值来改变窗口大小。

参考文章:

Watch video using Picture-in-Picture

浏览器中的画中画(Picture-in-Picture)模式及其 API