You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:06:23