首页 / Electron 入门教程 / 截图与桌面捕获:捕获屏幕与窗口

Electron 入门教程

截图与桌面捕获:捕获屏幕与窗口

本教程共 45 篇 · 第 28 篇 · 更新于 2026-08-03

ElectrondesktopCapturer截图屏幕捕获WebRTC

28. 截图与桌面捕获:采集屏幕与窗口

本节目标

  • desktopCapturer.getSources 获取屏幕与窗口列表及缩略图。
  • 理解 getDisplayMediasetDisplayMediaRequestHandler 的实时采集流程。
  • 把捕获到的流接入 <video> 做本地预览。
  • 了解 macOS、Windows 的授权与音频捕获限制。
  • 掌握沙箱下如何安全地在渲染进程触发捕获。

1-1 desktopCapturer 的定位

desktopCapturer 用来列出屏幕上可捕获的「媒体源」:整个屏幕,或者某个应用窗口。它在主进程运行,是一个偏底层的模块。

最常见的两个用途:一是生成屏幕/窗口的缩略图,做「选择要共享哪个画面」的界面;二是配合 WebRTC 的 getDisplayMedia,把桌面画面作为视频流采集进应用,用于录屏、直播推流等。

需要强调:单纯调用 desktopCapturer 并不会「自动开始录像」。它只是提供源信息和缩略图,真正的画面流要靠 WebRTC 的 getUserMedia / getDisplayMedia 拿到。

1-2 获取屏幕与窗口列表

getSources(options) 接受一个配置对象,返回一个 Promise,解析为 DesktopCapturerSource 数组。关键选项有:

  • types:要枚举的类型,数组里可含 'screen''window'
  • thumbnailSize:缩略图缩放尺寸,默认 150×150;不需要缩略图时把宽或高设为 0 可省性能。
  • fetchWindowIcons:是否取窗口图标,默认 false

每个 source 带有 idname 以及 thumbnail(一个 NativeImage)。下面演示列出所有屏幕与窗口并打印名称。

// 主进程 main.js
const { desktopCapturer } = require('electron/main')

async function listSources() {
  const sources = await desktopCapturer.getSources({
    types: ['screen', 'window'],
    thumbnailSize: { width: 150, height: 150 }
  })

  for (const source of sources) {
    console.log(source.id, source.name)
    // source.thumbnail 是 NativeImage,可转 DataURL 显示
    const url = source.thumbnail.toDataURL()
  }
}

如果渲染进程需要这些数据,应通过 preload 暴露一个安全方法,由主进程调用 getSources 后回传结果,而不是把模块直接暴露给网页。

// 预加载脚本 preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('capturer', {
  listSources: () => ipcRenderer.invoke('list-sources')
})
// 主进程 main.js
const { ipcMain, desktopCapturer } = require('electron/main')

ipcMain.handle('list-sources', () => {
  return desktopCapturer.getSources({
    types: ['screen', 'window'],
    thumbnailSize: { width: 150, height: 150 }
  }).then((sources) =>
    sources.map((s) => ({
      id: s.id,
      name: s.name,
      thumb: s.thumbnail.toDataURL()
    }))
  )
})

1-3 在 WebRTC 中采集实时画面

要拿到可播放、可录制的实时视频流,推荐走 Web 标准的 navigator.mediaDevices.getDisplayMedia。为了让它在 Electron 里自动选用某个桌面源,需要在主进程注册 setDisplayMediaRequestHandler

该处理器在渲染进程请求显示媒体时被调用,你在里面用 desktopCapturer.getSources 选源,并通过 callback 把选中的源交还给浏览器媒体管线。

// 主进程 main.js
const { app, BrowserWindow, desktopCapturer, session } = require('electron/main')

app.whenReady().then(() => {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true,
      preload: './preload.js'
    }
  })

  session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
    desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
      // 这里直接授予第一个屏幕;真实场景应让用户选择
      callback({ video: sources[0], audio: 'loopback' })
    })
  }, { useSystemPicker: true })

  mainWindow.loadFile('index.html')
})
// 渲染进程 renderer.js
const video = document.querySelector('video')

document.getElementById('start').addEventListener('click', async () => {
  const stream = await navigator.mediaDevices.getDisplayMedia({
    audio: true,
    video: { width: 320, height: 240, frameRate: 30 }
  })
  video.srcObject = stream
  video.onloadedmetadata = () => video.play()
})
<!-- index.html -->
<button id="start">开始捕获</button>
<video width="320" height="240" autoplay></video>
<script src="renderer.js"></script>

注意 getDisplayMedia 不允许用 deviceId 指定源(这是 Web 规范限制),源的选择权实际在主进程的 setDisplayMediaRequestHandler 里。

1-4 缩略图与选择的界面

一个实用的做法:先用 getSources 拿到各源的缩略图,渲染成可点击的网格,用户点选后再开始 getDisplayMedia。这样既友好又符合权限预期。

// 渲染进程 renderer.js
const sources = await window.capturer.listSources()
const grid = document.getElementById('grid')
for (const s of sources) {
  const btn = document.createElement('button')
  btn.innerHTML = `<img src="${s.thumb}"><span>${s.name}</span>`
  btn.addEventListener('click', () => startCapture(s.id))
  grid.appendChild(btn)
}

这里再次体现 IPC 的价值:缩略图由主进程生成,页面只拿到无害的 DataURL 与 id,无法直接操作底层模块。

1-5 权限与平台限制

桌面捕获涉及隐私,各平台都有门槛,必须在打包与 Info.plist 层面做好准备。

macOS 10.15 Catalina 起,捕获屏幕需要用户在「系统设置—隐私与安全性—屏幕录制」中授权。可先用上一章的 systemPreferences.getMediaAccessStatus('screen') 预检状态。

macOS 14.2 Sonoma 起,捕获系统音频必须配置 NSAudioCaptureUsageDescription 这个 Info.plist 键;若从终端或 IDE 启动 Electron,则要求父程序也带该键,否则音频流会静默失败且不报错。

Windows 上 getSources 可用,但 screenwindow 类型的选取逻辑与系统版本相关。Linux 使用 PipeWire 时,getSources 通常只返回单一源,且屏幕与窗口会合并为窗口捕获。

1-6 录制与导出思路

拿到 MediaStream 后,可用 MediaRecorder 把它录成 WebM 等格式,再经 IPC 交给主进程写盘。这是纯前端能力,沙箱下也能工作。

// 渲染进程 renderer.js
let recorder
function startCapture(sourceId) {
  navigator.mediaDevices.getDisplayMedia({ video: true }).then((stream) => {
    recorder = new MediaRecorder(stream)
    recorder.ondataavailable = (e) => {
      // e.data 是 Blob,可经 IPC 发主进程保存
    }
    recorder.start()
  })
}

如果要录的是「应用内画面」而非桌面,也可考虑 contents.debuggerwebContents 的快照能力,但那是更进阶的话题,本教程点到为止。

1-7 采集参数与音频选项

getDisplayMedia 的约束对象决定了捕获质量。你可以指定 widthheightframeRatecursor 等,约束越精细,对性能与体积的影响越明显。一般的录屏 30fps、1080p 已足够清晰。

音频方面,macOS 14.2 起需要在 Info.plist 提供 NSAudioCaptureUsageDescription 才能捕获系统声音。若只想要麦克风而不抓系统声,可在约束里给 audiotrue,但同样受系统授权约束。

// 渲染进程 renderer.js
navigator.mediaDevices.getDisplayMedia({
  video: { frameRate: 30, width: 1920, height: 1080 },
  audio: false
}).then((stream) => {
  // 只录屏幕画面,不含声音
})

捕获到的流是标准 MediaStream,除了接 <video> 预览,也能喂给 MediaRecorder 生成录像文件,或推送到 WebRTC 对端做实时共享。无论哪种用途,源的选择与授权都先在系统层完成,应用层只需消费这条流。

关于「选哪个源」的体验,推荐在让用户选择之前先用 getSources 生成缩略图网格,并把选中结果缓存下来。当用户再次点击「开始共享」时,直接用上次选择的 idsetDisplayMediaRequestHandler 里匹配,省去重复弹窗。注意 useSystemPicker: true 是实验特性,系统选择器可用时会优先用它,否则仍走你的 callback,两种路径都要兼容。

若你的应用仅需在窗口内展示某个窗口的实时画面(而非录制),也可考虑用 BrowserWindowwebContents 捕获或 desktopCapturer 拿到的缩略图轮询刷新,但实时性与性能都不如 getDisplayMedia 的流方案,按场景权衡即可。

1-8 局限与注意事项

桌面捕获有几个常被忽略的边界。受数字版权保护(DRM)的窗口或视频内容通常无法被捕获,缩略图可能呈现为黑屏,这是系统层面而非代码层面的限制,需要在界面上提前说明。

thumbnailSize 越大,生成缩略图占用的内存越高。不需要预览时把宽或高设为 0 即可跳过缩略图生成,窗口很多时能省下可观开销。

拿到 MediaStream 后,停止共享要主动释放资源:调用 stream.getTracks().forEach((t) => t.stop()),否则摄像头或屏幕可能会被持续占用,下一次捕获也会受影响。

多显示器环境下 getSources 会返回每个屏幕的源,选择界面的 name 要清晰地告诉用户哪一个是哪一块屏,避免选错。

常见误区

不要把 desktopCapturer 当成录像机。它只负责列源和缩略图,真正的流来自 WebRTC,二者要配合使用。

不要忽视 macOS 的屏幕录制授权。未授权时 getSources 可能返回空或失败,应在 UI 里引导用户去系统设置开启。

不要以为 useSystemPicker: true 在所有平台都可用。它是实验特性,系统选择器不可用时仍会走你自己的 callback 逻辑,代码要为两种路径都做好准备。