Статьи

Создайте приложение для потоковой передачи музыки с помощью Electron, React и ES6

Эта статья была рецензирована Марком Брауном , Дэном Принсом и Бруно Мота . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

Electron — это среда, разработанная GitHub, которая позволяет вам использовать свои навыки веб-дизайна для создания изящных кроссплатформенных настольных приложений. В этом уроке я продемонстрирую, как объединить возможности Electron с React, ES6 и Soundcloud API для создания стильного приложения для потоковой передачи музыки, которое будет передавать ваши любимые мелодии прямо на ваш рабочий стол. Я также продемонстрирую, как вы можете упаковать приложение и распространять его в виде переносимого пакета для конкретной ОС.

Это руководство предполагает базовые знания React. Если вы хотите ознакомиться с учебником перед началом работы, ознакомьтесь с нашим руководством по началу работы. Код для этого урока доступен в нашем репозитории GitHub .

Обзор того, что мы строим

Вот как будет выглядеть наше приложение:

Soundcloud player

Мы будем использовать React для создания пользовательского интерфейса, SoundCloud API для получения треков и Electron для запуска приложения в среде, подобной браузеру. Как вы можете видеть, в нем будет поле поиска для поиска воспроизводимой музыки, а результаты будут аудиоплеерами для каждого из результатов. Очень похоже на то, что вы видите на сайте SoundCloud.

Если вы хотите следовать, убедитесь, что у вас есть учетная запись SoundCloud и приложение SoundCloud . Обратите внимание на ключ API, потому что мы будем использовать его позже.

Добавление электронных и других зависимостей

Начните с клонирования репозитория Electron Quick Start на Github в папку под названием soundcloud-player :

 git clone https://github.com/atom/electron-quick-start soundcloud-player 

Войдите в эту папку, затем откройте файл package.json и добавьте следующие зависимости dev:

 "devDependencies": { "electron-prebuilt": "^1.2.0", "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.5.0", "babelify": "^7.3.0", "browserify": "^13.0.1" } 

Вот краткое описание каждой упаковки:

  • электронная сборка — устанавливает готовые двоичные файлы Electron для использования в командной строке.
  • babel-preset-es2015 — используется для преобразования кода ES6 в код ES5 (который может работать в любом современном браузере).
  • babel-preset-реакции — используется для преобразования кода JSX в JavaScript.
  • babelify — Вавилонский трансформер для Browserify.
  • browserify — создает пакет, который вы можете использовать для браузера, в одном <script> .

Добавьте следующее в dependencies :

 "dependencies": { "node-soundcloud": "0.0.5", "react": "^0.14.8", "react-dom": "^0.14.8", "react-loading": "0.0.9", "react-soundplayer": "^0.3.6" } 

Вот краткое описание каждой упаковки:

  • node-soundcloud — позволяет нам выполнять вызовы к SoundCloud API.
  • реагировать — библиотека Реакт. Позволяет нам создавать компоненты пользовательского интерфейса.
  • response-dom — позволяет нам отображать компоненты React в DOM.
  • ответная загрузка — используется в качестве индикатора загрузки приложения.
  • response-soundplayer — компонент React, который позволяет нам легко создавать собственные аудиоплееры для SoundCloud.

После того, как вы добавили dependencies и devDependencies , выполните npm install чтобы установить их все.

Наконец, добавьте сценарии для компиляции и запуска приложения. Это позволит вам запустить npm run compile для компиляции приложения, и npm start его запускать.

 "scripts": { "compile": "browserify -t [ babelify --presets [ react es2015 ] ] src/app.js -o js/app.js", "start": "electron main.js" } 

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

 { "name": "electron-soundcloud-player", "version": "1.0.0", "description": "Plays music from SoundCloud", "main": "main.js", "scripts": { "start": "electron main.js", "compile": "browserify -t [ babelify --presets [ react es2015 ] ] src/app.js -o js/app.js" }, "author": , ... } 

В общем, ваш файл package.json теперь должен выглядеть следующим образом .

Структура проекта

Вот как мы собираемся структурировать наш проект:

 . ├── css │ └── style.css ├── index.html ├── js ├── main.js ├── package.json ├── README.md └── src ├── app.js └── components ├── ProgressSoundPlayer.js └── Track.js 

Давайте создадим эти недостающие каталоги:

 mkdir -p css js src/components 

И файлы, которые они должны содержать:

 touch css/style.css src/app.js src/components/ProgressSoundPlayer.js src/components/Track.js 

Каталог js будет содержать скомпилированный JavaScript для нашего приложения, каталог css — стили нашего приложения, а каталог src — компоненты приложения.

Из файлов, которые мы извлекли из репозитория Electron Quick Start, мы можем удалить следующее:

 rm renderer.js LICENSE.md 

Что оставляет main.js и « ìndex.html . Из этих двух файлов именно main.js отвечает за создание нового окна браузера, в котором будет работать приложение. Однако нам нужно внести в него пару изменений. Сначала настройте ширину в строке 13:

 mainWindow = new BrowserWindow({width: 1000, height: 600}) 

Во-вторых, удалите следующее из строки 19 (иначе наше приложение инициализирует показ инструментов dev):

 mainWindow.webContents.openDevTools() 

Когда main.js создает новое окно браузера, оно загружает index.html (мы рассмотрим этот файл позже в руководстве). Отсюда приложение будет работать так же, как и в окне браузера.

Сборка приложения

Компонент Трек

Далее давайте создадим компонент Track для аудиоплеера (в src / components / Track.js ).

Сначала нам потребуется React и несколько компонентов, предоставляемых React SoundPlayer:

 import React, {Component} from 'react'; import { PlayButton, Progress, Timer } from 'react-soundplayer/components'; 

Обратите внимание, что с помощью этого синтаксиса мы эффективно извлекаем класс Component из React. Как следует из названия, Component используется для создания новых компонентов.

Затем мы создаем новый компонент с именем Track и назначаем ему метод render . Обратите внимание, что мы экспортируем этот класс, чтобы позже его можно было импортировать в другой файл.

 export default class Track extends Component { render() { ... } } 

Внутри метода render мы извлекаем информацию о текущей звуковой дорожке из полученных им props а затем присваиваем их собственным переменным, используя назначение деструктурирования . Таким образом, мы можем использовать track вместо this.props.track .

 const { track, soundCloudAudio, playing, seeking, currentTime, duration } = this.props; 

Затем мы рассчитываем текущий прогресс трека:

 const currentProgress = currentTime / duration * 100 || 0; 

И вернуть пользовательский интерфейс компонента.

 return ( <div className="player"> <PlayButton className="orange-button" soundCloudAudio={soundCloudAudio} playing={playing} seeking={seeking} /> <Timer duration={duration} className="timer" soundCloudAudio={soundCloudAudio} currentTime={currentTime} /> <div className="track-info"> <h2 className="track-title">{track && track.title}</h2> <h3 className="track-user">{track && track.user && track.user.username}</h3> </div> <Progress className="progress-container" innerClassName="progress" soundCloudAudio={soundCloudAudio} value={currentProgress} /> </div> ); 

Как видно из приведенного выше кода, у нас довольно стандартный аудиоплеер. Он имеет кнопку воспроизведения, таймер (который показывает текущее время / продолжительность воспроизведения), заголовок и имя пользователя, который загрузил песню, и индикатор выполнения.

Вот как выглядит полный компонент .

Компонент ProgressSoundPlayer

Давайте перейдем к компоненту ProgressSoundPlayer ( src / components / ProgressSoundPlayer.js ). Это будет служить оболочкой для компонента Track созданного выше.

Помимо React и компонента Track , нам также необходимо импортировать SoundPlayerContainer . SoundPlayerContainer — это контейнер более высокого уровня, который SoundPlayerContainer реквизиты, необходимые для создания аудиоплеера.

 import React, {Component, PropTypes} from 'react'; import { SoundPlayerContainer } from 'react-soundplayer/addons'; import Track from './Track'; 

Далее мы создадим компонент ProgressSoundPlayer . Все это делает визуализацию SoundPlayerContainer который оборачивает компонент Track . Обратите внимание, что нам не нужно ничего передавать компоненту Track поскольку SoundPlayerContainer автоматически делает это для нас за кулисами. Однако нам нужно передать resolveUrl и clientId качестве реквизитов для SoundPlayerContainer .

 export default class ProgressSoundPlayer extends Component { render() { const {resolveUrl, clientId} = this.props; return ( <SoundPlayerContainer resolveUrl={resolveUrl} clientId={clientId}> <Track /> </SoundPlayerContainer> ); } } 

Наконец, мы указываем реквизиты, необходимые для этого компонента. В этом случае мы требуем, чтобы resolveUrl и clientId передавались при визуализации этого компонента.

 ProgressSoundPlayer.propTypes = { resolveUrl: PropTypes.string.isRequired, clientId: PropTypes.string.isRequired }; 

Указание propTypes — это хорошая практика . Это вызовет предупреждения в консоли инструментов разработчика, если реквизиты, которые требует компонент, не будут переданы ему. Обратите внимание, что нам не нужно было делать это ранее в компоненте Track поскольку SoundPlayerContainer отвечает за передачу всех необходимых реквизитов.

Вот как выглядит полный компонент .

Главный компонент

Основной файл — это src / app.js. Это отвечает за отображение полного пользовательского интерфейса приложения, то есть поля поиска и аудиоплееров.

Разбивая код, мы сначала импортируем все библиотеки, которые нам нужны. Каждый из них был упомянут ранее в разделе зависимостей (кроме созданного нами ProgressSoundPlayer ).

 import React, {Component} from 'react'; import ReactDOM from 'react-dom'; import ProgressSoundPlayer from './components/ProgressSoundPlayer'; import SC from 'node-soundcloud'; import Loading from 'react-loading'; 

Добавьте свой идентификатор клиента SoundCloud:

 var client_id = 'YOUR SOUNDCLOUD APP ID'; 

Обратите внимание, что вы можете использовать что-то вроде dotenv, так что вам не придется отправлять этот фрагмент данных в ваш репозиторий.

Инициализируйте библиотеку node-soundcloud, указав объект, содержащий ваш идентификатор клиента SoundCloud.

 SC.init({ id: client_id }); 

Создайте Main компонент:

 class Main extends Component { ... } 

Внутри класса определите метод конструктора. Это позволяет нам добавить код для инициализации этого класса. Внутри метода constructor мы затем вызываем super() для вызова конструктора класса Component и любого кода инициализации, который есть в классе Component .

 constructor(props){ super(); } 

Далее мы устанавливаем состояние приложения по умолчанию:

  • query является поисковым запросом по умолчанию.
  • hasResults используется для отслеживания того, имеет ли компонент какие-либо результаты от API или нет.
  • searchResults хранит текущие результаты поиска.
  • isLoading используется для отслеживания, получает ли приложение в настоящее время результаты из API или нет. Когда для этого параметра установлено значение true , счетчик становится видимым, чтобы указать, что что-то происходит.
 this.state = { query: '', hasResults: false, searchResults: [], isLoading: false }; 

Затем идет метод handleTextChange . Это используется для обновления значения query в state а также вызывает метод search если нажата клавиша Enter . Этот метод onKeyUp когда событие onKeyUp запускается в поле поиска.

 handleTextChange(event){ this.setState({ query: event.target.value }); if(event.key === 'Enter'){ this.search.call(this); } } 

После этого у нас есть метод search , который отправляет запрос в API SoundCloud и обрабатывает ответ. Сначала он устанавливает для состояния isLoading значение true так что счетчик становится видимым. Затем он выполняет запрос GET к конечной точке tracks API SoundCloud. Эта конечная точка принимает запрос в качестве обязательного параметра, но мы также передаем дополнительный параметр embeddable_by чтобы указать, что мы хотим выбирать только те дорожки, которые могут быть добавлены всеми. Как только мы получаем ответ, мы проверяем, есть ли какие-либо ошибки, и если нет, мы обновляем state с результатами поиска. В этот момент компонент должен повторно выполнить рендеринг, чтобы показать результаты поиска.

 search(){ this.setState({ isLoading: true }); SC.get('/tracks', { q: this.state.query, embeddable_by: 'all' }, (err, tracks) => { if(!err){ this.setState({ hasResults: true, searchResults: tracks, isLoading: false }); } }); } 

Метод render визуализирует пользовательский интерфейс компонента. Он содержит поле поиска для ввода названия песни или исполнителя и кнопку для отправки поиска. Он также содержит несколько условных операторов для рендеринга компонента Loading (который становится видимым только тогда, когда isLoading имеет истинное значение) и результаты поиска (которые отображаются только в том случае, если hasResults — true, но isLoading — false).

 render(){ return ( <div> <h1>Electron SoundCloud Player</h1> <input type="search" onKeyUp={this.handleTextChange.bind(this)} className="search-field" placeholder="Enter song name or artist..." /> <button className="search-button" onClick={this.search.bind(this)}>Search</button> <div className="center"> {this.state.isLoading && <Loading type="bars" color="#FFB935" />} </div> {this.state.hasResults && !this.state.isLoading ? this.renderSearchResults.call(this) : this.renderNoSearchResults.call(this)} </div> ); } 

Обратите внимание, что мы должны использовать bind() для метода handleTextChange и call() для методов renderSearchResults и renderNoSearchResults . Это связано с тем, что методы в React не привязываются автоматически при использовании синтаксиса класса ES6. Кроме того, вы можете использовать что-то вроде decko для автоматической привязки определенных методов к классу. Например:

 import { bind } from 'decko'; // ... @bind handleTextChange(event){ this.setState({ query: event.target.value }); if(event.key == 'Enter'){ this.search(); } } 

Далее у нас есть метод, который вызывается по умолчанию, так как при первом визуализации компонента результатов поиска нет.

 renderNoSearchResults(){ return ( <div id="no-results"></div> ); } 

И метод, который вызывается, когда есть результаты поиска для отображения. Это вызывает метод map в searchResults для циклического просмотра всех результатов и выполнения функции renderPlayer для каждой итерации.

 renderSearchResults(){ return ( <div id="search-results"> {this.state.searchResults.map(this.renderPlayer.bind(this))} </div> ); } 

Функция renderPlayer принимает отдельный объект track качестве аргумента. Мы используем его как источник key и реквизитов resolveUrl . Если вы работали с React в прошлом, вы уже знаете, что при использовании метода map для отображения списка нам всегда нужно передавать уникальный key иначе React будет жаловаться. Два других параметра: clientId и resolveUrl требуются для компонента ProgressSoundPlayer . clientId — это ключ API SoundCloud, который вы определили ранее, а resolveUrl — уникальный URL-адрес, относящийся к этой конкретной аудиодорожке. Это тот же URL-адрес, который вы получаете при посещении страницы определенной звуковой дорожки в SoundCloud.

 renderPlayer(track){ return ( <ProgressSoundPlayer key={track.id} clientId={client_id} resolveUrl={track.permalink_url} /> ); } 

Наконец, мы рендерим компонент в DOM.

 var main = document.getElementById('main'); ReactDOM.render(<Main />, main); 

Вот как выглядит полный компонент .

Стилизация приложения

Стили для приложения находятся в css / style.css . Таблица стилей содержит объявления стилей для каждого из компонентов (кнопка воспроизведения, кнопка поиска, индикатор выполнения и другие элементы, которые мы использовали).

Индексный файл

Как упоминалось ранее, когда файл Electron main.js создает новое окно браузера, он загружает index.html . Здесь нет ничего необычного, только ваш стандартный файл HTML с таблицей стилей и файлом JavaScript.

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Electron Soundcloud Player</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <div id="main"></div> <script src="js/app.js"></script> </body> </html> 

Компиляция приложения

Внутри среды Electron вам могут потребоваться такие вещи, как в стандартном приложении Node.js. Это означает, что вы можете использовать что-то вроде:

 import fs from 'fs'; const buffer = fs.readFileSync(`${__dirname}/index.html`); console.log(buffer.toString()); 

И Электрон с радостью проведет это для вас.

Но так как мы использовали ES6 и JSX для написания приложения, мы не можем использовать эту функцию. У нас есть возможность использовать Babel для преобразования кода JSX и ES6 в код, читаемый браузером (ES5). Ранее в разделе зависимостей мы установили все необходимые пакеты, чтобы это работало. Теперь все, что вам нужно сделать, это выполнить следующую команду, чтобы сгенерировать основной файл JavaScript:

 npm run compile 

Запуск и упаковка приложения

Вы можете запустить приложение, выполнив npm start в корне вашего проекта. Но это было бы совсем не весело. Вы также можете просто запустить приложение в браузере и назвать его днем. Вместо этого мы упакуем приложение в одну папку. Эта папка будет содержать все файлы, необходимые для запуска приложения. Затем вы можете создать архив из этой папки для распространения вашего приложения.

Для упаковки приложения нам необходимо установить электронный упаковщик:

 npm install electron-packager -g 

После установки вы можете перейти на один уровень вверх от корня вашего проекта и выполнить следующую команду:

 electron-packager ./soundcloud-player SoundCloudPlayer --version=1.2.4 --platform=linux --out=/home/jim/Desktop --arch=all --ignore="(node_modules|src)" 

Разбивая эту команду, мы имеем:

  • ./soundcloud-player — каталог ./soundcloud-player проекта.
  • SoundCloudPlayer — название вашего приложения.
  • --version=1.2.0 — версия Electron, которую вы хотите использовать. На момент написания этой статьи она была версии 1.2.0, поэтому, если вы читаете ее позже, вы, вероятно, сможете использовать последнюю версию, если в API нет серьезных изменений.
  • --platform=linux — платформа, на которой вы хотите выполнить развертывание. В этом случае я использовал Linux, так как я нахожусь на Ubuntu. Если вы хотите выполнить упаковку для всех основных платформ (Windows, OSX, Linux), вы можете использовать опцию --all .
  • --out=/home/wern/Desktop — выходной каталог. Это где пакет будет создан.
  • --arch=all — архитектура процессора. Мы указали all что означает, что он будет собираться как для 32-битных, так и для 64-битных операционных систем.
  • --ignore="(node_modules|src)" поскольку приложение будет упаковано с Electron и Chrome, его размер будет довольно большим. Единственное, что мы можем сделать, чтобы предотвратить его дальнейшее раздувание, это исключить все файлы, которые нам не нужны. Поскольку мы уже компилируем в один файл JavaScript, нам больше не нужно ничего внутри node_modules и каталога src .

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

Куда пойти отсюда

В этом уроке мы создали довольно простое приложение Electron. Это работает, но мы все еще можем улучшить это. Вот несколько предложений по улучшению, которые можно сделать:

  • Разбейте результаты поиска.
  • Добавьте функцию для автоматической остановки воспроизведения дорожки, когда пользователь выполняет поиск.
  • Удалите кнопку и вызовите поиск непосредственно из метода handleTextChange .
  • Упакуйте приложение в архив asar, чтобы не показывать ваш исходный код всем.
  • Если вы серьезно относитесь к распространению своего приложения по всему миру. Вы можете создать установщик для всех основных платформ (Windows, OSX и Linux). Есть проект под названием « Электронный строитель», который позволяет вам сделать это.

Чтобы получить больше вдохновения, загляните в приложение SoundNode — проект с открытым исходным кодом для поддержки SoundCloud для настольных Mac, Windows и Linux.

Если вы хотите узнать больше об Electron и создании настольных приложений с использованием веб-технологий в целом, я рекомендую вам ознакомиться со следующими ресурсами:

Вывод

Из этого урока мы узнали, как создать стильное и стильное кроссплатформенное приложение с использованием Electron. Что лучше, так это то, что мы сделали это, используя наши навыки веб-разработки. Мы также видели, как легко упаковать и распространить это приложение в виде пакета для конкретной ОС.

Я хотел бы услышать о приложениях, которые вы создаете с помощью Electron, в комментариях ниже.