能力 · API
Screen Capture API(getDisplayMedia)
发布于 更新于
navigator.mediaDevices.getDisplayMedia() 打开浏览器的屏幕、窗口或标签页选择器,兑现为一个恰好含一条视频轨、至多一条音频轨的 MediaStream,可直接交给 MediaRecorder、<video> 元素或 RTCPeerConnection。与 getUserMedia() 不同,授权从不持久化:每次调用都会再弹选择器,页面无法枚举显示源,也无法预选。
桌面端的 Chrome 72、Edge 79、Firefox 66 与 macOS 上的 Safari 13 实现了它(Edge 17 到 79 在 navigator 上暴露过早期版本)。Android 上的 Chrome 72 到 88 与 Firefox 66 到 79 暴露了该方法,但每次调用都以 NotAllowedError 拒绝(Chromium bug 40418135);如今 Android 上的 Chrome、Firefox 与 iOS 上的 Safari 都不再暴露它(BCD api.MediaDevices.getDisplayMedia)。video 与 audio 之外的捕获选项只有 Chromium 实现。
navigator.mediaDevices.getDisplayMedia()navigator.mediaDevices.getDisplayMedia(options)返回 Promise<MediaStream>。调用需要瞬时激活、处于 fully active 且拥有焦点的文档、安全上下文,以及 display-capture Permissions Policy(默认允许列表为 'self',跨源 iframe 需要 allow="display-capture")。浏览器会消耗这次激活,第二次调用需要再点一次。
唯一的可选参数是 DisplayMediaStreamOptions 字典。video 与 audio 来自 W3C 规范;其余成员是浏览器可以忽略的提示,只有 Chromium 实现(版本号来自 BCD)。
| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
video |
boolean 或 MediaTrackConstraints |
否,默认 true |
传 false 以 TypeError 拒绝。约束对象可带作为提示的 displaySurface("browser"、"window"、"monitor")以及 width、height、frameRate、aspectRatio、cursor;约束在用户选定之后才应用,不会过滤选择器。min、exact 与 advanced 会被拒绝。 |
audio |
boolean 或 MediaTrackConstraints |
否,默认 false |
请求音频轨;浏览器可以只返回视频。Chrome 74 在 Windows 与 ChromeOS 上捕获系统音频,其他平台只捕获标签页音频。 |
controller |
CaptureController |
否 | 用户选定标签页或窗口后,让页面通过 setFocusBehavior() 保留或转移焦点;每次调用一个控制器。Chrome 109。 |
preferCurrentTab |
boolean |
否,默认 false |
把调用方标签页排在选择器最前。Chrome 94。 |
selfBrowserSurface |
"include" 或 "exclude" |
否 | 调用方标签页是否出现在选择器中;Chrome 112 默认 "exclude"(107 到 111 默认 "include")。 |
surfaceSwitching |
"include" 或 "exclude" |
否 | 捕获期间 Chrome 是否显示「改为共享此标签页」控件。Chrome 107。 |
systemAudio |
"include" 或 "exclude" |
否 | 选择整个屏幕时选择器是否提供系统音频。Chrome 105。 |
windowAudio |
"exclude"、"window" 或 "system" |
否 | 选择窗口时提供的音频;Chrome 141 只接受 "exclude" 与 "system"。 |
monitorTypeSurfaces |
"include" 或 "exclude" |
否 | 是否提供整个屏幕作为选项。Chrome 119。 |
Promise 以下列之一拒绝。Chrome 的消息引自 media_devices.cc。
| 异常 | 条件 |
|---|---|
InvalidStateError |
没有瞬时激活(Chrome:getDisplayMedia() requires transient activation (user gesture).);文档不是 fully active 或没有焦点;传入的 controller 已被用过(Chrome:A CaptureController object may only be used with a single getDisplayMedia() invocation.)。 |
TypeError |
video: false,或约束集中含 advanced、min 或 exact。 |
OverconstrainedError |
某个 max 约束低于该属性的下限,或对所选表面应用约束失败。 |
NotAllowedError |
用户关闭了选择器、权限状态为 "denied"、操作系统或企业策略禁止捕获,或 display-capture Permissions Policy 拒绝了该文档(Chrome:Access to the feature "display-capture" is disallowed by permissions policy.)。 |
NotFoundError |
不存在所请求类型的源。 |
NotReadableError |
用户已授权,但操作系统层面的锁阻止读取该表面。 |
AbortError |
设备访问因上述之外的原因失败。 |
每个示例都从按钮点击开始以保证激活存在,调用前先检测方法,并给出捕获不可用或被拒绝时页面的做法。
预览所选表面,不可用时给出提示
Section titled “预览所选表面,不可用时给出提示”检测 navigator.mediaDevices 上的 getDisplayMedia;在非安全源上 mediaDevices 本身就不存在。方法缺失覆盖了 iOS 上的 Safari 与所有 Android 浏览器;选择器出现之后的 NotAllowedError 是用户改了主意,不应当作失败上报。
const button = document.querySelector("#share-screen");const preview = document.querySelector("video#preview");const notice = document.querySelector("#capture-notice");
if (!navigator.mediaDevices?.getDisplayMedia) { button.disabled = true; notice.textContent = "此浏览器不支持屏幕共享。";} else { button.addEventListener("click", async () => { try { const stream = await navigator.mediaDevices.getDisplayMedia({ video: { displaySurface: "window" }, audio: false, }); preview.srcObject = stream; stream.getVideoTracks()[0].addEventListener("ended", () => { preview.srcObject = null; // 用户点了浏览器自己的「停止共享」控件 }); } catch (err) { notice.textContent = err.name === "NotAllowedError" ? "已取消共享。" : `${err.name}: ${err.message}`; } });}ended 事件是用户从浏览器自身 UI 停止共享的唯一信号;不监听它,<video> 会一直停在最后一帧。
用 MediaRecorder 把标签页录成 WebM
Section titled “用 MediaRecorder 把标签页录成 WebM”用 preferCurrentTab(Chrome 94 的提示,其他浏览器忽略)请求当前标签页并带音频,再把流交给 MediaRecorder。轨道结束时停止录制器,这样即使用户从浏览器工具栏而不是你的按钮结束共享,文件也能收尾。
async function recordTab() { if (!navigator.mediaDevices?.getDisplayMedia || typeof MediaRecorder === "undefined") { return null; } const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true, preferCurrentTab: true, }); const recorder = new MediaRecorder(stream, { mimeType: "video/webm" }); const chunks = []; recorder.addEventListener("dataavailable", (event) => chunks.push(event.data));
const done = new Promise((resolve) => { recorder.addEventListener("stop", () => resolve(new Blob(chunks, { type: "video/webm" }))); }); stream.getVideoTracks()[0].addEventListener("ended", () => recorder.stop()); recorder.start(1000); return { stop: () => recorder.stop(), done };}向用户承诺有音频轨之前先看 stream.getAudioTracks().length:Firefox 与 Safari 只返回视频,macOS 与 Linux 上的 Chrome 只在共享标签页时带音频,窗口与屏幕都没有。
用 CaptureController 让焦点留在你的页面
Section titled “用 CaptureController 让焦点留在你的页面”默认情况下,选择器关闭后 Chrome 会把焦点交给被捕获的标签页或窗口,用户刚点过的控件随之被挡住。调用前创建的 CaptureController 可以要求 Chrome 停在原处;必须在 Promise 兑现后的那一刻调用,其他浏览器根本没有这个构造函数。
async function startCaptureKeepingFocus() { const options = { video: true }; let controller = null; if ("CaptureController" in window) { controller = new CaptureController(); options.controller = controller; } const stream = await navigator.mediaDevices.getDisplayMedia(options); if (controller && stream.getVideoTracks()[0].getSettings().displaySurface !== "monitor") { controller.setFocusBehavior("no-focus-change"); } return stream;}对整个屏幕 setFocusBehavior() 会被忽略,因为没有可聚焦的对象;规范的「finalize focus decision」步骤在 Promise 兑现后紧接着排队的任务里执行,此后再调用会抛 InvalidStateError,所以 await 之后要同步调用。
- WebRTC,把捕获到的流发给另一端
- WebCodecs,不经
MediaRecorder直接编码捕获帧 - Document Picture-in-Picture,用户切到别处时让捕获控件保持可见
- Screen Capture: getDisplayMedia() method(w3.org)
- Screen Capture: DisplayMediaStreamOptions dictionary(w3.org)
- Better screen sharing with Conditional Focus and the other screen-sharing controls(developer.chrome.com)
- Chromium bug 40418135: getDisplayMedia on Android(crbug.com)
规范
| 规范 | 状态 |
|---|---|
| Screen Capture: getDisplayMedia() method | W3C |
| Screen Capture: DisplayMediaStreamOptions dictionary | W3C |