编程 OpenScreen 深度解析:当开源撕掉屏幕录制的高价标签,开发者终于不用再被 Screen Studio 收割

2026-04-10 02:23:58 +0800 CST views 3

OpenScreen 深度解析:当开源撕掉屏幕录制的高价标签,开发者终于不用再被 Screen Studio 收割

背景介绍:当「录制」变成一种奢侈消费

做技术内容创作的同学,大概都绕不开一个痛点——录屏。

你想录一段代码演示给团队新人,想录一个产品功能介绍放到官网,想录一段技术分享发到 B 站,或者只是想把自己解决问题的过程记录下来以后复盘——这些场景太常见了,需求太普遍了。

但当你真的去找工具的时候,你会发现一个诡异的现象:「录屏」这件事,在 2026 年的今天,居然还是个付费生意

最典型的例子就是 Screen Studio。这个 macOS 上的明星录屏工具,定价 $29/月 或者 $89 买断。光看这个价格你可能会想:它凭什么?

凭的是它能做到的效果:自动缩放(录制时光标走到哪里,镜头就自动跟到哪里)、运动模糊(光标移动时带有自然的拖影效果)、专业的背景虚化、以及导出即用的高质量视频。确实,比 macOS 自带的 QuickTime 或者 OBS Studio 强了不止一个档次。

问题是:$89 买断的价格,对于一个开源社区来说,完全可以做出来

然后 Siddharth Vaddem 站出来了。

2026 年初,他开源了一个项目叫 OpenScreen,定位就是 Screen Studio 的免费开源替代品。项目上线 GitHub 后迅速走红,目前已经积累 26,625+ Stars,本周新增超过 13,938 Stars,稳居 GitHub Trending 前列。

这就是今天这篇文章的主角。


核心概念:OpenScreen 是什么

项目定位

OpenScreen 的官方定义非常清晰:

一款免费的屏幕录制与演示视频创作工具,是付费软件 Screen Studio 的开源替代品。完全免费、无订阅、无水印、可商用。

技术栈:Electron + React + TypeScript

这意味着它是一个跨平台的桌面应用,基于 Web 技术构建,分发到 macOS、Windows 和 Linux 上运行。

目标用户

OpenScreen 的目标用户非常明确:开发者和技术内容创作者

不是给影视制作人用的,不是给游戏主播用的。它的核心场景是:

  • 录制代码演示和技术讲解视频
  • 制作产品功能介绍和教程
  • 录制 Bug 修复过程用于知识沉淀
  • 为开源项目制作演示视频

这些场景有一个共同特点:录制的画面主要是屏幕内容(代码、UI),偶尔有摄像头画中画,对自动缩放和光标美化有强需求,但不需要 Premiere 级别的专业剪辑能力。

与竞品的核心差异

我们先来看一张对比表,直观看差异:

维度OpenScreenScreen StudioOBS StudioCamtasia
价格免费开源$89 买断 / $29 月免费开源$249.5 买断
自动缩放
运动模糊
背景虚化
开箱即用✅ 开箱即用✅ 开箱即用❌ 需配置✅ 开箱即用
技术栈Electron原生 macOSC/C++原生
跨平台✅ 全平台❌ 仅 macOS✅ 全平台✅ 全平台

最直接的差异化:Screen Studio 能做到的效果,OpenScreen 全部有,而且免费,而且跨平台


架构分析:Electron 桌面应用的技术选型逻辑

为什么是 Electron?

这个问题很关键。用原生开发(Swift/Kotlin)当然也能做录屏,但成本高、维护难、跨平台要写两套代码。

用 Electron 做录屏,有几个天然优势:

1. 屏幕捕获 API 是 Web API

Electron 基于 Chromium,而 Chromium 内置了 desktopCapturer API,这是 Chromium 团队为 WebRTC 场景开发的屏幕捕获实现,封装成了可以直接调用的 JavaScript API:

// Electron 中获取可录制的窗口和屏幕
const { desktopCapturer } = require('electron');

async function getScreenSources() {
  const sources = await desktopCapturer.getSources({
    types: ['window', 'screen'],
    thumbnailSize: { width: 320, height: 180 }
  });
  
  return sources.map(source => ({
    id: source.id,
    name: source.name,
    thumbnail: source.thumbnail.toDataURL()
  }));
}

这套 API 在 Electron 12+ 版本中已经相当成熟,直接拿到屏幕画面帧数据,不需要额外调用系统底层 API。

2. FFmpeg 是最好的视频处理库,而 FFmpeg 有成熟的 Node.js 绑定

录屏拿到的是原始视频帧,要变成可以导出的 MP4/GIF,需要编码和封装。FFmpeg 是这个领域当之无愧的王者,而 Node.js 生态中有 fluent-ffmpeg@ffmpeg/ffmpeg 等成熟绑定,Electron 可以无缝集成。

// 使用 fluent-ffmpeg 进行视频编码
const ffmpeg = require('fluent-ffmpeg');

function encodeVideo(inputPath, outputPath, options = {}) {
  return new Promise((resolve, reject) => {
    ffmpeg(inputPath)
      .outputOptions([
        '-c:v libx264',           // H.264 编码
        '-preset medium',         // 编码速度/质量平衡
        '-crf 23',               // 质量参数 (0-51, 越小越好)
        '-pix_fmt yuv420p',       // 兼容所有播放器
        `-vf scale=${options.width || 1920}:${options.height || 1080}`
      ])
      .audioCodec('aac')
      .audioBitrate('128k')
      .output(outputPath)
      .on('end', resolve)
      .on('error', reject)
      .run();
  });
}

3. React 负责 UI,TypeScript 确保代码质量

录屏应用的 UI 相对固定(录制控制栏、时间显示、预览窗口、导出设置),用 React 做状态管理和组件化非常合适。TypeScript 的静态类型检查在 Electron 的 IPC 通信中尤为重要——主进程和渲染进程之间的数据传递,如果类型错了,轻则功能异常,重则崩溃。

OpenScreen 的架构分层

从 GitHub 上的源码结构来看,OpenScreen 采用了典型的 Electron 三层架构:

┌─────────────────────────────────────────────────────────┐
│                    Renderer Process                      │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────┐ │
│  │   React UI  │  │ State Manager │  │ Video Preview  │ │
│  │  (Controls) │  │  (Recording)  │  │   (Canvas)     │ │
│  └─────────────┘  └──────────────┘  └────────────────┘ │
│                         │ IPC                            │
├─────────────────────────┼───────────────────────────────┤
│                    Main Process                          │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────┐ │
│  │  Capture    │  │   FFmpeg     │  │  File System   │ │
│  │  Manager    │  │   Encoder    │  │   Manager      │ │
│  └─────────────┘  └──────────────┘  └────────────────┘ │
└─────────────────────────────────────────────────────────┘
  • 捕获层(Capture Manager):调用 desktopCapturer 获取屏幕/窗口帧数据,管理录制生命周期
  • 编码层(FFmpeg Encoder):接收原始帧序列,执行 H.264/AAC 编码和 MP4 封装
  • 文件管理层(File System Manager):管理临时文件和最终导出,处理文件系统权限

代码实战:从零理解屏幕录制核心流程

第一步:获取可录制的屏幕源

在 Electron 中,录制之前要先问系统:「有哪些窗口和屏幕可以录?」这通过 desktopCapturer.getSources() 实现:

// main-process/captureManager.js
const { desktopCapturer } = require('electron');

class CaptureManager {
  constructor() {
    this.currentStream = null;
    this.recording = false;
  }

  // 获取所有可用的录制源
  async getAvailableSources() {
    const sources = await desktopCapturer.getSources({
      types: ['window', 'screen'],  // 同时获取窗口和屏幕
      thumbnailSize: { width: 320, height: 180 },
      fetchWindowIcons: true
    });

    return sources.map(source => ({
      id: source.id,
      name: source.name,
      thumbnail: source.thumbnail.toDataURL(),
      isScreen: source.id.startsWith('screen:'),
      isWindow: source.id.startsWith('window:')
    }));
  }

  // 选择一个源开始预览
  async startPreview(sourceId) {
    // 获取该源的媒体流约束
    const constraints = {
      audio: false,  // 预览时不需要音频
      video: {
        // sourceId 格式: "window:12345:0" 或 "screen:1:0"
        mandatory: {
          chromeMediaSource: sourceId.split(':')[0],
          chromeMediaSourceId: sourceId
        }
      }
    };

    try {
      this.currentStream = await navigator.mediaDevices.getUserMedia(constraints);
      return this.currentStream;
    } catch (err) {
      console.error('获取媒体流失败:', err);
      throw err;
    }
  }
}

module.exports = new CaptureManager();

第二步:实现带音频的录制

录制时需要同时捕获屏幕画面和系统音频(或者麦克风声音)。这里用到的是 MediaRecorder API:

// renderer-process/recorder.js

class ScreenRecorder {
  constructor() {
    this.mediaRecorder = null;
    this.recordedChunks = [];
    this.audioContext = null;
  }

  // 组合屏幕视频流 + 音频流
  async setupStream(screenStream, audioSource = 'system') {
    // audioSource: 'system' = 系统音频, 'microphone' = 麦克风, 'both' = 两者
    const audioConstraints = { audio: audioSource === 'system' ? true : 'microphone' };

    let audioStream;
    if (audioSource !== 'none') {
      try {
        // macOS: 需要在 Electron 中配置音频捕获权限
        audioStream = await navigator.mediaDevices.getUserMedia(audioConstraints);
      } catch (e) {
        console.warn('音频捕获失败,继续仅录屏:', e);
      }
    }

    // 使用 canvas 合并视频和音频轨道
    if (audioStream) {
      const combinedStream = new MediaStream();
      
      // 添加视频轨道
      screenStream.getVideoTracks().forEach(track => {
        combinedStream.addTrack(track);
      });
      
      // 添加音频轨道
      audioStream.getAudioTracks().forEach(track => {
        combinedStream.addTrack(track);
      });
      
      return combinedStream;
    }

    return screenStream;
  }

  // 开始录制
  startRecording(stream, options = {}) {
    this.recordedChunks = [];
    
    // 视频编码选项
    const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
      ? 'video/webm;codecs=vp9'
      : 'video/webm;codecs=vp8';

    this.mediaRecorder = new MediaRecorder(stream, {
      mimeType,
      videoBitsPerSecond: options.videoQuality === 'high' ? 8000000 : 4000000,
      audioBitsPerSecond: 128000
    });

    this.mediaRecorder.ondataavailable = (event) => {
      if (event.data && event.data.size > 0) {
        this.recordedChunks.push(event.data);
      }
    };

    this.mediaRecorder.start(1000); // 每 1 秒生成一个数据块
    console.log('录制已开始');
  }

  // 停止录制并返回 Blob
  async stopRecording() {
    return new Promise((resolve) => {
      this.mediaRecorder.onstop = () => {
        const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
        resolve(blob);
      };

      this.mediaRecorder.stop();
    });
  }
}

module.exports = ScreenRecorder;

第三步:实现自动缩放(Auto-Zoom)

自动缩放是 OpenScreen 和 Screen Studio 的核心差异点。它的原理是:追踪光标位置,动态调整 Canvas 视图的缩放比例和中心点

// renderer-process/autoZoom.js

class AutoZoom {
  constructor(canvas, videoElement) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.video = videoElement;
    
    this.zoom = 1.0;           // 当前缩放级别
    this.targetX = 0;           // 目标中心 X
    this.targetY = 0;           // 目标中心 Y
    this.smoothing = 0.1;       // 平滑系数(越小越平滑)
    
    this.enabled = true;
    this.zoomOnActivity = true; // 移动时触发缩放
    this.minZoom = 1.0;
    this.maxZoom = 2.5;
    this.zoomRadius = 150;      // 光标周围的有效缩放区域(像素)
    
    // 鼠标追踪
    document.addEventListener('mousemove', (e) => this.onMouseMove(e));
    
    // 开始渲染循环
    this.renderLoop();
  }

  onMouseMove(event) {
    if (!this.enabled || !this.zoomOnActivity) return;
    
    const rect = this.canvas.getBoundingClientRect();
    
    // 计算光标相对于 Canvas 的位置
    const mouseX = (event.clientX - rect.left) * (this.canvas.width / rect.width);
    const mouseY = (event.clientY - rect.top) * (this.canvas.height / rect.height);
    
    // 根据光标位置计算需要的缩放
    // 光标越靠近边缘,缩放越大
    const centerX = this.canvas.width / 2;
    const centerY = this.canvas.height / 2;
    const distFromCenter = Math.sqrt(
      Math.pow(mouseX - centerX, 2) + 
      Math.pow(mouseY - centerY, 2)
    );
    
    // 将距离映射为缩放值
    const maxDist = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
    const normalizedDist = distFromCenter / maxDist;
    
    // 距离中心越远 → 缩放越大
    const targetZoom = this.minZoom + (this.maxZoom - this.minZoom) * Math.pow(normalizedDist, 2);
    
    // 平滑过渡到目标缩放
    this.zoom = this.zoom + (targetZoom - this.zoom) * this.smoothing;
    
    // 目标中心点向光标方向偏移
    this.targetX = mouseX;
    this.targetY = mouseY;
  }

  renderLoop() {
    const render = () => {
      if (!this.enabled) {
        requestAnimationFrame(render);
        return;
      }

      const w = this.canvas.width;
      const h = this.canvas.height;
      
      // 清空画布
      this.ctx.clearRect(0, 0, w, h);
      
      // 保存状态
      this.ctx.save();
      
      // 计算裁剪区域(以 targetX, targetY 为中心)
      const cropW = w / this.zoom;
      const cropH = h / this.zoom;
      const cropX = this.targetX - cropW / 2;
      const cropY = this.targetY - cropH / 2;
      
      // 绘制缩放后的视频帧
      this.ctx.drawImage(
        this.video,
        cropX, cropY, cropW, cropH,  // 源区域
        0, 0, w, h                     // 目标区域
      );
      
      // 恢复状态
      this.ctx.restore();
      
      requestAnimationFrame(render);
    };
    
    render();
  }

  // 切换自动缩放开关
  toggle() {
    this.enabled = !this.enabled;
    return this.enabled;
  }
}

module.exports = AutoZoom;

这段代码的核心逻辑:

  1. 监听鼠标移动事件,获取光标在 Canvas 上的相对坐标
  2. 计算光标距离 Canvas 中心的距离
  3. 距离越远 → 缩放值越大(最大 2.5 倍)
  4. 使用 smoothing 系数做平滑插值,避免画面抖动

第四步:运动模糊效果

运动模糊(Motion Blur)是在光标移动时给轨迹添加渐变拖影,让画面看起来更自然。实现思路是:每帧不完全清除画布,而是用半透明黑色覆盖,造成拖影效果

// renderer-process/motionBlur.js

class MotionBlur {
  constructor(ctx) {
    this.ctx = ctx;
    this.trail = [];              // 存储历史鼠标位置
    this.maxTrailLength = 10;      // 拖影长度
    this.opacity = 0.3;           // 拖影透明度
  }

  // 记录新的光标位置
  addPosition(x, y) {
    this.trail.push({ x, y, timestamp: Date.now() });
    
    // 限制历史长度
    if (this.trail.length > this.maxTrailLength) {
      this.trail.shift();
    }
  }

  // 渲染运动模糊(覆盖在每帧画面之上)
  render(canvasWidth, canvasHeight) {
    if (this.trail.length < 2) return;
    
    const ctx = this.ctx;
    
    // 从旧到新绘制拖影
    for (let i = 0; i < this.trail.length - 1; i++) {
      const point = this.trail[i];
      const nextPoint = this.trail[i + 1];
      
      // 越新的位置透明度越高
      const progress = i / this.trail.length;
      const alpha = this.opacity * (1 - progress);
      
      // 绘制从 point 到 nextPoint 的渐变线
      const gradient = ctx.createLinearGradient(
        point.x, point.y,
        nextPoint.x, nextPoint.y
      );
      
      gradient.addColorStop(0, `rgba(0, 0, 0, ${alpha * 0.3})`);
      gradient.addColorStop(1, `rgba(0, 0, 0, ${alpha})`);
      
      ctx.beginPath();
      ctx.strokeStyle = gradient;
      ctx.lineWidth = 4 * (1 - progress * 0.5);  // 越远越细
      ctx.lineCap = 'round';
      ctx.moveTo(point.x, point.y);
      ctx.lineTo(nextPoint.x, nextPoint.y);
      ctx.stroke();
    }
    
    // 绘制当前光标位置的光晕
    const current = this.trail[this.trail.length - 1];
    const gradient = ctx.createRadialGradient(
      current.x, current.y, 0,
      current.x, current.y, 15
    );
    gradient.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
    gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.3)');
    gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
    
    ctx.beginPath();
    ctx.fillStyle = gradient;
    ctx.arc(current.x, current.y, 15, 0, Math.PI * 2);
    ctx.fill();
  }

  // 清除历史(开始新录制时调用)
  clear() {
    this.trail = [];
  }
}

module.exports = MotionBlur;

第五步:FFmpeg 后处理导出

录屏完成后,浏览器原生的 WebM 格式虽然可以直接播放,但为了更好的兼容性,需要用 FFmpeg 转码为 MP4,并且加上水印、裁剪等后处理:

// main-process/ffmpegProcessor.js
const ffmpeg = require('fluent-ffmpeg');
const path = require('path');

class VideoProcessor {
  constructor() {
    // 设置 FFmpeg 路径(Electron 打包后需要指定)
    ffmpeg.setFfmpegPath('/usr/local/bin/ffmpeg'); // macOS 默认路径
  }

  // 将 WebM 转换为 MP4
  async convertToMP4(inputPath, outputPath) {
    return new Promise((resolve, reject) => {
      ffmpeg(inputPath)
        .outputOptions([
          '-c:v libx264',           // H.264 视频编码
          '-preset fast',           // 快速编码(速度优先)
          '-crf 20',                // 高质量 (0-51, 越小越好)
          '-c:a aac',               // AAC 音频编码
          '-b:a 192k',              // 192kbps 音频
          '-movflags +faststart',   // 优化 Web 播放
          '-pix_fmt yuv420p'        // 确保兼容性
        ])
        .output(outputPath)
        .on('progress', (progress) => {
          console.log(`处理进度: ${progress.percent?.toFixed(1)}%`);
        })
        .on('end', () => {
          console.log('转码完成:', outputPath);
          resolve(outputPath);
        })
        .on('error', (err) => {
          console.error('转码失败:', err);
          reject(err);
        })
        .run();
    });
  }

  // 添加自动缩放效果(使用 FFmpeg zoompan 滤镜)
  async applyAutoZoomEffect(inputPath, outputPath, zoomPath) {
    // zoomPath 是 JSON 配置文件,记录了每帧的缩放参数
    return new Promise((resolve, reject) => {
      const zoomConfig = require(zoomPath);
      
      // 构建 zoompan 滤镜参数
      // 格式: zoompan=z='min(zoom+0.001,2)':d=1:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)'
      // 这里用 zoomConfig 动态生成每帧的参数
      
      ffmpeg(inputPath)
        .videoFilters(`zoompan=z='min(zoom+0.002,2.5)':d=1:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=${1920}x${1080}`)
        .outputOptions([
          '-c:v libx264',
          '-preset fast',
          '-crf 20',
          '-pix_fmt yuv420p'
        ])
        .output(outputPath)
        .on('end', () => resolve(outputPath))
        .on('error', (err) => reject(err))
        .run();
    });
  }

  // 导出为 GIF(用于社交媒体)
  async exportToGIF(inputPath, outputPath, options = {}) {
    const width = options.width || 480;
    const fps = options.fps || 10;
    
    return new Promise((resolve, reject) => {
      ffmpeg(inputPath)
        .outputOptions([
          `-vf scale=${width}:-1:flags=lanczos,fps=${fps}`
        ])
        .format('gif')
        .output(outputPath)
        .on('end', () => resolve(outputPath))
        .on('error', (err) => reject(err))
        .run();
    });
  }
}

module.exports = new VideoProcessor();

性能优化:Electron 录屏应用的优化策略

1. 录制帧率的智能选择

不是所有场景都需要 60fps。对于代码演示类内容,30fps 足够,但可以省一半的存储和处理开销。OpenScreen 提供了帧率选项:

// 根据录制内容类型自动选择帧率
function selectOptimalFrameRate(contentType) {
  switch (contentType) {
    case 'gaming':
      return 60;  // 游戏需要高帧率
    case 'code-demo':
      return 30;  // 代码演示 30fps 足够
    case 'slow-motion':
      return 120; // 慢动作回放
    default:
      return 30;
  }
}

2. 使用 SharedArrayBuffer 减少内存拷贝

Electron 12+ 支持 SharedArrayBuffer,允许渲染进程和主进程共享内存缓冲区,避免大量数据通过 IPC 传递时的序列化开销:

// 在主进程中创建共享缓冲区
const { desktopCapturer } = require('electron');

async function createSharedCapture() {
  const { width, height } = { width: 1920, height: 1080 };
  
  // 创建共享缓冲区(RGBA 格式)
  const sharedBuffer = new SharedArrayBuffer(width * height * 4);
  
  // 开始捕获到共享内存
  // ... 捕获逻辑使用 sharedBuffer 直接写入,避免 IPC 拷贝
  
  return sharedBuffer;
}

3. 录制时使用低分辨率预览

录制可以用高分辨率,但预览窗口可以用低分辨率渲染,减少 GPU 开销:

// 录制使用全分辨率
const highResStream = await navigator.mediaDevices.getUserMedia({
  video: { width: 1920, height: 1080, frameRate: 30 }
});

// 预览用低分辨率(Canvas 缩小渲染)
previewCanvas.width = 640;
previewCanvas.height = 360;
// drawImage 会自动缩放,高分辨率 → 低分辨率开销很小
ctx.drawImage(highResVideo, 0, 0, 640, 360);

4. FFmpeg 编码的参数调优

对于不同场景,FFmpeg 的编码参数需要权衡速度和文件大小:

// 速度优先(适合预览)
const fastPreset = ['-preset ultrafast', '-crf 28'];

// 质量优先(适合最终导出)
const qualityPreset = ['-preset slow', '-crf 18'];

// 平衡模式
const balancedPreset = ['-preset medium', '-crf 23'];

// H.264 编码器选择:libx264(兼容性最好)vs libx264 (CPU) vs h264_videotoolbox(macOS GPU 硬解)
function selectEncoder(platform) {
  if (platform === 'darwin') {
    return 'h264_videotoolbox'; // macOS 使用 VideoToolbox 硬件编码
  }
  return 'libx264'; // 其他平台用 CPU 编码
}

生态对比:GitHub 上四个同名 OpenScreen 的区别

文章开头的搜索结果中提到,GitHub 上有多个叫 OpenScreen 的项目,极易混淆。在写代码之前,厘清它们的区别很重要:

1. siddharthvaddem/openscreen(⭐ 26,625+)— 本文的主角

免费录屏工具,Screen Studio 开源替代品。

2. chromium/openscreen(Google 官方)

这是 Google 主导的投屏协议库,用 C++ 实现,运行在 Chromium 内部。它实现的是 Open Screen Protocol(OSP),用于 Chromecast 和其他投屏设备之间的通信。名字相同但完全不是一回事。

3. Recordly(openscreen fork)

从 siddharthvaddem/openscreen Fork 出来的社区分支,增加了自动缩放、动态光标模糊、摄像头叠加、自动字幕等进阶功能,适合更专业的产品演示制作。

4. openscreenPlus(中文社区 Fork)

在原版基础上添加了中英文界面自动切换,针对中文用户做了本地化优化。

一句话总结:如果你想录屏,就用 siddharthvaddem/openscreen;如果想做投屏协议开发,就用 chromium/openscreen


安装与使用

快速安装

macOS(推荐使用 Homebrew)

brew install --cask openscreen

或者从 GitHub Releases 下载

# 假设最新版本是 v1.2.3
curl -L https://github.com/siddharthvaddem/openscreen/releases/download/v1.2.3/OpenScreen-1.2.3.dmg -o openscreen.dmg
open openscreen.dmg
# 拖拽到 Applications 文件夹即可

Linux(AppImage)

wget https://github.com/siddharthvaddem/openscreen/releases/download/v1.2.3/OpenScreen-1.2.3.AppImage
chmod +x OpenScreen-1.2.3.AppImage
./OpenScreen-1.2.3.AppImage

从源码编译

# 克隆项目
git clone https://github.com/siddharthvaddem/openscreen.git
cd openscreen

# 安装依赖
npm install

# 开发模式运行
npm run dev

# 生产构建
npm run build

核心使用流程

  1. 选择录制源:启动后选择「全屏录制」或「窗口录制」
  2. 配置录制选项:设置帧率(30fps/60fps)、音频来源(系统音频/麦克风/无)
  3. 开启自动效果:勾选「自动缩放」「运动模糊」「背景虚化」
  4. 开始录制:点击录制按钮,3-2-1 倒计时后开始
  5. 结束录制:点击停止,弹出导出设置
  6. 导出:选择格式(MP4/GIF/WebM)、分辨率、质量

总结:开源工具如何重塑付费市场

OpenScreen 的出现不是偶然。它代表了开源社区对「高价工具」的一次精准打击。

Screen Studio $89 的定价,对于个人开发者来说不算贵,但不合理。它的核心功能——自动缩放、运动模糊、背景虚化——在底层实现上并不复杂,一个有经验的 Web 开发者完全可以在 Electron 生态里复现出来。

OpenScreen 正是这么做的。它用 Electron + React + FFmpeg 的技术组合,在不到一年的时间内做出了 Screen Studio 80% 的功能,而价格是 $0

对于我们这些技术内容创作者来说,这是个好消息。工具成本降低,意味着更多人可以参与到技术内容的创作中来。录屏不再是设计师和专业视频制作人的专属,开发者自己就能搞定产品演示、教程录制和 Bug 复盘。

当然,OpenScreen 目前还有不足:自动缩放算法不够细腻、转码速度比 Screen Studio 慢、macOS 上偶尔有音频同步问题。但这些都是开源项目的成长过程。关键在于它证明了:付费工具能做到的,开源一样能做到,而且会越来越接近

这就是开源的力量。


项目信息

  • GitHub: siddharthvaddem/openscreen
  • Stars: 26,625+
  • 语言: TypeScript
  • 技术栈: Electron + React + FFmpeg
  • 许可: MIT
  • 平台: macOS / Windows / Linux

推荐文章

html一份退出酒场的告知书
2024-11-18 18:14:45 +0800 CST
html文本加载动画
2024-11-19 06:24:21 +0800 CST
Nginx 实操指南:从入门到精通
2024-11-19 04:16:19 +0800 CST
GROMACS:一个美轮美奂的C++库
2024-11-18 19:43:29 +0800 CST
JavaScript设计模式:观察者模式
2024-11-19 05:37:50 +0800 CST
页面不存在404
2024-11-19 02:13:01 +0800 CST
基于Webman + Vue3中后台框架SaiAdmin
2024-11-19 09:47:53 +0800 CST
微信内弹出提示外部浏览器打开
2024-11-18 19:26:44 +0800 CST
虚拟DOM渲染器的内部机制
2024-11-19 06:49:23 +0800 CST
在JavaScript中实现队列
2024-11-19 01:38:36 +0800 CST
Vue 3 中的 Fragments 是什么?
2024-11-17 17:05:46 +0800 CST
程序员茄子在线接单