在桌面应用中,获取屏幕画面或摄像头视频流是非常常见的需求,例如截图工具、录屏软件、视频会议客户端等。Electron 提供了两种主要途径来实现这些功能:基于 Chromium 的 Web API(如 getUserMedia 和 getDisplayMedia)以及 Electron 自身提供的原生桌面捕获模块(desktopCapturer)。前者适合摄像头调用和简单的屏幕共享,后者更适合高性能、可定制的屏幕截图和桌面采集。本节将分别介绍它们的用法和典型场景。
14.1.1 使用 desktopCapturer 获取屏幕源
Electron 的 desktopCapturer 模块可以枚举所有可用的桌面捕获源(整个屏幕或独立的应用程序窗口),并返回它们的缩略图和 ID,供后续的视频流获取或截图处理。这对于需要让用户选择具体窗口或屏幕进行录制的应用非常关键。
基本流程:
- 在主进程或渲染进程(需启用相应权限)中调用
desktopCapturer.getSources({ types: ['screen', 'window'] })。 - 返回的
sources数组中的每个对象包含id、name、thumbnail(原生图像的NativeImage对象)和显示尺寸等信息。 - 可以利用这些信息在 UI 中展示一个选择面板,然后根据用户选择的
source.id,调用navigator.mediaDevices.getUserMedia并传入约束{ audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: sourceId } } }来获取该源的视频流。 - 拿到视频流后,可以将其赋值给
<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> 元素上,然后在 requestVideoFrameCallback 或 setInterval 中将当前帧绘制到 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。
主进程核心代码:
- 打开选择窗口:创建一个无边框的覆盖窗口,列出所有捕获源的缩略图(通过 IPC 获取)。
- 当用户点击某个缩略图,将源 ID 发送回主进程。
- 主进程创建一个新窗口加载预览页面,并通过
webContents.send将源 ID 传递过去。 - 预览页使用
getUserMedia获取视频流并播放,截图按钮触发 Canvas 截图并保存(通过 IPC 调主进程的dialog.showSaveDialog和fs.writeFile)。
关键安全点:
所有对 desktopCapturer 的调用都在主进程,渲染进程通过预加载暴露的有限接口进行通信,权限边界清晰。
常用 API 总结表格:
| 功能 | API | 适用场景 |
|------|-----|----------|
| 获取屏幕源列表 | desktopCapturer.getSources() | 让用户选择录屏/截屏目标 |
| 窗口截图 | win.capturePage() | 快速截取应用自身窗口内容 |
| 基于视频流的灵活截图 | getUserMedia + Canvas | 截图整个屏幕或任意窗口的指定区域 |
| 摄像头流 | navigator.mediaDevices.getUserMedia() | 视频通话、拍照、视觉识别 |
| 保存图片 | nativeImage.toPNG/toJPEG + fs.writeFile | 保存截图到本地 |
| 系统权限检测 | systemPreferences.getMediaAccessStatus() | 检查摄像头/麦克风权限状态(macOS) |
通过灵活组合这些 API,你可以在 Electron 中构建出功能强大的图像和视频捕获工具,而且大部分逻辑都可以复用 Web 前端积累的经验和库。唯一的难点通常在于性能优化(例如高分辨率下的截图延迟)和平台差异处理,但社区已有大量成熟解决方案供参考。