赞
踩
本项目构建环境:
node => v12.15.0
electron => 10.1.3
搭建环境:
# 克隆示例项目的仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入这个仓库
$ cd electron-quick-start
# 安装依赖并运行
$ npm install && npm start
本项目所需依赖:
"devDependencies": {
"electron": "^10.1.3",
"electron-builder": "^22.8.1"
},
"dependencies": {
"bootstrap": "^4.5.2",
"electron-store": "^6.0.1",
"nodemon": "^2.0.4",
"uuid": "^8.3.1"
}
本项目无使用任何前端框架,纯原生。(体验一把原生开发带来的快落吧)
修改 main.js
const { app, BrowserWindow, ipcMain, dialog } = require('electron') const DataStore = require('./renderer/MusicDataStore') const myStore = new DataStore({ name: 'Music Data' }) // 封装一个 BrowserWindow,避免每一次重复的编写 class AppWindow extends BrowserWindow { constructor(config, fileLocation) { const baseConfig = { width: 960, height: 660, webPreferences: { nodeIntegration: true } } // 两个对象合并 const finalConfig = { ...baseConfig, ...config } super(finalConfig) this.loadFile(fileLocation) this.once('ready-to-show', () => { this.show() }) } } app.on('ready', () => { // 加载主页面 const mainWindow = new AppWindow({}, './renderer/index.html') mainWindow.webContents.on('did-finish-load', () => { mainWindow.send('getTracks', myStore.getTracks()) }) ipcMain.on('add-music-window', () => { const addWindow = new AppWindow({ width: 800, height: 600, // 指定主进程 parent: mainWindow }, './renderer/add.html') }) // 添加数据 ipcMain.on('add-tracks', (event, tracks) => { const updatedTracks = myStore.addTracks(tracks).getTracks() mainWindow.send('getTracks', updatedTracks) }) // 删除数据 ipcMain.on('delete-track', (event, id) => { const updatedTracks = myStore.deleteTrack(id).getTracks() mainWindow.send('getTracks', updatedTracks) }) ipcMain.on('open-music-file', (event) => { dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'], filters: [{ name: 'Music', extensions: ['mp3'] }] }).then((files) => { if (files) { event.sender.send('selected-file', files) } }) }) })
index.html
(这是我们的主页面)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>本地播放器</title> <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="./index.css"> <link rel="stylesheet" href="../font/iconfont.css"> </head> <body> <div class="music-content"> <div class="container"> <h1>我的播放器</h1> <button type="button" class="btn btn-primary btn-lg btn-block mt-4" id="add-music-button" > 添加歌曲到曲库 </button> <div id="tracksList" class="mt-4"> </div> <div class="container fixed-bottom pb-4 "> <hr/> <div class="row my-2" id="player-status"></div> <div class="progress"> <div class="progress-bar" id="player-progress" role="progressbar" style="width: 0%;"> 0% </div> </div> </div> </div> </div> <script> require('./index.js'); </script> </body> </html>
index.js
const { ipcRenderer } = require('electron') const { $, convertDuration, str } = require('./helper') let musicAudio = new Audio() let allTracks let currentTrack $('add-music-button').addEventListener('click', () => { ipcRenderer.send('add-music-window') }) const renderListHTML = (tracks) => { const tracksList = $('tracksList') const tracksListHTML = tracks.reduce((html, track) => { html += `<li class="row music-track list-group-item d-flex justify-content-between align-items-center"> <div class="col-10"> <i class="mr-2 text-secondary iconfont icon-yinle"></i> <b>${str(track.fileName)}</b> </div> <div class="col-2"> <i class="fas fa-play mr-3 icon-ziyuan iconfont" data-id="${track.id}"></i> <i class="fas fa-trash-alt icon-del iconfont" data-id="${track.id}"></i> </div> </li>` return html }, '') const emptyTrackHTML = '<div class="alert alert-primary">还没有添加任何音乐</div>' tracksList.innerHTML = tracks.length ? `<ul class="list-group">${tracksListHTML}</ul>` : emptyTrackHTML } const renderPlayerHTML = (name, duration) => { const player = $('player-status') const html = `<div class="col font-weight-bold""> 正在播放:${str(name)} </div> <div class="col"> <span id="current-seeker">00:00</span> / ${convertDuration(duration)} </div>` player.innerHTML = html } const updateProgressHTML = (currentTime, duration) => { // 计算 progress 的数值 const progress = Math.floor(currentTime / duration * 100) const bar = $('player-progress') bar.innerHTML = progress + '%' bar.style.width = progress + '%' const seeker = $('current-seeker') seeker.innerHTML = convertDuration(currentTime) } ipcRenderer.on('getTracks', (event, tracks) => { allTracks = tracks renderListHTML(tracks) }) musicAudio.addEventListener('loadedmetadata', () => { // 开始渲染播放器状态 renderPlayerHTML(currentTrack.fileName, musicAudio.duration) }) musicAudio.addEventListener('timeupdate', () => { // 更新播放器状态 updateProgressHTML(musicAudio.currentTime, musicAudio.duration) }) $('tracksList').addEventListener('click', (event) => { event.preventDefault() const { dataset, classList } = event.target const id = dataset && dataset.id if (id && classList.contains('icon-ziyuan')) { // 播放音乐 if (currentTrack && currentTrack.id === id) { // 继续播放音乐 musicAudio.play() } else { // 播放新的歌曲 currentTrack = allTracks.find(track => track.id === id) musicAudio.src = currentTrack.path musicAudio.play() const resetIconEle = document.querySelector('.icon-zanting') if (resetIconEle) { // 更改播放图标 resetIconEle.classList.replace('icon-zanting', 'icon-ziyuan') } } classList.replace('icon-ziyuan', 'icon-zanting') } else if (id && classList.contains('icon-zanting')) { // 暂停 musicAudio.pause() classList.replace('icon-zanting', 'icon-ziyuan') } else if (id && classList.contains('icon-del')) { // 删除 ipcRenderer.send('delete-track', id) } })
MusicDataStore.js
(这是存储音乐数据的地方)
const Store = require('electron-store') const {path} = require('path') // 保证唯一的ID const { v4: uuidv4 } = require('uuid'); class DataStore extends Store { constructor(settings) { super(settings); this.tracks = this.get('tracks') || [] } // 保存数据 saveTracks() { this.set('tracks', this.tracks) return this } // 获取数据 getTracks() { return this.get('tracks') || [] } // 添加数据 addTracks(tracks) { const tracksWithProps = tracks.map(track => { return { id: uuidv4(), path: track, fileName: track } }).filter(track => { const currentTrackPath = this.getTracks().map(track => track.path) return currentTrackPath.indexOf(track.path) < 0 }) this.tracks = [...this.tracks, ...tracksWithProps] return this.saveTracks() } // 删除数据 deleteTrack(deleteId) { this.tracks = this.tracks.filter(item => item.id !== deleteId) return this.saveTracks() } } module.exports = DataStore
大概的主代码就差不多了。剩下的一些辅助代码我就不写出来了。
(这个 demo 其实是存在 bug,就是显示音乐的名字有一点问题)
我在 electron 引入 path 模块是 undefined,这个我不知道是为什么,所以代码的名字我只是做了很简单的处理。就很丑了。
代码地址奉上
https://gitee.com/suiboyu/electron-music
下载之后的代码中 electron-music/outName/appName-win32-x64,中有一个 appName.exe 就是打包出来的 exe(这个打包好像也行有点问题的,不过 demo 嘛,刚刚开始学习,凑合凑合用吧)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。