本文由Mark Brown , Dan Prince和Bruno Mota进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
Electron由GitHub开发,是一个框架,可让您利用自己的Web设计技能来构建流畅的,跨平台的桌面应用程序。 在本教程中,我将演示如何将Electron与React,ES6和Soundcloud API的功能结合起来,创建一个时尚的音乐流应用程序,该应用程序将您喜欢的音乐直接流到您的桌面。 我还将演示如何打包应用程序并将其作为可移植的,特定于操作系统的捆绑包进行分发。
本教程假定您具有React的基本知识。 如果您想在开始之前入门,请查看我们的入门教程 。 可从我们的GitHub存储库中获得本教程的代码。
我们正在建设的概述
这是我们的应用程序的外观:
我们将使用React来创建UI,使用SoundCloud API来获取曲目,使用Electron来允许应用程序在类似浏览器的环境中运行。 如您所见,它将有一个搜索字段,用于搜索要播放的音乐,并且结果将是每个结果的音频播放器。 就像您在SoundCloud网站上看到的一样。
如果要继续进行,请确保您具有SoundCloud帐户和SoundCloud应用程序 。 请注意API密钥,因为稍后我们将使用它。
添加电子和其他依赖性
首先将Github上的Electron Quick Start repo克隆到名为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"
}
这是每个程序包的简要说明:
- electronic-prebuilt —安装电子版的预编译二进制文件以供命令行使用。
- babel-preset-es2015 —用于将ES6代码转换为ES5代码(可以在任何现代浏览器中运行)。
- babel-preset- react-用于将JSX代码转换为JavaScript。
- babelify-用于Browserify的Babel转换器。
- browserify -builds捆绑你能容纳在一个单一的浏览器中
<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。
- react — React库。 允许我们创建UI组件。
- react- dom-使我们能够将React组件呈现到DOM中。
- react- loading-用作应用程序的加载指示器。
- react-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": "Wern Ancheta",
...
}
总而言之,您的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快速入门仓库中提取的文件中,我们可以删除以下内容:
rm renderer.js LICENSE.md
main.js
和ìndex.html
。 在这两个文件中, main.js
负责创建将在其中运行应用程序的新浏览器窗口。 但是,我们需要对其进行一些更改。 首先调整第13行的宽度:
mainWindow = new BrowserWindow({width: 1000, height: 600})
其次,从第19行删除以下内容(否则我们的应用程序将初始化显示开发工具):
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';
注意,通过使用这种语法,我们可以有效地从React中提取Component
类。 顾名思义, 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;
并返回组件的UI。
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
是一个更高级别的容器,用于传播其子项与构建音频播放器所需的道具。
import React, {Component, PropTypes} from 'react';
import { SoundPlayerContainer } from 'react-soundplayer/addons';
import Track from './Track';
接下来,我们将创建ProgressSoundPlayer
组件。 所有这些操作就是渲染包装了Track
组件的SoundPlayerContainer
。 请注意,由于SoundPlayerContainer
自动为我们执行此操作,因此我们无需将任何内容传递给Track
组件。 但是,我们确实需要传递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
是进入的好习惯 。 如果组件所需的道具未传递给开发工具控制台,则会触发警告。 请注意,由于SoundPlayerContainer
负责传递所有必要的道具,因此我们不必在Track
组件的前面进行此操作。
这是完整组件的外观 。
主要成分
主文件是src / app.js。 这负责呈现应用程序的完整UI(即搜索字段)和音频播放器。
分解代码,我们首先导入所需的所有库。 其中的每一个都已在前面的“依赖项”部分中提到(我们创建的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客户端ID:
var client_id = 'YOUR SOUNDCLOUD APP ID';
请注意,您可以使用诸如dotenv之类的东西,因此您不必将这些数据推送到存储库中。
通过提供包含您的SoundCloud客户端ID的对象来初始化node-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
方法。 这用于更新state
下的query
值,如果按下Enter键,还调用search
方法。 当在搜索字段上触发onKeyUp
事件时,将调用此方法。
handleTextChange(event){
this.setState({
query: event.target.value
});
if(event.key === 'Enter'){
this.search.call(this);
}
}
之后,我们有了search
方法,该方法将查询发送到SoundCloud API并处理响应。 首先,它将isLoading
状态设置为true
以便微调器可见。 然后,它向SoundCloud API的tracks
端点发出GET
请求。 该端点接受查询作为其必需参数,但是我们还传递了一个额外的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
方法呈现组件的UI。 它包含一个用于输入歌曲名称或歌手的搜索字段,以及一个用于提交搜索的按钮。 它还包含一些条件语句,用于呈现Loading
组件(仅当isLoading
具有真实值时才可见)和搜索结果(仅当hasResults
为true但isLoading
为假时才显示)。
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
方法。 这是因为使用ES6类语法时,React中的方法不会自动绑定。 另外,您也可以使用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>
);
}
以及有要显示的搜索结果时调用的方法。 这将在searchResults
调用map
方法以遍历所有结果,并为每次迭代执行renderPlayer
函数。
renderSearchResults(){
return (
<div id="search-results">
{this.state.searchResults.map(this.renderPlayer.bind(this))}
</div>
);
}
renderPlayer
函数接受单个track
对象作为其参数。 我们将其用作key
和resolveUrl
道具的来源。 如果您过去使用过React,那么您已经知道使用map
方法呈现列表时,我们总是必须传递一个唯一的key
,否则React会抱怨。 ProgressSoundPlayer
组件需要其他两个道具: clientId
和resolveUrl
。 clientId
是您之前定义的SoundCloud API密钥, resolveUrl
是引用该特定音轨的唯一URL。 它是访问页面时在SoundCloud上获取特定音轨的URL。
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
。 这里没有花哨的东西,只有带有样式表和JavaScript文件的标准HTML文件。
<!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());
Electron会很乐意为您运行它。
但是,由于我们已经使用ES6和JSX编写了该应用程序,因此我们无法真正使用此功能。 我们要做的一个选择是使用Babel将JSX和ES6代码转换为浏览器(ES5)可读的代码。 在“ 依赖关系”部分的前面,我们安装了所有必需的软件包才能使其正常运行。 因此,您现在要做的就是执行以下命令来生成主要的JavaScript文件:
npm run compile
运行和打包应用
您可以通过在项目的根目录中执行npm start
来运行该应用程序。 但这根本没有任何乐趣。 您不妨在浏览器中运行该应用程序,然后将其命名为“一天”。 相反,我们要做的是将应用程序打包到一个文件夹中。 该文件夹将包含运行该应用程序所需的所有文件。 然后,您可以从该文件夹创建一个存档,以分发您的应用程序。
要打包该应用程序,我们需要安装electron-packager:
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
您的项目目录。 -
SoundCloudPlayer
您的应用程序名称。 -
--version=1.2.0
您要使用的Electron版本。 在撰写本文时,它的版本为1.2.0,因此,如果您稍后再阅读,则只要API中没有重大更改,就可以使用最新版本。 -
--platform=linux
您要部署到的平台。 在这种情况下,因为我在Ubuntu上,所以我使用Linux。 但是,如果要打包用于所有主要平台(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应用程序 -一个开源项目,以支持适用于台式机Mac,Windows和Linux的SoundCloud。
如果您想了解有关Electron的更多信息以及通常使用Web技术构建桌面应用程序的知识,建议您查看以下资源:
- NW.js-以前称为node-webkit。 允许您直接从DOM调用节点模块。 如果您正在寻找电子替代品,则可能需要检查一下。
- 使用Electron创建跨平台桌面节点应用程序 —最近的SitePoint教程。
- 电子视频教程
- 电子官方文件
结论
在本教程中,我们学习了如何使用Electron创建一个时尚,时尚的跨平台应用程序。 更好的是,我们通过利用现有的Web开发技能来做到这一点。 我们还看到了打包和分发此应用程序为特定于操作系统的捆绑包是多么容易。
我很想在下面的评论中听到您使用Electron构建的应用程序。
From: https://www.sitepoint.com/music-streaming-app-electron-react-es6/