Создайте проигрыватель прыгающих звуковых волн (Electron+Nodejs+React)

Node.js JavaScript React.js Electron

Electron позволяет нам использовать html, css, javascript для создания кроссплатформенных (Windows, macOS, Linux) настольных приложений. Давайте реализуем проигрыватель, который поддерживает воспроизведение онлайн-музыки и локальной музыки через Electron+Nodejs+React. Стиль дизайна плеера — Fluent Design для Windows, он может работать как на win10, так и на macOS (если сборка и упаковка должны быть упакованы по-разному на разных платформах), на Linux не тестировался.
В этой статье в основном описываются некоторые трудности и ключевые моменты, с которыми пришлось столкнуться при разработке. Если вы хотите узнать подробности, вы можете просмотреть код, чтобы просмотреть его.

адрес проекта:GitHub.com/buy-core-AKi/…

1. Предварительная подготовка

  1. Electron
    Рекомендуется использовать зеркало Taobao, оригинальное зеркало сложно установить даже с лестницей.
npm install -g package --registry=https://registry.npm.taobao.org
  1. React(последняя версия подходит)
  2. React-Router4(для перехода по страницам)
  3. Redux/react-redux(контейнер состояния js, хранящий глобальное состояние и данные)
  4. Express(для создания сервисов API)
  5. LowDB(LowDB — это основанная на узлах база данных файлов JSON, сервер не требуется, а память и хранилище на жестком диске используются для кэширования данных игрока)
  6. Облачный API NetEase

2. Создайте облачный API NetEase

  1. Вытащите git-проект NetEase Cloud API
git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git
  1. Введите каталог для запуска службы
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) с характеристиками модульной маршрутизации. Основные аудиооперации выполняются на аудиоузлах, которые соединены вместе, чтобы сформировать граф маршрутизации аудио.
Аудиоузлы соединяются друг с другом через свои входы и выходы, образуя цепочку или простую сеть. Как правило, эта цепочка или сеть начинается с одного или нескольких источников звука.
Простой и типичный процесс веб-аудио выглядит следующим образом:

  1. Создайте звуковой контекст
  2. Создавайте источники в аудиоконтексте — например, осцилляторы, потоки
  3. Создание узлов эффектов, таких как реверберация, биквадрат, панорамирование, компрессор
  4. Выберите место для звука, например системные динамики.
  5. Подключите источник к эффекту и выведите эффект в место назначения.

С этими концепциями можно начинать последующую работу. После получения сигнала вам нужно использовать холст для вывода сигнала, поэтому сначала создайте метку.

//获取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));

В настоящее время форма волны однородна только по размаху, но ключевые точки напрямую соединены прямыми линиями, и нет ощущения волны, поэтому далее вам нужно использовать кривую для соединения двух соседних точек.
Есть много способов соединить серию дискретных точек кривыми.кубическая функциярешать.

Образ кубической функции представляет собой параболу регрессии кривой, Когда ∆> 0, есть максимальное значение и минимальное значение, и два значения связаны кривой. Таким образом, волны можно получить, нарисовав плавную кривую, приняв за крайние значения две соседние точки ключевой точки.

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. Интерфейс игрока

Интерфейс списка игроков разделен на три части:

  1. Четыре вкладки на главной странице — это рекомендуемые плейлисты, последние синглы, новые диски и местные песни.
  2. Интерфейс плейлиста и сведений об эксперте, стиль обеих страниц сведений одинаков.

3. Интерфейс поиска

Нажимаются плейлисты и альбомы на главной странице, чтобы ввести сведения, и использовать 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