使用“画中画”模式观看视频

François Beaufort
François Beaufort

画中画 (PiP) 功能让用户可以在浮动窗口中观看视频 (始终显示在其他窗口的顶部),以便密切监控当前 在与其他网站或应用互动时观看。

借助画中画 Web API,您可以启动和控制 以画中画形式显示您网站上的视频元素。试试我们的 官方画中画示例

背景

2016 年 9 月,Safari 通过 WebKit API 添加了画中画支持 。6 个月后,Chrome 自动播放了 Android O 发布后,移动设备上的画中画视频 原生 Android API。六个月后,我们宣布了构建和 标准化一个与 Safari 兼容的 Web API, 让开发者可以创建和控制有关画中画的完整体验。 我们到了!

深入了解代码

进入画中画模式

我们先简单介绍一个视频元素和一种用户交互方式 例如按钮元素

<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>

仅在响应用户手势时请求画中画,而不得在 由 videoElement.play() 返回的 promise。这是因为,promise 不会 会传播用户手势。应改为在requestPictureInPicture() 点击处理程序在 pipButtonElement 上创建,如下所示。您的责任 处理用户点击两次后会发生的情况

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

当 promise 解析后,Chrome 会将视频缩小为一个小窗口, 用户可以四处移动和定位其他窗口。

大功告成。太棒了!你可别再读了,去享受应有的价值 度假。遗憾的是,情况并非始终如此。对于任何请求,promise 可能会拒绝 原因如下:

  • 系统不支持画中画。
  • 由于存在限制,文档不得使用画中画功能 权限政策
  • 视频元数据尚未加载 (videoElement.readyState === 0)。
  • 视频文件是纯音频。
  • 视频元素上提供了新的 disablePictureInPicture 属性。
  • 调用不是在用户手势事件处理脚本中进行的(例如按钮点击)。 从 Chrome 74 开始,仅当 Chrome 中不含任何元素时,此属性才适用 已处于画中画模式。

下面的功能支持部分介绍了如何根据以下建议启用/停用按钮: 这些限制。

我们来添加一个 try...catch 代码块来捕获这些潜在错误,并让 用户会知道发生了什么

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  try {
    await videoElement.requestPictureInPicture();
  } catch (error) {
    // TODO: Show error message to user.
  } finally {
    pipButtonElement.disabled = false;
  }
});

无论视频元素是处于画中画模式还是 不能:事件会被触发,并且调用方法可以正常使用。它反映了 画中画窗口(例如播放、暂停、跳转等) 可以在 JavaScript 中以编程方式更改状态

退出“画中画”模式

现在,让我们通过按钮来切换进入和退出画中画模式。周三 首先,必须检查只读对象 document.pictureInPictureElement 也就是我们的视频元素如果不是,我们将发送一个请求, 画中画(如上所示)。否则,我们会呼叫您退出 document.exitPictureInPicture(),这表示视频将以以下形式重新显示: 原始标签页。请注意,此方法也会返回一个 promise。

    ...
    try {
      if (videoElement !== document.pictureInPictureElement) {
        await videoElement.requestPictureInPicture();
      } else {
        await document.exitPictureInPicture();
      }
    }
    ...

监听画中画事件

操作系统通常将画中画限制在一个窗口内,因此 Chrome 的实现遵循此模式。也就是说,用户只能 每次只能播放一个画中画视频。您应该会让用户退出 进入画中画模式,即使您并未要求使用该功能。

新的 enterpictureinpictureleavepictureinpicture 事件处理脚本允许 为用户量身定制体验。比如浏览网页 视频目录,以及显示直播聊天窗口。

videoElement.addEventListener('enterpictureinpicture', function (event) {
  // Video entered Picture-in-Picture.
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  // Video left Picture-in-Picture.
  // User may have played a Picture-in-Picture video from a different page.
});

自定义“画中画”窗口

Chrome 74 支持播放/暂停、上一首和下一首 可使用 Media Session API 控制的画中画窗口。

<ph type="x-smartling-placeholder">
</ph> 画中画窗口中的媒体播放控件 <ph type="x-smartling-placeholder">
</ph> 图 1. 画中画窗口中的媒体播放控件

默认情况下,画中画中会始终显示播放/暂停按钮 窗口中,除非视频正在播放 MediaStream 对象(例如 getUserMedia()getDisplayMedia()canvas.captureStream())或视频具有 MediaSource 时长设置为 +Infinity(例如实时 Feed)。要确保有播放/暂停按钮 始终可见,请为“Play”和 “暂停”媒体事件。

// Show a play/pause button in the Picture-in-Picture window
navigator.mediaSession.setActionHandler('play', function () {
  // User clicked "Play" button.
});
navigator.mediaSession.setActionHandler('pause', function () {
  // User clicked "Pause" button.
});

当前显示的是“上一首”和“下一首”窗口控件类似设置 这些内容的媒体会话操作处理程序会在画中画中显示它们 然后您就可以处理这些操作了

navigator.mediaSession.setActionHandler('previoustrack', function () {
  // User clicked "Previous Track" button.
});

navigator.mediaSession.setActionHandler('nexttrack', function () {
  // User clicked "Next Track" button.
});

如需了解实际操作,请尝试官方 Media Session 示例

获取画中画窗口大小

如果您想在视频进入和离开时调整视频质量 画中画,您需要知道画中画窗口的大小,并 在用户手动调整窗口大小时收到通知。

以下示例展示了如何获取 创建或调整大小时的画中画窗口。

let pipWindow;

videoElement.addEventListener('enterpictureinpicture', function (event) {
  pipWindow = event.pictureInPictureWindow;
  console.log(`> Window size is ${pipWindow.width}x${pipWindow.height}`);
  pipWindow.addEventListener('resize', onPipWindowResize);
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  pipWindow.removeEventListener('resize', onPipWindowResize);
});

function onPipWindowResize(event) {
  console.log(
    `> Window size changed to ${pipWindow.width}x${pipWindow.height}`
  );
  // TODO: Change video quality based on Picture-in-Picture window size.
}

我建议不要直接挂接到调整大小事件,因为每一次细微更改都会 将会触发单独的事件,而这可能会导致 则会导致性能问题。在 换句话说,调整大小操作会在很短的时间内 。我建议采用常用的方法,例如限制和 去抖动以解决此问题。

功能支持

画中画网络 API 可能不受支持,因此您必须检测 以提供渐进式增强。即使它受支持,也可能 或被权限政策停用。幸运的是,你可以使用 新的布尔值 document.pictureInPictureEnabled 来确定此值。

if (!('pictureInPictureEnabled' in document)) {
  console.log('The Picture-in-Picture Web API is not available.');
} else if (!document.pictureInPictureEnabled) {
  console.log('The Picture-in-Picture Web API is disabled.');
}

应用于视频的特定按钮元素时,您可能希望 处理画中画按钮的可见性。

if ('pictureInPictureEnabled' in document) {
  // Set button ability depending on whether Picture-in-Picture can be used.
  setPipButton();
  videoElement.addEventListener('loadedmetadata', setPipButton);
  videoElement.addEventListener('emptied', setPipButton);
} else {
  // Hide button if Picture-in-Picture is not supported.
  pipButtonElement.hidden = true;
}

function setPipButton() {
  pipButtonElement.disabled =
    videoElement.readyState === 0 ||
    !document.pictureInPictureEnabled ||
    videoElement.disablePictureInPicture;
}

MediaStream 视频支持

视频播放 MediaStream 对象(例如 getUserMedia()getDisplayMedia()canvas.captureStream())也支持 Chrome 71 中的画中画功能。这个 表示您可以显示包含用户网络摄像头的画中画窗口 视频流、展示视频流,甚至是画布元素。请注意, 视频元素就无需附加到 DOM 画中画,如下所示。

在画中画窗口中显示用户的摄像头

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

在“画中画”窗口中显示内容

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

在画中画窗口中显示画布元素

const canvas = document.createElement('canvas');
// Draw something to canvas.
canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);

const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();

// Later on, video.requestPictureInPicture();

canvas.captureStream()Media Session API 结合使用,您可以: 在 Chrome 74 中创建音频播放列表窗口的实例。查看官方 音频播放列表示例

<ph type="x-smartling-placeholder">
</ph> 画中画窗口中的音频播放列表 <ph type="x-smartling-placeholder">
</ph> 图 2. 画中画窗口中的音频播放列表

示例、演示和 Codelab

如需试用画中画功能,请查看我们的官方画中画示例 Web API。

后续将提供演示和 Codelab。

后续步骤

首先,请查看实现状态页面,了解 目前,Chrome 和其他浏览器中已实现 API。

不久之后,您将会看到以下变化:

浏览器支持

Chrome、Edge、Opera 和 Safari 支持画中画 Web API。 如需了解详情,请参阅 MDN

资源

非常感谢 Mounir Lamouri 和 Jennifer Apacible 画中画功能,以及有关此文章的帮助信息。衷心感谢每一位 标准化工作所需的资源。