Electron集成Angular SPA实现桌面录屏功能异常问题咨询
Electron+Angular桌面录屏功能异常排查修复
你的代码一共存在4处核心问题,逐一修复即可正常运行:
- 第一处:主进程存在语法错误+源匹配逻辑写死
你注释提到代码执行到console.log(sources)就中断,本质是下一行判断source.name === ‘Entire ’Screen存在引号嵌套错误,左引号包裹"Entire "后直接写了右引号,后面的Screen成了游离语法token,主进程运行到这里直接抛语法异常,根本走不到后续发送事件的逻辑。另外你写死匹配"Entire Screen"的逻辑兼容性极差:Windows系统全屏源名称为"屏幕 1"、macOS为"Entire Screen"、Linux多为"Display X",多显示器场景还会带设备编号,写死名称大概率匹配不到目标源。 - 第二处:渲染层获取媒体流时传了空配置
你当前写的navigator.mediaDevices.getUserMedia({})没有关联任何桌面捕获源,只会默认请求摄像头权限,完全拿不到桌面流,这是功能失效的核心原因。 - 第三处:录屏逻辑存在时序问题和资源泄漏
你在setInterval里反复新建MediaRecorder实例,既没有提前绑定事件导致开头的视频数据容易丢失,也没有释放之前的流资源,多个录制实例同时占用流会导致最终生成的Blob为空或者损坏。 - 第四处:TS不识别
mandatory属性是类型兼容问题
桌面捕获所需的mandatory配置是Electron对标准媒体约束的私有扩展,不在TypeScript内置的DOM类型定义里,所以会报类型错误,不需要额外安装依赖或者修改配置,做简单类型断言即可正常编译。
修正后主进程代码
const { ipcMain, desktopCapturer } = require('electron'); ipcMain.on('desktop-capture', async () => { const sources = await desktopCapturer.getSources({ types: ['window', 'screen'], fetchWindowIcons: true }); // 过滤出所有屏幕类源回传,不要在主进程写死名称匹配 const screenSources = sources.filter(source => source.display_id !== ''); mainWindow.webContents.send('desktop-capture-res', screenSources); });
多窗口、多显示器场景建议直接把所有捕获源回传给渲染层,弹出选择框让用户自主选择录制目标,兼容性更好
修正后Angular侧初始化逻辑
// 组件内新增属性存储流和定时器,方便后续销毁释放 private desktopStream: MediaStream | null = null; private recordTimer: NodeJS.Timeout | null = null; initializeDesktop() { this.ipc.send('desktop-capture'); // 先移除重复监听,避免多次初始化导致事件重复触发 this.ipc?.removeAllListeners('desktop-capture-res'); this.ipc?.on('desktop-capture-res', async (event, sources: Electron.DesktopCapturerSource[]) => { // 单屏场景默认取第一个屏幕源,多屏场景可替换为用户选择逻辑 const targetSource = sources[0]; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: targetSource.id, minWidth: 1280, maxWidth: 1920, minHeight: 720, maxHeight: 1080 } } as MediaTrackConstraints // 类型断言解决TS识别不到mandatory的问题 }); this.desktopStream = stream; this.startRecordLoop(stream); } catch (err) { console.error('获取桌面流失败:', err); } }) };
修正后录屏流处理逻辑
private mediaRecorder: MediaRecorder | null = null; private recordedChunks: Blob[] = []; startRecordLoop(stream: MediaStream) { // 清除之前可能存在的录制定时器 if (this.recordTimer) clearInterval(this.recordTimer); const recordOnce = () => { this.recordedChunks = []; this.mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' }); // 先绑定事件再启动录制,避免丢失开头数据 this.mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) this.recordedChunks.push(e.data); }; this.mediaRecorder.onstop = () => { const blob = new Blob(this.recordedChunks, { type: 'video/webm' }); this.videos.push(blob); console.log('生成录屏blob:', blob); }; this.mediaRecorder.start(); // 到达录制时长后停止当前录制,自动触发下一轮 setTimeout(() => { if (this.mediaRecorder?.state === 'recording') { this.mediaRecorder.stop(); } }, API_SEND_TIMEOUT); }; // 启动第一轮录制,之后按配置间隔循环 recordOnce(); this.recordTimer = setInterval(recordOnce, API_SEND_TIMEOUT); } // 组件销毁时释放所有资源,避免内存泄漏 ngOnDestroy() { if (this.recordTimer) clearInterval(this.recordTimer); if (this.mediaRecorder?.state === 'recording') this.mediaRecorder.stop(); if (this.desktopStream) this.desktopStream.getTracks().forEach(track => track.stop()); this.ipc?.removeAllListeners('desktop-capture-res'); }
额外注意点
- 创建BrowserWindow时如果开启了沙箱或严格的上下文隔离,需要确认渲染进程有访问
navigator.mediaDevices的权限 - 如果需要录制系统音频,除了配置audio参数,还要在
mandatory字段里补充音频相关的Electron扩展配置,不能直接传audio: true - 生成Blob时必须指定正确的MIME类型,否则后续播放、上传时可能出现格式不识别问题
内容的提问来源于stack exchange,提问作者Giulia
相关产品推荐
相关产品推荐

