使用Electron自制錄屏軟件
前言
錄屏軟件對(duì)于我們來(lái)說(shuō)都不陌生了,今天我們要做的事情是實(shí)現(xiàn)自己的錄屏軟件。載體使用Electron
,因?yàn)樗m合錄制桌面的場(chǎng)景。我們今天實(shí)現(xiàn)的錄屏軟件會(huì)包括下面的功能
- 分辨率調(diào)節(jié)
- 幀率調(diào)節(jié)
- 支持保存為
webm
、mp4
、gif
格式
本文用到的技術(shù)包括:
electron
ffmpeg
vite
antd
下面話不多說(shuō),我們馬上進(jìn)入實(shí)戰(zhàn)環(huán)節(jié)
環(huán)境搭建
我個(gè)人習(xí)慣的技術(shù)棧是React+Vite
,所以這次搭建這個(gè)Electron
腳手架的時(shí)候我也往這方面去搭建。搭建過(guò)程中發(fā)現(xiàn)了一個(gè)很好的腳手架工具——electron-vite。需要搭建Electron
項(xiàng)目的兄弟們也可以考慮用這個(gè)。功能還是十分強(qiáng)大的,用起來(lái)十分舒服,很多東西都預(yù)設(shè)好了
直接下面的一條命令就可以搭建一個(gè)基礎(chǔ)框架:
npm create @quick-start/electron
搭建完框架之后,下面現(xiàn)在寫(xiě)一下簡(jiǎn)單的配置頁(yè)面,因?yàn)槲覀兪桥渲娩浿频母鞣N參數(shù)的。配置頁(yè)面如下:
就是使用了antd
下了幾個(gè)下拉框,還是比較簡(jiǎn)單的。
這里寫(xiě)的是渲染進(jìn)程的代碼,所以我們寫(xiě)在了renderer/src/App.jsx
里。做Electron
開(kāi)發(fā)心里需要清楚,這種頁(yè)面相關(guān)的,就寫(xiě)在渲染進(jìn)程里,跟我們平時(shí)寫(xiě)web
頁(yè)面無(wú)異。
另外,我們的配置肯定是要持久化存儲(chǔ)的,所以我會(huì)把這些配置存儲(chǔ)在localStorage
里。
useEffect(() => { //清晰度 setLocal(DEFINITION, definition) }, [definition]) useEffect(() => { // 幀率 setLocal(FRAME_RATE, frameRate) }, [frameRate]) useEffect(() => { // 產(chǎn)物拓展名 setLocal(EXT, ext) }, [ext])
以下是三個(gè)下拉框的選項(xiàng)配置,可以稍微看一下,后續(xù)會(huì)作為參數(shù)使用到錄屏功能中
const DEFINITION_LIST = [ { label: '超清', value: '3840x2160' }, { label: '高清', value: '1280x720' }, { label: '標(biāo)清', value: '720x480' } ] const FRAME_RATE_LIST = [ { label: '高', value: '60' }, { label: '中', value: '30' }, { label: '低', value: '15' } ] const EXT_LIST = [ { label: 'webm', value: 'webm' }, { label: 'mp4', value: 'mp4' }, { label: 'gif', value: 'gif' } ]
IPC通信
在Electron
中,包括渲染進(jìn)程跟主進(jìn)程。渲染進(jìn)程就是我們寫(xiě)的頁(yè)面,主進(jìn)程就是跟Electron
窗體相關(guān)的,或者是需要調(diào)用一些node
模塊的邏輯。在主進(jìn)程跟渲染進(jìn)程是需要相互通信的,這里的通信方式就是IPC
。
下面介紹兩個(gè)例子,幫助理解這兩個(gè)進(jìn)程如何通信
渲染進(jìn)程發(fā),主進(jìn)程收
下面需要在主進(jìn)程中實(shí)現(xiàn)錄屏的功能,所以我們頁(yè)面的配置需要發(fā)送到主進(jìn)程。那么就可以通過(guò)這種方式發(fā)送到主進(jìn)程中。
//渲染進(jìn)程發(fā)送 useEffect(() => { const options = { definition, frameRate, ext } window.electron.ipcRenderer.send(RECORD_EVNET.SET_CONFIG, options) }, [definition, frameRate, ext])
而主進(jìn)程則可以通過(guò)下面的方式來(lái)接收
import { ipcMain } from 'electron' let config = {} ipcMain.on(RECORD_EVNET.SET_CONFIG, (e, data) => { config = data })
主進(jìn)程發(fā),渲染進(jìn)程收
下面介紹另外一種,主進(jìn)程發(fā)送給渲染進(jìn)程
//主進(jìn)程發(fā)送 const mainWindow = new BrowserWindow({ width: 900, height: 670, show: false, // frame: false, // autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false } }) mainWindow.webContents.send('event-name', { name:'jayliang' })
渲染進(jìn)程接收
window.electron.ipcRenderer.on('event-name', callback)
系統(tǒng)托盤
下面要實(shí)現(xiàn)的是系統(tǒng)托盤的功能,方便我們?cè)诟鱾€(gè)窗口的時(shí)候也能快速喚起錄屏以及停止錄屏。
具體代碼實(shí)現(xiàn)如下
import { Menu, Tray, nativeImage, } from 'electron' let trayIcon = nativeImage.createFromPath(icon) // 設(shè)置圖標(biāo)大小 trayIcon = trayIcon.resize({ width: 16, height: 16 }) const tray = new Tray(trayIcon) const contextMenu = Menu.buildFromTemplate([ { label: '開(kāi)始', type: 'normal', click: startRecording }, { label: '停止', type: 'normal', click: stopRecording }, { type: 'separator' }, { label: '退出', type: 'normal', role: 'quit' } ]) tray.setToolTip('你的應(yīng)用名稱') tray.setContextMenu(contextMenu)
可拖拽窗體
這個(gè)功能跟錄屏軟件無(wú)關(guān),覺(jué)得這是一個(gè)很有意思的功能,就把它實(shí)現(xiàn)了哈哈哈。實(shí)現(xiàn)的方法也很簡(jiǎn)單,具體代碼如下
mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.executeJavaScript(` document.addEventListener('mousedown', (e) => { if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') { window.isDragging = true; offset = { x: e.screenX - window.screenX, y: e.screenY - window.screenY }; } }); document.addEventListener('mousemove', (e) => { if (window.isDragging) { const { screenX, screenY } = e; window.moveTo(screenX - offset.x, screenY - offset.y); } }); document.addEventListener('mouseup', () => { window.isDragging = false; }); `) })
上面是在主進(jìn)程中監(jiān)聽(tīng)渲染進(jìn)程的加載完成事件,往渲染進(jìn)程中注入了一段javascript
代碼。這段js
代碼主要做的事情也是監(jiān)聽(tīng)鼠標(biāo)事件,然后調(diào)用moveTo
方法來(lái)移動(dòng)整個(gè)窗體。
實(shí)現(xiàn)錄制
好的,前面啰嗦了那么多。終于到實(shí)現(xiàn)真正的錄制功能的時(shí)候了。先安裝一下ffmpeg
,這里使用的是ffmpeg-static
這個(gè)庫(kù),安裝這個(gè)庫(kù)的時(shí)候它會(huì)識(shí)別你的操作系統(tǒng),然后去安裝編譯好的ffmpeg
二進(jìn)制文件,最后導(dǎo)出的是一個(gè)ffmpeg
的路徑。
它里面用的下載鏈接可能會(huì)超時(shí),所以可以設(shè)置一個(gè)鏡像地址:
export FFMPEG_BINARIES_URL=https://cdn.npmmirror.com/binaries/ffmpeg-static
打包的命令記得改一下,為的是安裝不同操作系統(tǒng)的ffmpeg
"build:win": "rm -rf ./node_modules && npm run build && electron-builder --win --config", "build:mac": "rm -rf ./node_modules && npm run build && electron-builder --mac --config", "build:linux": "rm -rf ./node_modules && npm run build && electron-builder --linux --config"
安裝完之后就可以實(shí)現(xiàn)開(kāi)始錄制以及停止錄制功能了,實(shí)現(xiàn)的代碼如下:
import ffmpegPath from 'ffmpeg-static' const startRecording = async () => { let ffmpegCommand const { ext, frameRate, definition } = config const fileName = `${app.getPath('downloads')}/${+new Date()}.${ext}` if (ext === 'webm') { ffmpegCommand = `${ffmpegPath} -f avfoundation -framerate ${frameRate} -video_size ${definition} -i "1" -c:v libvpx-vp9 -c:a libopus ${fileName}` } else if (ext === 'mp4') { ffmpegCommand = `${ffmpegPath} -f avfoundation -framerate ${frameRate} -video_size ${definition} -i "1" -vsync vfr -c:v libx264 -preset ultrafast -qp 0 -c:a aac ${fileName}` } else if (ext === 'gif') { ffmpegCommand = `${ffmpegPath} -f avfoundation -framerate ${frameRate} -video_size ${definition} -i "1" -vf "fps=15" -c:v gif ${fileName}` } ffmpegProcess = spawn(ffmpegCommand, { shell: true }) ffmpegProcess.stderr.on('data', (data) => { console.error(`FFmpeg Error: ${data}`) }) ffmpegProcess.on('exit', (code, signal) => { console.log(`Recording process exited with code $[code] and signal ${signal}`) }) } const stopRecording = () => { if (ffmpegProcess) { ffmpegProcess.kill('SIGINT') // 發(fā)送中斷信號(hào)停止錄制 } }
解釋一下上面的代碼,ext
是拓展名、frameRate
是幀率、definition
是分辨率,這些都是從渲染進(jìn)程中獲取的。
不同的錄制產(chǎn)物對(duì)應(yīng)不同的ffmpeg
命令,開(kāi)始錄制時(shí)使用spawn
開(kāi)啟一個(gè)子進(jìn)程去調(diào)用ffmpeg
錄制,并保存子進(jìn)程的飲用,在點(diǎn)擊托盤的停止按鈕時(shí),向子進(jìn)程發(fā)送停止命令。
這樣我們就可以愉快的錄制屏幕,并保存為我們想要的樣子啦
以上還是存在不足:avfoundation
是用在mac
的采集庫(kù),在Windows
下應(yīng)該是用不了的,所以希望后續(xù)有時(shí)間也把windows
版本給兼容了~
以上就是使用Electron自制錄屏軟件的詳細(xì)內(nèi)容,更多關(guān)于Electron錄屏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
全面了解函數(shù)聲明與函數(shù)表達(dá)式、變量提升
下面小編就為大家?guī)?lái)一篇全面了解函數(shù)聲明與函數(shù)表達(dá)式、變量提升。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08JS下拉框內(nèi)容左右移動(dòng)效果的具體實(shí)現(xiàn)
這篇文章介紹了JS下拉框內(nèi)容左右移動(dòng)效果的具體實(shí)現(xiàn)方法,有需要的朋友可以參考一下2013-07-07動(dòng)態(tài)加載iframe時(shí)get請(qǐng)求傳遞中文參數(shù)亂碼解決方法
這篇文章主要介紹了動(dòng)態(tài)加載iframe時(shí)get請(qǐng)求傳遞中文參數(shù)亂碼解決方法,需要的朋友可以參考下2014-05-05同一個(gè)網(wǎng)頁(yè)中實(shí)現(xiàn)多個(gè)JavaScript特效的方法
這篇文章主要介紹了同一個(gè)網(wǎng)頁(yè)中實(shí)現(xiàn)多個(gè)JavaScript特效的方法,實(shí)例分析了多個(gè)特效的實(shí)現(xiàn)原理與相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02基于JS實(shí)現(xiàn)checkbox全選功能實(shí)例代碼
最近做了個(gè)項(xiàng)目其中有這樣的需求要求實(shí)現(xiàn)點(diǎn)擊全選選中所有菜單,再次點(diǎn)擊全選取消選中。下面小編給大家分享實(shí)現(xiàn)代碼,對(duì)js實(shí)現(xiàn)checkbox全選功能感興趣的朋友參考下吧2016-10-10js拆分字符串并將分割的數(shù)據(jù)放到數(shù)組中的方法
這篇文章主要介紹了js拆分字符串并將分割的數(shù)據(jù)放到數(shù)組中的方法,涉及javascript中split方法及數(shù)組的操作技巧,需要的朋友可以參考下2015-05-05原生JavaScript實(shí)現(xiàn)滾動(dòng)條效果
這篇文章主要介紹了原生JavaScript實(shí)現(xiàn)滾動(dòng)條效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01