windows查看时间日志中进程有没有crash_【ElectronPlayground系列】写一个录屏工具需要多长时间?...

作者:ivyhaswell[1]

关于它的介绍,我想从最近发生的一个实际例子说起:

最近组内开始给项目写文档,需要找一款录屏工具做视频和gif图。于是我们开始寻找录屏的工具,它们或截不到状态栏,或视频体积太大,或windows平台没有等等。最终找到一款能用的花了不少时间。我正好在做electron的项目,于是就寻思:用electron做一个简单的,跨平台的录屏工具,需要多久呢?

答案是二十分钟,数十行代码。

有兴趣可以跟着下面的步骤尝试一下:

(一) 创建一个录屏工具

1.首先创建一个目录,目录下安装electron;

yarn add -D electron

1.创建main.js,这里是主进程的代码;

const { app, BrowserWindow, globalShortcut } = require("electron");const path = require("path");app.on("ready", () => {  const browserWindow = new BrowserWindow({    webPreferences: { nodeIntegration: true, enableRemoteModule: true },  });  browserWindow.loadFile(path.resolve(__dirname, "index.html"));  globalShortcut.register('CommandOrControl+Shift+R', () => browserWindow.webContents.send("StartRecording"))  globalShortcut.register('CommandOrControl+Shift+S', () => browserWindow.webContents.send("StopRecording"))});app.on('will-quit', () => globalShortcut.unregisterAll())

1.创建index.html,用来引入渲染进程脚本

当前状态:空闲

macOS下快捷键:Command+Shift+R 开始录制, Command+Shift+S停止录制; Windows下快捷键:Ctrl+Shift+R 开始录制, Ctrl+Shift+S停止录制;

1.创建renderer.js,这里有主要的业务代码

const { desktopCapturer, remote, shell, ipcRenderer } = require("electron");const path = require("path");const fs = require("fs");let mediaRecorder = null;let chunks = []async function start() {    if (mediaRecorder) return;    const sources = await desktopCapturer.getSources({ types: ["screen"] });    const stream = await navigator.mediaDevices.getUserMedia({      audio: false,      video: {        mandatory: {          chromeMediaSource: "screen",          chromeMediaSourceId: sources[0].id,        },      },    });    mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" });    mediaRecorder.ondataavailable = (event) => {      event.data.size > 0 && chunks.push(event.data);    };    mediaRecorder.start();      updateStatusText('录制中...')}function stop(){  if(!mediaRecorder) return  mediaRecorder.onstop = async () => {    const blob = new Blob(chunks, { type: "video/webm" });    const buffer = Buffer.from(await blob.arrayBuffer());    const filePath = path.resolve(remote.app.getPath("downloads"), `${Date.now()}.webm`);    fs.writeFile(filePath, buffer, () => {      shell.openPath(filePath);      mediaRecorder = null;      chunks = []    });  };  mediaRecorder.stop();  updateStatusText('空闲')}function updateStatusText(text){  const $statusElement = document.querySelector('#status')  $statusElement.textContent = text}ipcRenderer.on("StartRecording", start);ipcRenderer.on("StopRecording", stop);

然后就可以命令行启动应用

./node_modules/.bin/electron  main.js

使用也非常简单:

1.Mac下使用快捷键 Command+Shift+R 开始录制,Command+Shift+S 停止录制;Windows的快捷键则为 Control+Shift+R 和 Control+Shift+S;2.停止录制后会自动打开录制好的视频(视频默认保存在下载目录);

应用启动后长这样:

d2da0610c8e5d03a4ec8f068b37a6274.png

下面的动图就是使用这个demo录屏然后转换而成的:

2f82d657738b321756407136173ba451.gif

(二) 工具代码解析

二十分钟和数十行代码的说法可能有些夸张,毕竟还有许多功能,如录制参数配置、格式转换、多平台打包等等都还没有实现;但并不妨碍能看出来,electron开发上手,确实挺简单。

在分析代码之前先来看看electron的一个基本概念:主进程和渲染进程。

•主进程通过BrowserWindow创建窗口,每个窗口对应一个渲染进程;•渲染进程管理对应的web页面,BrowserWindow销毁后,相应的渲染进程也会终止;•一个渲染进程的崩溃不会影响其他渲染进程;

为了理解这个概念,我们直接动手尝试一下:

首先在根目录添加一个main-process.js

const { app, BrowserWindow, dialog } = require("electron");const path = require("path");app.on("ready", () => {  const win1 = new BrowserWindow({ x: 20, y: 20 });  win1.loadURL("https://github.com");  const win2 = new BrowserWindow({ x: 500, y: 20 });  win2.loadURL("https://stackoverflow.com");  const win3 = new BrowserWindow({    x: 20,    y: 500,    webPreferences: { nodeIntegration: true },  });  win3.loadFile(path.resolve(__dirname, "crash-renderer.html"));  win3.webContents.on('render-process-gone', async () => {    await dialog.showMessageBoxSync(win3, {message: '进程已崩溃.', buttons: ['关闭']})    win3.close()  })});

再添加一个crash-renderer.html

  setTimeout(() => {    process.crash()  }, 2000);

启动应用试试:./node_modules/.bin/electron main-process.js

在这里main-process.js创建了三个窗口,第一个打开了github,第二个打开了stackoverflow,第三个打开了本地的html文件。三个窗口分别对应三个渲染进程。

crash-renderer.html中我们执行了process.crash(),因此可以发现第三个窗口的进程在2秒后崩溃,而另外两个窗口github和stackoverflow依然正常。

ebd957ee1b12a89cc1474d29e644efb8.png

回到录屏应用,它的基本功能结构设计是这样的:

c36ffe24a4cd96ecf772b5fc12b7b9dc.png

反映到实现上,我们从main.js看起

先是注册了应用准备好之后和退出之前的方法

app.on('ready', () => {...});app.on('will-quit', () => {...});

在ready事件中做了两件事,首先是创建窗口:

const browserWindow = new BrowserWindow({  webPreferences: { nodeIntegration: true, enableRemoteModule: true },});browserWindow.loadFile(path.resolve(__dirname, "index.html"));

nodeIntegrationenableRemoteModule设置为true以便在渲染进程中使用node和remote模块,有时为了应用安全我们需要禁用这两个属性;

然后通过loadFile方法加载index.html

接下来是注册全局快捷键:

globalShortcut.register('CommandOrControl+Shift+R', () => browserWindow.webContents.send("StartRecording"))globalShortcut.register('CommandOrControl+Shift+S', () => browserWindow.webContents.send("StopRecording"))

监听到快捷键按下后,通过ipc向渲染进程发送消息;

在renderer.js中,则会接收对应的ipc消息执行对应的方法:

ipcRenderer.on("StartRecording", start);ipcRenderer.on("StopRecording", stop);

其中start方法用于执行录制:

1.通过desktopCapturer.getSources获取屏幕源,这里取其中第一个,通常为主屏幕;2.通过navigator.mediaDevices.getUserMedia获取视频流;3.创建mediaRecorder,通过mediaRecorder记录视频数据;

async function start() {    // 已经录制中    if (mediaRecorder) return;    // 这里取屏幕源的第一个,通常为主屏幕    const sources = await desktopCapturer.getSources({ types: ["screen"] });    const stream = await navigator.mediaDevices.getUserMedia({      audio: false,      video: {        mandatory: {          chromeMediaSource: "screen",          chromeMediaSourceId: sources[0].id,        },      },    });    mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" });    mediaRecorder.ondataavailable = (event) => {      event.data.size > 0 && chunks.push(event.data);    };    mediaRecorder.start();      updateStatusText('录制中...')}

end方法结束录制并保存文件:

1.给mediaRecorder添加结束方法,调用mediaRecorder.stop结束录制;2.结束后,取录制的数据chunks创建Blob对象;3.将Blob对象转换成arrayBuffer,然后转换成buffer;4.将buffer写入本地新建视频文件;5.打开视频文件;6.重置mediaRecorder和chunks;

function stop(){  if(!mediaRecorder) return  mediaRecorder.onstop = async () => {    const blob = new Blob(chunks, { type: "video/webm" });    const buffer = Buffer.from(await blob.arrayBuffer());    const filePath = path.resolve(remote.app.getPath("downloads"), `${Date.now()}.webm`);    fs.writeFile(filePath, buffer, () => {      shell.openPath(filePath);      mediaRecorder = null;      chunks = []    });  };  mediaRecorder.stop();  updateStatusText('空闲')}

大功告成。

(三) 一探Electron

我们再来回顾一下electron的官方自我介绍:用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序。

通过上面的例子也能看的出来,编写的代码确实都没有超出这三驾马车(甚至没用到CSS)。对于开发过nodejs的前端同学来说,不需要看解析都能很容易理解这些代码。

是不是感觉,抛掉主进程和通信模块的话,开发体验就像是在开发一个集成了node环境的网页?

而electron本身也提供了快速打开一个链接或html文件的方法,甚至不需要写主进程的代码,比如:

electron https://github.comelectron index.htmlelectron /Users/username/Projects/electron-demo/index.html...

这些方式都会让electron启动应用后打开一个窗口,并加载对应的网页链接或文件。

但如果只是单纯的网页套壳,我们用PWA不就好了吗?

因为electron能够提供更多的东西。

我们来看看electron的主体,它包括三个部分:

1.Chromium:用于web内容的显示;2.Node.js:用于文件读写,操作系统等底层api交互;3.自定义API:用来提供常用的系统操作需要的方法,比如设置菜单和托盘,控制窗口等;

官方文档自己也吐槽:使用Electron开发应用程序,就像使用Web界面构建Node.js应用程序,或通过无缝集成的Node.js构建网页一样。其中web开发和Node.js想必很多前端同学已经很熟悉了,因此上手electron开发,更多的就是需要熟悉electron的开发模式及其提供的API。另外如果想要搭建一个大型一些,在公司使用的项目,还需要一些electron开发的工程化经验。

官方文档是一个很好的学习对象,但无法作为唯一的参考。从官方文档到周边库文档,文档说不清的自己去试,试不全的打开vscode源码参考考;遇着bug到github找issue,有时需要一路追溯到electron源码和chromium源码......

在其中学过的一些知识,踩过的一些坑,我们总结出了一个开源项目:

https://github.com/tal-tech/electron-playground

项目中有我们总结了自己学习和踩坑的经验,参考了一些开源社区比较优秀的方案,做了这个项目,作为electron的快速学习和踩坑用。

目前最主要的功能,一是文档中内嵌代码都能直接运行,也可以直接在界面上修改代码运行,方便调整参数看效果;二是演练场,用于编写一些小的功能模块直接运行,目前只有基础模板,我们的目标是以后增加许多常用的功能模块,比如截图,比如消息通知,比如文件上传下载......等等等等,请拭目以待。

对项目有什么建议、想要什么功能、发现什么bug,都欢迎到Github提issue,我们的回复速度,超快的。

为了能更好学习electron,我们目前创作了一个系列,有兴趣可以看看

•【Electron-playground系列】菜单篇[2]•【Electron-Playground系列】Dialog与文件选择篇[3]•【Electron-playground系列】协议篇[4]•【Electron-Playground系列】托盘篇[5]

如果想看更完整的文档,请参考下面文档

Electron-Playground官方文档[6]

github地址传送门:https://github.com/tal-tech/electron-playground

References

[1] ivyhaswell: https://juejin.im/user/1644525124323880[2] 【Electron-playground系列】菜单篇: https://juejin.im/post/6887808911831728141[3] 【Electron-Playground系列】Dialog与文件选择篇: https://juejin.im/post/6887845363340804104[4] 【Electron-playground系列】协议篇: https://juejin.im/post/6887845625447055367[5] 【Electron-Playground系列】托盘篇: https://juejin.im/post/6887845739432771591[6] Electron-Playground官方文档: https://www.yuque.com/ezg6c4/op375w

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值