人人都会AI编程

14.1 屏幕截图、桌面捕获、摄像头调用

更新时间:2026-07-11

在桌面应用中,获取屏幕画面或摄像头视频流是非常常见的需求,例如截图工具、录屏软件、视频会议客户端等。Electron 提供了两种主要途径来实现这些功能:基于 Chromium 的 Web API(如 getUserMediagetDisplayMedia)以及 Electron 自身提供的原生桌面捕获模块(desktopCapturer)。前者适合摄像头调用和简单的屏幕共享,后者更适合高性能、可定制的屏幕截图和桌面采集。本节将分别介绍它们的用法和典型场景。

14.1.1 使用 desktopCapturer 获取屏幕源

Electron 的 desktopCapturer 模块可以枚举所有可用的桌面捕获源(整个屏幕或独立的应用程序窗口),并返回它们的缩略图和 ID,供后续的视频流获取或截图处理。这对于需要让用户选择具体窗口或屏幕进行录制的应用非常关键。

基本流程:

  1. 在主进程或渲染进程(需启用相应权限)中调用 desktopCapturer.getSources({ types: ['screen', 'window'] })
  2. 返回的 sources 数组中的每个对象包含 idnamethumbnail(原生图像的 NativeImage 对象)和显示尺寸等信息。
  3. 可以利用这些信息在 UI 中展示一个选择面板,然后根据用户选择的 source.id,调用 navigator.mediaDevices.getUserMedia 并传入约束 { audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: sourceId } } } 来获取该源的视频流。
  4. 拿到视频流后,可以将其赋值给 <video> 元素进行预览,或者利用 Canvas 进行逐帧截图、录制等。

安全注意事项:
由于直接访问屏幕内容具有隐私风险,desktopCapturer 只能在主进程中被调用(或通过 remote 模块,但 remote 已不推荐)。现代安全实践是通过预加载脚本暴露一个安全的 IPC 接口,由渲染进程发送请求,主进程调用 desktopCapturer 并返回序列化后的数据。

代码示例:
预加载脚本中暴露获取源的方法:

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  getDesktopSources: () => ipcRenderer.invoke('get-desktop-sources')
});

主进程中处理请求:

// main.js
const { ipcMain, desktopCapturer } = require('electron');

ipcMain.handle('get-desktop-sources', async () => {
  const sources = await desktopCapturer.getSources({ types: ['window', 'screen'] });
  // 将 sources 转成可序列化的对象(thumbnail 可转为 dataURL)
  return sources.map(source => ({
    id: source.id,
    name: source.name,
    thumbnail: source.thumbnail.toDataURL()
  }));
});

渲染进程中调用并展示选择弹窗:

// renderer.js
const sources = await window.electronAPI.getDesktopSources();
// 用这些缩略图绘制一个选择界面,当用户点击某个源时,记录其 source.id 并开始捕获

开始捕获时,渲染进程可以使用标准 Web API:

const stream = await navigator.mediaDevices.getUserMedia({
  audio: false,
  video: {
    mandatory: {
      chromeMediaSource: 'desktop',
      chromeMediaSourceId: selectedSourceId
    }
  }
});
videoElement.srcObject = stream;

14.1.2 屏幕截图

很多时候我们只需要一张静态的屏幕截图,而不需要一个持久的视频流。Electron 提供了几种快速截图的方式。

方法一:使用 BrowserWindow.capturePage()
该方法可以直接捕获某个窗口或整个桌面的截图,返回一个 NativeImage 对象,可以立即保存或显示。它不需要任何用户权限交互,适合内部工具或自动截图。

// 主进程代码:截取主窗口当前内容
const win = BrowserWindow.getFocusedWindow();
const image = await win.capturePage();
// 保存为 PNG 文件
const buffer = image.toPNG();
fs.writeFileSync('screenshot.png', buffer);

捕获特定区域或整个屏幕
capturePage 默认捕获整个窗口的可见区域(不包含透明部分和窗口外内容)。如果要捕获整个屏幕,可以创建一个全屏透明窗口并调用其 capturePage,或者结合 desktopCapturer 和 Canvas 进行自定义区域截图。

方法二:结合 desktopCapturer 和 Canvas 精确截图
这种方式可以截取任意屏幕源的任意区域(矩形)。流程是:先获取屏幕源的视频流,将其绘制到一个隐藏的 <video> 元素,然后将视频帧绘制到 Canvas 上,通过 Canvas 的 drawImage 截取指定区域,最后导出为图片。

// 获取屏幕流
const stream = await navigator.mediaDevices.getUserMedia({
  audio: false,
  video: {
    mandatory: {
      chromeMediaSource: 'desktop',
      chromeMediaSourceId: sourceId,
      // 可以指定捕获的尺寸和帧率(但截图只取单帧)
      maxWidth: 1920,
      maxHeight: 1080
    }
  }
});

const video = document.createElement('video');
video.srcObject = stream;
await video.play();

// 绘制到 Canvas 并截取矩形区域
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

// 截取区域 (x, y, width, height)
const regionCanvas = document.createElement('canvas');
regionCanvas.width = rectWidth;
regionCanvas.height = rectHeight;
const regionCtx = regionCanvas.getContext('2d');
regionCtx.drawImage(canvas, rectX, rectY, rectWidth, rectHeight, 0, 0, rectWidth, rectHeight);

// 导出为 Blob 或 Data URL
const dataURL = regionCanvas.toDataURL('image/png');

方法三:使用第三方包 screenshot-desktop
如果不想自己处理视频流,社区有成熟的包可以调用系统 API 进行截图,返回的是 Buffer。但要注意,这类包利用了原生截图接口(如 Windows 的 dxgkrnl 或系统的截图命令),而非直接使用 Chromium,因此性能更好且可以截取到 Electron 窗口外的内容。使用时需要额外安装原生模块。

14.1.3 摄像头调用

摄像头调用完全依赖 WebRTC 的 getUserMedia API,和浏览器中的使用方式一模一样。与其他桌面框架相比,Electron 的优势在于可以直接使用 Web 生态中大量的图像处理库(如 face-api.js、tensorflow.js)对视频流进行实时分析。

基本示例:

// 渲染进程代码
const stream = await navigator.mediaDevices.getUserMedia({
  video: {
    width: { ideal: 1280 },
    height: { ideal: 720 },
    frameRate: { ideal: 30 }
  },
  audio: false // 也可以启用音频
});
videoElement.srcObject = stream;

权限处理:
桌面应用和浏览器不同,Electron 不会显示浏览器的权限请求条,而是直接询问用户(通常是通过系统级的权限对话框)。在 macOS 上,应用必须在 Info.plist 中声明 NSCameraUsageDescription 才能访问摄像头;Windows 和 Linux 通常无需额外配置,但系统隐私设置可能会影响访问。首次调用 getUserMedia 时,Electron 会触发系统权限提示,用户许可后即可正常使用。

Electron 还提供了 systemPreferences.askForMediaAccess('camera')(仅 macOS)来显式请求权限,方便在应用启动时预先授权。

实用技巧 - 静默捕获与帧操作:
如果不需要在页面上显示视频,但仍然需要处理每一帧(例如用作虚拟背景或人脸检测),可以将视频流连接到一个未挂载到 DOM 的 <video> 元素上,然后在 requestVideoFrameCallbacksetInterval 中将当前帧绘制到 Canvas 进行处理。这样既能利用硬件解码,又不影响 UI。

摄像头选择与切换:
通过 navigator.mediaDevices.enumerateDevices() 可以列出所有摄像头设备,筛选出 kind === 'videoinput' 的条目,然后使用对应的 deviceId 作为约束重新获取流,实现切换。

const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');

// 选择第一个外接摄像头(通常内置摄像头标记为 'default' 或名称中包含 'built-in')
const selectedCamera = cameras.find(c => !c.label.includes('Virtual')) || cameras[0];
const stream = await navigator.mediaDevices.getUserMedia({
  video: { deviceId: { exact: selectedCamera.deviceId } }
});

注意事项:

  • 在 Windows 上,某些虚拟摄像头(如 OBS Virtual Camera)在 Electron 中可能无法直接枚举到,需要在启动应用时添加 --enable-features=WebRTC-H264WithOpenH264FFmpeg 等标志,或确保系统安装了正确的编解码器。
  • 当页面隐藏或窗口最小化时,浏览器可能会暂停视频轨道以节省资源。可通过监听 visibilitychange 事件,在页面恢复时重新获取流,或者使用 track.enabled = false 临时禁用轨道而不释放设备。

14.1.4 实战组合:一个简单的截图工具

下面我们组合上述技术,实现一个最小化的截图应用:点击按钮,弹出屏幕源选择窗口,用户点击某个屏幕/窗口后,在新窗口中显示该源的实时预览,并提供“截图”按钮保存当前帧为 PNG。

主进程核心代码:

  1. 打开选择窗口:创建一个无边框的覆盖窗口,列出所有捕获源的缩略图(通过 IPC 获取)。
  2. 当用户点击某个缩略图,将源 ID 发送回主进程。
  3. 主进程创建一个新窗口加载预览页面,并通过 webContents.send 将源 ID 传递过去。
  4. 预览页使用 getUserMedia 获取视频流并播放,截图按钮触发 Canvas 截图并保存(通过 IPC 调主进程的 dialog.showSaveDialogfs.writeFile)。

关键安全点:
所有对 desktopCapturer 的调用都在主进程,渲染进程通过预加载暴露的有限接口进行通信,权限边界清晰。

常用 API 总结表格:

| 功能 | API | 适用场景 |
|------|-----|----------|
| 获取屏幕源列表 | desktopCapturer.getSources() | 让用户选择录屏/截屏目标 |
| 窗口截图 | win.capturePage() | 快速截取应用自身窗口内容 |
| 基于视频流的灵活截图 | getUserMedia + Canvas | 截图整个屏幕或任意窗口的指定区域 |
| 摄像头流 | navigator.mediaDevices.getUserMedia() | 视频通话、拍照、视觉识别 |
| 保存图片 | nativeImage.toPNG/toJPEG + fs.writeFile | 保存截图到本地 |
| 系统权限检测 | systemPreferences.getMediaAccessStatus() | 检查摄像头/麦克风权限状态(macOS) |

通过灵活组合这些 API,你可以在 Electron 中构建出功能强大的图像和视频捕获工具,而且大部分逻辑都可以复用 Web 前端积累的经验和库。唯一的难点通常在于性能优化(例如高分辨率下的截图延迟)和平台差异处理,但社区已有大量成熟解决方案供参考。