視頻筆記軟件JumpVideo技術解析一:Electron案例-調用VLC播放器
大家好,我是TheGodOfKing,是 最強考研學習神器,免費視頻筆記應用JumpVideo,可以快速添加截圖時間戳,支持所有筆記軟件,學習效率MAX!的開發者之一,分享技術的目的是想找到更多志同道合的人,如果有大學生加入,我們還允許他把項目作為畢設(只有一個名額喲)群(689978959),那么今天要給大家分享軟件框架electron一個實用小案例: 調用vlc播放器播放視頻并控制vlc播放器,支持生成時間戳、播放、暫停、截圖、ab片段播放、ab循環播放、快進、快退等操作

業務說明
還是簡單說下需求:electron構建一個app,app主要可以打開本地視頻播放,同時app支持設置快捷鍵來控制打開的播放器.比如使用快捷鍵截取當前幀并提取相關文字,播放,暫停,等
關于VLC
VLC 媒體播放器(最初為 VideoLAN Client)是一款高度便攜的多媒體播放器,可播放各種音頻和視頻格式(MPEG、DivX/Xvid、Ogg 等)以及 DVD、VCD 和各種流媒體協議。 不過,近年來它也成為了一個功能極其強大的服務器,可將多種格式的實時和點播視頻流傳輸到我們的網絡和互聯網上。 VLC 由非營利基金會 VideoLAN 制作。
優點
- 支持多種音視頻格式
- 跨平臺,支持Window、Mac
正因為VLC是跨平臺的超強播放器,因此我才會選擇VLC作為項目軟件的本地視頻播放器。
實戰:核心代碼一:使用系統調用api來打開vlc播放器
關于exec和spawn
exec 和 spawn 是 Node.js 中 child_process 模塊提供的兩個方法,用于在子進程中執行命令。它們的主要區別在于它們如何處理輸入輸出數據以及適用的使用場景。
exec
exec 方法用于執行一個 shell 命令并且將其輸出(包括標準輸出和標準錯誤)作為一個緩沖區返回。它適用于執行簡單的、一次性的命令,并且不需要與子進程進行大量的交互。
使用示例:
const { exec } = require('child_process');
// 執行一個 shell 命令
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`執行出錯: ${error}`);
return;
}
console.log(`標準輸出:\n${stdout}`);
console.error(`標準錯誤:\n${stderr}`);
});
特點:
- 輸出作為一個緩沖區(字符串)返回。
- 默認情況下有一個 200KB 的輸出限制,可以通過 maxBuffer 選項增加。
- 適用于需要執行簡單命令并一次性獲取輸出的場景。
spawn
spawn 方法用于啟動一個新的進程并且為其輸入輸出流提供一個接口。它更適合處理長時間運行的進程或者需要與子進程進行大量交互的場景。
使用示例:
const { spawn } = require('child_process');
// 啟動一個新的進程
const ls = spawn('ls', ['-l']);
ls.stdout.on('data', (data) => {
console.log(`標準輸出: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`標準錯誤: ${data}`);
});
ls.on('close', (code) => {
console.log(`子進程退出碼: ${code}`);
});
特點:
- 返回一個 ChildProcess 對象,提供了 stdout 和 stderr 流來實時處理輸出。
- 沒有輸出緩沖區大小限制。
- 適用于需要實時處理輸出或者與子進程進行交互的場景。
總結
- 使用 exec 當你需要執行簡單命令并獲取其輸出。
- 使用 spawn 當你需要處理長時間運行的進程,或者需要實時處理進程輸出和交互
核心代碼二:打開播放器業務代碼
async openVLC(videoPath, seekTime = null) {
try {
const isVlcRunning = this.vlcProcess !== null
console.log('vlc.isVlcRunning=', isVlcRunning)
if (isVlcRunning) {
return this.onOpenVideo(videoPath, seekTime)
}
const args = [videoPath]
let selfSeekTime = false
if (seekTime) {
const {
mode,
times
} = this.parseSeekTime(seekTime)
if (mode === 'ab') {
selfSeekTime = true
// A-B片段
args.push(
`--start-time=${times[0]}`,
// `--stop-time=${times[1]}`,
// '--play-and-pause',
)
} else if (mode === 'ab-loop') {
selfSeekTime = true
args.push(
`--start-time=${times[0]}`,
// `--stop-time=${times[1]}`,
// '--loop',
)
} else {
args.push(`--start-time=${this.convertSeekTimeToSeconds(seekTime)}`)
}
}
if (SystemConfig.isWindows) {
args.push('--intf', 'qt')
}
console.log('vlc cmd:', this.vlcPath, args.join(' '))
const vlcProcess = spawn(this.vlcPath, args, {
detached: true,
stdio: 'ignore',
})
vlcProcess.unref()
vlcProcess.on('close', (code) => {
console.log(`VLC process exited with code ${code}`)
this.vlcProcess = null
this.clearAbCheckTimeInterval()
this.clearAbLoopCheckTimeInterval()
})
vlcProcess.on('spawn', () => {
if (selfSeekTime) {
this.seekTimeLoop(seekTime)
}
console.log('VLC opened successfully')
})
this.vlcProcess = vlcProcess
return true
} catch (error) {
console.log(`Failed to open VLC: ${error.message}`)
return false
}
}
代碼解釋:
- 首先,它檢查vlc是否已經在運行,如果是,則調用onOpenVideo方法并返回。
- 然后,根據傳入的seekTime參數來處理開始播放的時間。如果seekTime的模式是ab或ab-loop,則會處理A-B片段的播放,并將selfSeekTime設置為true。
- 接下來,根據系統是否是Windows來添加相應的參數。
- 在接下來,使用spawn方法創建一個新的vlc進程,并設置相應的參數和事件監聽器。如果selfSeekTime為true,則會調用seekTimeLoop方法。
- 最后,將創建的vlc進程賦值給this.vlcProcess,并返回true表示成功打開vlc。如果出現錯誤,則會打印錯誤信息并返回false。
到這里我們就打開了VLC播放器,然后這里在解釋下vlc終端指令幾個關鍵參數:
- --start-time 指定開始播放的時間點
- --stop-time 指定結束播放的時間點
- --loop 循環播放
- --intf qt 打開的播放器畫面擁有功能欄、進度欄等豐富的界面元素,不加只會彈出一個播放器
基本上,我們現在通過終端指令已經可以實現打開本地任一視頻播放了,而且也能實現a-b片段、ab片段循環,當然作者這里實際是放棄了指令實現的ab操作,具體實現有興趣的朋友可以留言.接下來,我們繼續看其他操作的實現.
實現播放、暫停、獲取當前播放狀態、快進、快退等操作
對于vlc來講,它已經為我們提供很好的對接方式,那就是vlc http api.因此我們便可以通過接口來實現我們的操作.
第一步:播放器設置http
作者給出mac的設置(默認顯示基本,就會有個http 密碼設置),??啟用并設置vlc密碼.就開啟了我們http接口.windows用戶設置基本一樣,具體看vlc軟件.
顯示基本
顯示全部
第二步:查看官方文檔
文檔地址: VLC HTTP requests - VideoLAN Wiki
第三步: 封裝一個請求方法
// 獲取 VLC 的當前狀態
async getVlcStatus() {
const {
port,
password
} = await this.getVlcConfig()
const url = `http://${SystemConfig.vlcHttpHost}:${port}/requests/status.xml`
try {
const response = await axios.get(url, {
auth: {
username: '',
password: password,
},
})
const parseStringPromise = promisify(xml2js.parseString)
const result = await parseStringPromise(response.data)
return result
} catch (error) {
console.error(`Error fetching VLC status: ${error.message}`)
throw error
}
}
// 發送 VLC HTTP 請求的函數
async sendVlcHttpCommand(command) {
const {
port,
password
} = await this.getVlcConfig()
const url = `http://${SystemConfig.vlcHttpHost}:${port}/requests/status.xml?command=${command}`
console.log('sendVlcHttpCommand:', url)
try {
const response = await axios.get(url, {
auth: {
username: '',
password: password,
},
})
//console.log('vlc http api result:', response.data)
return response.data
} catch (error) {
console.error(`Error sending VLC http command: ${error.message}`)
throw error
}
}
到這里,我們已經實現了electron 操作vlc播放器的大部分需求了. 那最后我們還要在實現一個截取當前播放幀畫面.對于它我們來看下該如何實現
實現截圖
終端指令和vlc接口都不好使的情況下,我們能想到的是ffmpeg.那這樣我們的實現方式就有了: 獲取當前播放的視頻信息->判斷有沒有在播的視頻->存在信息拿到播放路徑和當前播放時間->調用ffmpeg指令
當然,我們這里的場景方案只針對單開的情況,如果vlc開啟多開情況(好像可以)就不太對了,不過vlc接口獲取的是一個播放列表,說不定可以操作.
async takeScreenshot(videoPath, videoTime, outputDir) {
const outputFilePath = path.join(outputDir, 'vlc-local-video-snapshot.png')
return new Promise((resolve, reject) => {
ffmpeg(decodeURIComponent(videoPath))
.seekInput(videoTime)
.outputOptions('-frames:v 1')
.output(outputFilePath)
.on('end', () => {
console.log(`VlC Local Video Screenshot saved to ${outputFilePath}`)
resolve(outputFilePath)
})
.on('error', (err) => {
console.error(
`VlC Local Video Error taking screenshot: ${err.message}`,
)
reject(err)
})
.run()
})
}
總結
通過以上操作,我們完成了electron調用vlc播放器的需求,如果有大佬對項目感興趣的可以加群(689978959)私信我,如果是大學生加入我們還允許他把項目作為畢設(只有一個名額喲) ,后續關于如何自己實現ab操作有感興趣的朋友可以留言。下一期,小編將出一期 electron 調用 potPlayer播放器的文章。

浙公網安備 33010602011771號