Electron позволяет нам использовать html, css, javascript для создания кроссплатформенных (Windows, macOS, Linux) настольных приложений. Давайте реализуем проигрыватель, который поддерживает воспроизведение онлайн-музыки и локальной музыки через Electron+Nodejs+React. Стиль дизайна плеера — Fluent Design для Windows, он может работать как на win10, так и на macOS (если сборка и упаковка должны быть упакованы по-разному на разных платформах), на Linux не тестировался.
В этой статье в основном описываются некоторые трудности и ключевые моменты, с которыми пришлось столкнуться при разработке. Если вы хотите узнать подробности, вы можете просмотреть код, чтобы просмотреть его.
1. Предварительная подготовка
-
Electron
Рекомендуется использовать зеркало Taobao, оригинальное зеркало сложно установить даже с лестницей.
npm install -g package --registry=https://registry.npm.taobao.org
- React(последняя версия подходит)
- React-Router4(для перехода по страницам)
- Redux/react-redux(контейнер состояния js, хранящий глобальное состояние и данные)
- Express(для создания сервисов API)
- LowDB(LowDB — это основанная на узлах база данных файлов JSON, сервер не требуется, а память и хранилище на жестком диске используются для кэширования данных игрока)
- Облачный API NetEase
2. Создайте облачный API NetEase
- Вытащите git-проект NetEase Cloud API
git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git
- Введите каталог для запуска службы
cd NeteaseCloudMusicApi
node app.js
Конфигурация службы по умолчанию — порт 3000, который можно изменить в app.js.
Чтобы обеспечить последующее обновление NeteaseCloudMusicApi, не рекомендуется напрямую изменять код проекта NeteaseCloudMusicApi и запускать службу Node для предоставления API в качестве транзитной службы.Необходимая бизнес-логика может быть размещена на вашем собственном уровне Node. .
3. Строительство проекта
Структура каталогов
fluentApp
|--app
|--cache //图片缓存目录
|--dist //js,scss打包编译文件夹
|--font //字体文件
|--...文件
|--server
|--express目录
|--ui
|--js //js开发文件
|--scss //scss样式文件
1. Создайте службу API
Установить экспресс-зависимости
npm install express --save
Инициализировать каталог с экспресс
express server
Создайте файлы api.js и service.js в папке маршрутов, чтобы определить маршруты API и бизнес-логику обработки соответственно.
запустить службу
node bin/www
2. Создайте Электрон
1.app
app как основной процесс управляет жизненным циклом приложения
const {app} = require('electron');
Приложение может прослушивать множество событий и делать то, что оно ожидает, когда срабатывает соответствующее событие.Документация APIВо-первых, вам нужно создать главное окно программы, которое должно запускаться, когда Электрон завершает инициализацию.
app.on('ready', createWindow);
Приложение в Electron создает только основной процесс, но для отображения окна на интерфейсе его нужно использоватьBrowserWindow, его роль заключается в создании окна браузера и управлении им.
const {BrowserWindow} = require('electron');
let win//定义一个窗口
function createWindow() {
win = new BrowserWindow({
frame: false,
width: 400,
height: 670,
transparent: true,
resizable: false,
maximizable: false,
backgroundColor: '#00FFFFFF',
webPreferences: {
nodeIntegrationInWorker: true
},
icon: path.join(__dirname, 'icon.ico')
});
}
2.BrowserWindow
Создайте экземпляр объекта BrowserWindow, задайте ширину и высоту формы и некоторые другие свойства. Окно проигрывателя полностью настраивается, поэтому, чтобы удалить панель заголовка, которая поставляется вместе с системой, установите frame:false. Ссылка на конкретный параметрДокументация API.
Создание формы эквивалентно созданию браузера, которому необходимо отображать содержимое в браузере и файле html. Все создают новый app.html, а затем позволяют браузеру отображать эту html-страницу.
win.loadURL(url.format({
pathname: path.join(__dirname, 'app.html'),
protocol: 'file:',
slashes: true
}));
Показать окно, когда форма готова к отображению
win.on('ready-to-show', () => {
win.show();
});
Таким образом, после запуска Electron появится оконный интерфейс без полей, отображающий app.html.
Слушайте событие закрытия окна приложения и выходите из основного потока, когда все окна закрыты.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
});
3. Интерфейс отладки
Окно Electron эквивалентно интерфейсу браузера Chrome, а метод отладки такой же, как в Chrome с использованием консольной отладки. Сначала измените ширину окна 400 -> 800 консолей, чтобы освободить место, а затем добавьте строку кода в main.js
win.webContents.openDevTools();
Это откроет консоль. Если окно настроено как прозрачное после открытия консоли, окно превратится в белый фон, и консоль можно будет закрыть для восстановления.
Если вам нужны другие плагины для отладки Chrome, вы также можете загрузить другие плагины для инструментов отладки с редукцией, например:
main.js
const { default: installExtension, REDUX_DEVTOOLS } = require('electron-devtools-installer');
Сначала загрузите плагин, затем
installExtension(REDUX_DEVTOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err));
Это позволит вам использовать плагин отладки.
3. Основные функции плеера
1.audio
Используйте тег аудио html5 в качестве проигрывателя для воспроизведения музыки.Документация API
<audio src="audio.mp3" id="audio"></audio>
let audio = document.getElementById('audio');
Получите звуковой объект для работы через API.
2.Web audio/canvas
Чтобы получить прыгающие волны во время воспроизведения, вам необходимо получить форму волны звука, полученную через API веб-аудио.Узнайте о веб-аудио.
Web Audio API позволяет пользователям выполнять аудиооперации в аудиоконтексте (AudioContext) с характеристиками модульной маршрутизации. Основные аудиооперации выполняются на аудиоузлах, которые соединены вместе, чтобы сформировать граф маршрутизации аудио.
Аудиоузлы соединяются друг с другом через свои входы и выходы, образуя цепочку или простую сеть. Как правило, эта цепочка или сеть начинается с одного или нескольких источников звука.
Простой и типичный процесс веб-аудио выглядит следующим образом:
- Создайте звуковой контекст
- Создавайте источники в аудиоконтексте — например, осцилляторы, потоки
- Создание узлов эффектов, таких как реверберация, биквадрат, панорамирование, компрессор
- Выберите место для звука, например системные динамики.
- Подключите источник к эффекту и выведите эффект в место назначения.
//获取web audio上下文
this.audioContext = new window.AudioContext();
//获取canvas节点
this.canvas = document.getElementById('waveCanvas');
//获取canvas 2d上下文
this.ctx = this.canvas.getContext('2d');
//设置canvas宽高
this.width = this.canvas.offsetWidth,
this.height = this.canvas.offsetHeight;
this.canvas.width = this.width,
this.canvas.height = this.height;
//画出矩形方框,this.baseY是方框高度相对于窗口高度的基准线
this.ctx.beginPath();
this.ctx.fillStyle = 'rgba(102,102,102,0.8)';
this.ctx.moveTo(0, this.baseY);
this.ctx.lineTo(this.width, this.baseY);
this.ctx.lineTo(this.width, this.height);
this.ctx.lineTo(0, this.height);
this.ctx.fill();
Прямоугольник — это серая часть в нижней части окна.Затем используйте тег audio в качестве источника звука и используйте веб-аудио, чтобы получить звук для анализа.
this.audio = document.getElementById('audio');
//使用audio标签作为音源
this.source = this.audioContext.createMediaElementSource(this.audio);
//创建一个分析器
this.analyser = this.audioContext.createAnalyser();
//串联起分析器节点和音源输出
this.analyser.connect(this.audioContext.destination);
this.source.connect(this.analyser);
//从分析器中获取当前播放的频率数据
let array = new Uint8Array(_this.analyser.frequencyBinCount);
this.analyser.getByteFrequencyData(array);
После получения данных начните рисовать график формы сигнала.Непосредственно полученные данные представляют собой массив Uint8Array с длиной по умолчанию 1024. После зацикливания данных добавьте текущую частоту на основе базовой линии baseY и нарисуйте ее в виде графика формы сигнала. .
this.ctx.beginPath();
this.ctx.moveTo(0,this.baseY);
for(let i = 0;i < array.length; i++) {
this.ctx.lineTo(i, this.baseY - array[i]);
}
this.ctx.lineTo(this.width, this.baseY);
this.ctx.lineTo(this.width, 0);
this.ctx.lineTo(0, 0);
this.ctx.fill();
Если сигнал, нарисованный с использованием 1024 точек, очень плотный, и две точки соединены прямой линией, как показано на этом рисунке.
Поэтому его нужно оптимизировать.Всего в качестве ключевых точек выбрано 1024 точки данных из 20 из 1024 точек с размером шага 50. Поскольку в большинстве песен больше низких частот, чем высоких, волна всегда высокая слева и низкая справа, а высокая частота большую часть времени плоская, а начальная точка слева всегда равна 0, а форма волны будет казаться очень жесткимДля того, чтобы форма сигнала была гладкой и ровной, возьмите первые десять из 20 ключевых точек и поместите их в середину окна, оставьте пространство с обеих сторон, а затем возьмите две группы по пять последовательных точек, одна группа склеена на левой стороне массива ключевых точек и другой группы Поместите правую сторону и нарисуйте область формы волны, немного превышающую ширину экрана, чтобы нарисованная точка больше не начиналась с 0 в качестве начальной точки.
let waveArr1 = [],waveArr2 = [],waveTemp = [],leftTemp = [],rightTemp = [],waveStep = 50,leftStep = 70, rightStep = 90;
array.map((data, k) => {
if(waveStep == 50 && waveTemp.length < 9) {
waveTemp.push(data / 2.6);
waveStep = 0;
}else{
waveStep ++;
}
if(leftStep == 0 && leftTemp.length < 5) {
leftTemp.unshift(Math.floor(data / 4.8));
leftStep = 70;
}else {
leftStep --;
}
if(rightStep == 0 && rightTemp.length < 5) {
rightTemp.push(Math.floor(data / 4.8));
rightStep = 90;
}else {
rightStep --;
}
});
waveArr1 = leftTemp.concat(waveTemp).concat(rightTemp);
waveArr2 = leftTemp.concat(rightTemp);
waveArr2.map((data, k) => {
waveArr2[k] = data * 1.8;
});
let waveWidth = Math.ceil(this.width / (waveArr1.length - 3));
let waveWidth2 = Math.ceil(this.width / (waveArr2.length - 3));
В настоящее время форма волны однородна только по размаху, но ключевые точки напрямую соединены прямыми линиями, и нет ощущения волны, поэтому далее вам нужно использовать кривую для соединения двух соседних точек.
Есть много способов соединить серию дискретных точек кривыми.кубическая функциярешать.
this.ctx.beginPath();
this.ctx.fillStyle = 'rgba(102,102,102,0.8)';
this.ctx.moveTo(-waveWidth * 2, this.baseY - waveArr1[0]);
for(let i = 1; i < waveArr1.length - 2; i ++) {
let p0 = {x: (i - 2) * waveWidth, y:waveArr1[i - 1]};
let p1 = {x: (i - 1) * waveWidth, y:waveArr1[i]};
let p2 = {x: (i) * waveWidth, y:waveArr1[i + 1]};
let p3 = {x: (i + 1) * waveWidth, y:waveArr1[i + 2]};
for(let j = 0; j < 100; j ++) {
let t = j * (1.0 / 100);
let tt = t * t;
let ttt = tt * t;
let CGPoint ={};
CGPoint.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
CGPoint.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
this.ctx.lineTo(CGPoint.x, this.baseY - CGPoint.y);
}
this.ctx.lineTo(p2.x, this.baseY - p2.y);
}
this.ctx.lineTo((waveArr1.length) * waveWidth, this.baseY - waveArr1[waveArr1.length - 1]);
this.ctx.lineTo(this.width + waveWidth * 2, this.baseY);
this.ctx.lineTo(this.width + waveWidth * 2, this.height);
this.ctx.lineTo(-2 * waveWidth, this.height);
this.ctx.fill();
Нарисовав гладкую форму волны, соответствующим образом отрегулируйте процентное соотношение точек перехода слева и справа по отношению к ключевым точкам, чтобы точки перехода были более плавными и выглядели более естественными.
После того, как группа волн успешно нарисована, используйте левую и правую две группы точек перехода, чтобы напрямую объединить их, чтобы нарисовать светлую форму волны, чтобы увеличить уровень биения волны.
3. Перетащите индикатор воспроизведения и
Индикатор выполнения воспроизведения использует круглую полосу выполнения, которая поддерживает перетаскивание для выбора позиции воспроизведения.
Круговой прогресс рисуется с помощью svg, рисуя дорожку и сыгранную часть
<svg width="32vw" height="32vw">
<circle cx="16vw" cy="16vw" r="15.5vw" strokeWidth="3" stroke="#DDD" fill="none"></circle>
<circle cx="16vw" cy="16vw" r="15.5vw" strokeWidth="3" stroke="#666" fill="none" strokeDasharray={`${this.calcCir()}vw 2000`}></circle>
</svg>
Атрибут stroke-dasharray используется для создания пунктирной линии и установки длины одной пунктирной линии.Пока расстояние между пунктирными линиями достаточно велико, можно получить эффект отображения дуги переменной длины.
Затем начните решать проблему перетаскивания.Чтобы сделать движение точки кнопки перетаскивания более плавным и плавным, для достижения этого используется преобразование угла CSS3.
<div className="dot-wrap" id="dotWrap" style={{transform: `rotate(${this.calcDeg()}deg)`}}>
<div className="dot" onMouseDown={this.dotMouseDown.bind(this)}></div>
</div>
Длина и ширина обтекания точками такие же, как у круглого индикатора выполнения. Точка кнопки перетаскивания находится в верхней средней позиции. Когда мышь перетаскивает кнопку, вычисляется разница между текущим положением мыши и верхней средней позицией, и расстояние между двумя точками рассчитывается с круговым центром в качестве центра.Сформированный угол, поверните точечную обертку в соответствии с этим углом, зная угол, вы можете получить процент угла до 360 ° и установить время воспроизведения воспроизводимой в данный момент песни в процентах.
4. Интерфейс игрока
Интерфейс списка игроков разделен на три части:
- Четыре вкладки на главной странице — это рекомендуемые плейлисты, последние синглы, новые диски и местные песни.
- Интерфейс плейлиста и сведений об эксперте, стиль обеих страниц сведений одинаков.
Нажимаются плейлисты и альбомы на главной странице, чтобы ввести сведения, и использовать react-route для перехода Страница поиска должна сохранять состояние результатов поиска и использовать плавающий слой для отображения страницы сведений.
5. Сканируйте местные песни
Обычный браузерный JavaScript не имеет полномочий и API для чтения папки сканирования, но JavaScript в Electron на html-странице можно использовать Nodejs в модуле, модуль можно использовать для открытия папки fs Scan.
import {remote} from 'electron';
const fs = remote.require('fs');
Внедряйте модули Nodejs через удаленный доступ.
Здесь используется другой метод сканирования файлов.Благодаря взаимодействию процессов Electron в интерфейсе запускаются события, чтобы уведомить основной процесс о необходимости сканирования.
Прежде всего, чтобы сканировать папку, вам нужно знать, какой путь сканировать, и использовать Electron для вызова собственного диалога для выбора пути.
remote.dialog.showOpenDialog({
title: '选择添加目录',
properties: ['openDirectory', 'multiSelections'],
}, (files) => {
if(!files) return;
...
})
После выбора пути для сканирования основной процесс может быть уведомлен о начале сканирования. Поскольку сканирование может занять много времени и вызвать отрисовку пользовательского интерфейса, все используют подпотоки Nodejs для сканирования.
const {ipcMain} = require('electron');
const child_process = require('child_process');
//主进程监听scanningDir,当渲染进程触发scanningDir时主进程开始进行扫描
ipcMain.on('scanningDir', (e, dirs) => {
const cp = child_process.fork('./scanFile.js');
cp.on('message', () => {
e.sender.send('scanningEnd');
cp.disconnect();
});
cp.send(dirs);
});
После сканирования будут получены все музыкальные файлы с суффиксами в пути. Поскольку музыкальные файлы, загружаемые большинством музыкальных платформ, содержат музыкальную информацию и обложки альбомов, рекомендуется использовать музыкальную информацию заранее.jsmediatagsмодуль
const jsmediatags = require('jsmediatags');
const btoa = require('btoa');
const fs = require('fs');
//对扫描到的所有音乐文件进行循环出来
songItem.map((data, k) => {
let name = getFileName(data);
jsmediatags.read(data, {
onSuccess: (tag) => {
//文件内包含的专辑封面是base64格式的图片,获取后转成jpeg格式缓存到cache文件夹内。
let image = tag.tags.picture;
let filename = `cache/albumCover/${createRandomId()}.jpeg`;
let base64String = "";
image.data.map((d, j) => {
base64String += String.fromCharCode(d);
});
let dataBuffer = new Buffer(btoa(base64String), 'base64');
fs.writeFile(filename, dataBuffer, (err) => {
...
});
},
onError: (error) => {
...
}
});
});
После обработки получается массив, содержащий пути к файлам и информацию, а для хранения данных используется lowdb.
const low = require('lowdb');
const FileAsync = require('lowdb/adapters/FileAsync');
const adapter = new FileAsync('db.json');
const db = low(adapter);
...
//读取一下数据库
db.then(db => {
db.set('localPlayList', data).write().then(() => {
process.send('');
})
});
...
В lowdb есть яма. После создания экземпляра объекта db содержимое db будет состоянием данных текущего экземпляра. Если db записывается в основном процессе, информация базы данных по-прежнему будет старыми данными, когда db процесса пользовательского интерфейса записывает данные. , что приведет к рассинхронизации данных. Поэтому сначала читайте базу данных для каждой записи.
db.read().get('key').value();
6. Создайте и обновите текущий список воспроизведения и воспроизведите его в случайном порядке.
Генерация списка:Когда вы нажимаете, чтобы воспроизвести песню, получите текущий идентификатор песни (случайный идентификатор также будет сгенерирован при локальном сканировании песни) и сравните, существует ли он уже в списке. Если он не существует, добавьте его. Другой способ добавить — нажать пакетное добавление в альбом или плейлист. . При открытии списка, если воспроизводится песня, перетащите список на позицию воспроизводимой песни.
Генерация случайного плейлиста:
Как правило, случайное воспроизведение игроков на рынке на самом деле не щелкает следующую песню для случайного выбора песни, а генерирует перемешанный список воспроизведения с помощью алгоритма перетасовки для случайного воспроизведения.Вы также можете играть в зависимости от того, сколько раз каждая песня Вес, чтобы увеличить вероятность того, что песня будет воспроизведена, вот простое использованиеshuffle-arrayмодуль для создания массива платежных последовательностей из существующего массива.
Когда программа загружается, если текущий режим воспроизведения является случайным режимом, изначально создается случайный список воспроизведения и кэшируется в редуксе.Если есть новая песня, она будет вставлена в случайную позицию в случайном списке, и список будет обновлено. Если вы вручную переключаетесь на случайное воспроизведение, сначала определите, есть ли в редуксе список случайного воспроизведения, а если нет, сгенерируйте новый для кэширования.
import shuffleArray from 'shuffle-array';
//创建随机列表
createShuffeList() {
let playlist = db.get('playList').value() || [];
let shuffleList = shuffleArray(playlist, {copy: true });
store.dispatch(Actions.setShuffleList(shuffleList));
}
//将新增歌曲插入到随机列表
insertSongToShuffleList(item) {
if(!item) return;
let shuffleList = store.getState().main.shuffleList;
let len = shuffleList.length;
(item || []).map((data, k) => {
let insertPosition = Math.floor(len * Math.random());
shuffleList = shuffleList.splice(insertPosition, 0, data);
});
store.dispatch(Actions.setShuffleList(shuffleList));
}
7. Управление кнопками на панели задач под платформой Windows
У Electron есть API для настройки пользовательских эскизов и панелей инструментов для приложений на панели задач Windows. Затем добавьте в проигрыватель кнопки «Предыдущий», «Воспроизведение/пауза» и «Следующий». В файле main.js в основном используетсяwebContentsДля достижения основного процесса отправить сообщение в процесс рендеринга.let thumbarButtons = [
{
tooltip: '上一曲',
icon: path.join(__dirname, 'prev.png'),
flags: [
'nobackground'
],
click: () => {
win.webContents.send('pre');
}
},
{
tooltip: '播放',
icon: path.join(__dirname, 'play.png'),
flags: [
'nobackground'
],
click: () => {
win.webContents.send('switch');
}
},
{
tooltip: '下一曲',
icon: path.join(__dirname, 'next.png'),
flags: [
'nobackground'
],
click: () => {
win.webContents.send('next');
}
}
]
win.setThumbarButtons(thumbarButtons);
...
ipcMain.on('playSwitch', (e, state) => {
let icon = state?'paused.png':'play.png';
thumbarButtons[1].icon = path.join(__dirname, icon);
win.setThumbarButtons(thumbarButtons);
});
Кнопка в трее имеет событие щелчка, и состояние песни переключается при нажатии, чтобы отправить информацию о связи в процесс рендеринга.После успешного переключения состояния процесс рендеринга уведомляет основной процесс об изменении значка кнопки в трее.
3. Прокси-сервис
Веб-аудио не может получить контекст междоменных аудиоресурсов.При воспроизведении онлайн-музыки вам необходимо самостоятельно создать слой прокси, используя Nodejs.http-proxyмодуль.
let http = require('http');
let https = require('https');
let httpProxy = require('http-proxy');
let url = require('url');
router.use('*', (req, res) => {
req.url = req.originalUrl.replace('/proxy', '');
let proxy = httpProxy.createProxy({});
proxy.on('error', (err) => {
console.log('ERROR');
console.log(err);
});
let finalUrl = 'http://m10.music.126.net';
let finalAgent = null;
let parsedUrl = url.parse(finalUrl);
if (parsedUrl.protocol === 'https:') {
finalAgent = https.globalAgent;
} else {
finalAgent = http.globalAgent;
}
proxy.web(req, res, {
target: finalUrl,
agent: finalAgent,
headers: { host: parsedUrl.hostname },
prependPath: false,
xfwd : true,
hostRewrite: finalUrl.host,
protocolRewrite: parsedUrl.protocol
});
});
4. Фронтальная конструкция
React использует синтаксис ES6, а css использует Scss для написания всего, что нужно скомпилировать и запустить.
Scss рекомендует использовать скомпилированную сборку, поставляемую с webstrom, которую можно скомпилировать в режиме реального времени.
Добавьте File Watcher, установите путь вывода файла css.
--no-cache --update $FileName$:$ProjectFileDir$/app/dist/$FileNameWithoutExtension$.css
js с помощью webpack, сначала войдите в каталог ui, запустите webpack для компиляции js-файла, рекомендуется закомментировать конфигурацию упаковки и сжатия во время разработки, что может улучшить компиляцию в реальном времени и скорость трансляции webpack -w.
plugins: [
new ExtractTextPlugin("bundle_style.css"),
//new webpack.DefinePlugin({
// 'process.env': {
// 'NODE_ENV': JSON.stringify('production')
// }
//}),
//new UglifyJSPlugin()
]
5. Запустите приложение в среде разработки
js упаковывается webpack и выводится в папку dist в каталоге приложения, а cd — в каталог приложения для запуска.
electron .
чтобы запустить приложение. Или создайте package.json в следующем каталоге, чтобы начать с npm, настроив скрипт
"scripts": {
"start": "electron app/main.js",
},
//运行 npm start
6. Упакуйте и соберите приложение
Во время разработки вы можете использовать директорию электрон для запуска приложения.Если вы хотите опубликовать приложение, вы не можете этого сделать.Нужно собрать приложение в исполняемый файл для соответствующей платформы. Рекомендуется здесьelectron-packagerмодули для создания приложений.
npm install electron-packager -g
пакетная команда
electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]
Возьмем пример упаковки в платформу Windows x64.
electron-packager ./ fluentApp --platform=win32 --out=../../build --arch=x64 --electron-version=1.4.13 --icon=./icon.ico --ignore=/"(cache|db.json)" --overwrite
После завершения упаковки будет создана папка fluentApp-win32-x64 с исполняемым файлом fluentApp.exe, который можно открыть напрямую.
Если вы хотите упаковать каталог fluentApp-win32-x64 в исполняемый файл установочного пакета, вы можете использоватьgrunt-electron-installerПакет.
npm install grunt --save
npm install grunt-electron-installer --save
Создайте файл Gruntfile.js
const grunt = require("grunt");
grunt.config.init({
pkg: grunt.file.readJSON('package.json'),
'create-windows-installer': {
x64: {
appDirectory: './fluentApp-win32-x64',
authors: 'maikuraki.',
exe: 'fluentApp.exe',
description:"music app",
}
}
});
grunt.loadNpmTasks('grunt-electron-installer');
grunt.registerTask('default', ['create-windows-installer']);
Для установки приложения с помощью установочного пакета необходимо создать ярлык приложения на рабочем столе и удалить его. Добавьте соответствующую обработку в main.js
let handleStartupEvent = () => {
let install = () => {
let updateDotExe = path.resolve(path.dirname(process.execPath), '..', 'update.exe');
let target = path.basename(process.execPath);
let child = child_process.spawn(updateDotExe, ["--createShortcut", target], {detached: true});
child.on('close', (code) => {
app.quit();
});
};
let uninstall = () => {
let updateDotExe = path.resolve(path.dirname(process.execPath), '..', 'update.exe');
let target = path.basename(process.execPath);
let child = child_process.spawn(updateDotExe, ["--removeShortcut", target], {detached: true});
child.on('close', (code) => {
app.quit();
});
};
if (process.platform !== 'win32') {
return false;
}
let squirrelCommand = process.argv[1];
switch (squirrelCommand) {
case '--squirrel-install':
case '--squirrel-updated':
install();
return true;
case '--squirrel-uninstall':
uninstall();
app.quit();
return true;
case '--squirrel-obsolete':
app.quit();
return true;
};
};
if (handleStartupEvent()) {
return;
}
Для построения других платформ обратитесь к документации Electron.Linux,macOS