画中画 (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 的实现遵循此模式。也就是说,用户只能 每次只能播放一个画中画视频。您应该会让用户退出 进入画中画模式,即使您并未要求使用该功能。
新的 enterpictureinpicture
和 leavepictureinpicture
事件处理脚本允许
为用户量身定制体验。比如浏览网页
视频目录,以及显示直播聊天窗口。
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">![画中画窗口中的媒体播放控件](https://developer.chrome.google.cn/static/blog/watch-video-using-picture-in-picture/image/media-playback-controls-b73d819a65211.png?authuser=7&hl=ro)
默认情况下,画中画中会始终显示播放/暂停按钮
窗口中,除非视频正在播放 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 中创建音频播放列表窗口的实例。查看官方
音频播放列表示例。
![画中画窗口中的音频播放列表](https://developer.chrome.google.cn/static/blog/watch-video-using-picture-in-picture/image/audio-playlist-a-picture-4c2f236920dae.jpeg?authuser=7&hl=ro)
示例、演示和 Codelab
如需试用画中画功能,请查看我们的官方画中画示例 Web API。
后续将提供演示和 Codelab。
后续步骤
首先,请查看实现状态页面,了解 目前,Chrome 和其他浏览器中已实现 API。
不久之后,您将会看到以下变化:
- Web 开发者可以添加自定义的画中画控件。
- 我们将提供新的 Web API,用于在浮动窗口中显示任意
HTMLElement
对象。
浏览器支持
Chrome、Edge、Opera 和 Safari 支持画中画 Web API。 如需了解详情,请参阅 MDN。
资源
- Chrome 功能状态:https://www.chromestatus.com/feature/5729206566649856
- Chrome 实现 bug:https://crbug.com/?q=component:Blink>Media>PictureInPicture
- 画中画 Web API 规范:https://wicg.github.io/picture-in-picture
- 规范问题:https://github.com/WICG/picture-in-picture/issues
- 示例:https://googlechrome.github.io/samples/picture-in-picture/
- 非官方画中画 polyfill:https://github.com/gbentaieb/pip-polyfill/
非常感谢 Mounir Lamouri 和 Jennifer Apacible 画中画功能,以及有关此文章的帮助信息。衷心感谢每一位 标准化工作所需的资源。