Musictonicのように、ミュージシャンの名前で検索したら、それに関連したyoutube動画を延々と再生してくれるものがデスクトップアプリケーションであったらいいなと思って、electronの勉強も兼ねて作ってみました。
https://github.com/makotot/tubeheadが今回作ってみたものです。
検索ボックスから適当に検索すると、検索ワードに関連する動画の再生リストが出来て、それをループで再生するだけのアプリです。
あとは、10件まで検索履歴を保持するくらいしかやってません。
electronのバージョンは、0.36.0です。
利用しているライブラリ
-
React
- Viewを富豪的に扱いたかったので、利用しました。 -
Flux
- Fluxフレームワークが色々出てるみたいですが、今回はそこまでは追いつかなかったので、Facebookのfluxを使いました。 -
Lodash
- 部分的に配列操作などで利用してます。 -
express
- electronのウィンドウで利用する静的ファイルをexpressで配信するようにしてます。 -
react-fontawesome
- アイコンで利用してます。
メインプロセス
アプリケーションサーバ
メインプロセス側で何かをやるということもないので、特にどうということも無いですが、
よく目にするメインプロセスのコードと若干違うのは、
appIcon.window.loadURL('http://127.0.0.1:'+ port);
となってる部分で、expressでアプリケーションサーバを立ち上げて、そのURLをloadURL
に渡しています。
Quick Startのように
mainWindow.loadURL('file://' + __dirname + '/index.html');
という感じだと、YouTubeのAPIで制限されているのか、ほとんどの動画が正常に読み込むことが出来ないようで、以下のようにexpressを使ってます。
var express = require('express');
var webApp = express();
var port = 3838;
webApp.get('/', function (req, res) {
res.sendFile(__dirname + '/dist/index.html');
});
webApp.use('/css', express.static(__dirname + '/dist/css'));
webApp.use('/js', express.static(__dirname + '/dist/js'));
var server = webApp.listen(port, function () {
console.log('app listening at http://%s:%s', server.address().host, server.address().port);
});
ショートカット
実際にパッケージを作ったあとに分かったんですが、検索ボックスでCommand+X
とかCommand+A
とかショートカットが全然使えなくて、開発中は全然問題なく使えてたのに何でだろうって思ったら、以下のようにアプリケーション側でキーバインドを設定しなければいけないということでした。
var template = [
{
label: 'Application',
submenu: [
{
label: 'Quit',
accelerator: 'Command+Q',
click: function () {
app.quit();
}
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:'
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:'
}
]
}
]
var menu = Menu.setApplicationMenu(Menu.buildFromTemplate(template))
Electron - Enable copy and paste
レンダラプロセス
Babel、React、Fluxなどは殆ど触ったことがない状態で、折角だから使いたいと導入して始めたので結構ハマったりするかなーと不安もありつつ進めたんですが、一番厄介だったのは、そういうところよりもYouTube APIでした。
再生リストをYouTubeのAPIから作れることを最初気づかずに、再生リストのようなものを作る方法を考えるのにだいぶ時間を無駄にしたりしました。
browserify
ちょっと困ったのは、アプリ内のリンクを押下時に通常のウェブブラウザで開きたいってなったときに、browserifyを使っていると、
require('shell')
のようなelectronのモジュールがcannot find module 'shell'
となってしまって読み込めない点で、HTML側にインラインのscriptで
const shell = require('electron').shell;
とすることで、解消しました。
window.require('remote')
とすることでも解決するようなので、グローバルのrequire()
であればモジュールを問題なく読み込めるようです。
YouTube動画の読み込み失敗時に何も表示されないとか、アプリの機能をもうちょっと充実なり整理なりしたいとか、ReactやFluxについて理解が足りてなくてコードの整理が必要なとことか、不足してる点はあるものの、全体的にはES6 + React で楽しく作れたと思います。
パッケージング + リリース
NW.js/ElectronアプリをGitHub Releaseで公開する[自動デプロイ] | Web Scratchを参考に、タグがついたコミットでtravis-ciからリリースするようにしました。
after_success:
- test ${TRAVIS_TAG} && npm run package
travis-ci のテストが通ったら、npm scriptsのpackage
を実行するようにしてます。
package
の中身はこれで、
"package": "gulp package && zip tubehead-darwin-x64.zip -r ./tubehead-darwin-x64"
Gulpのpackage
タスクを実行した後に、zip
コマンドでリリースするzipファイルを作ってます。package
タスクは、electron-packagerでパッケージを作るだけです。
gulp.task('package', ['build'], function (done) {
packager({
dir: './',
out: './',
name: 'tubehead',
arch: 'x64',
platform: 'darwin',
version: '0.36.0',
icon: './icons/tubehead.icns',
overwrite: true,
asar: true,
prune: true,
'app-version': require('./package.json').version,
ignore: [
'src',
'node_modules/babel-eslint',
'node_modules/babel-plugin-transform-object-assign',
'node_modules/babel-preset-es2015',
'node_modules/babel-preset-react',
'node_modules/babelify',
'node_modules/cssnano',
'node_modules/del',
'node_modules/electron-debug',
'node_modules/electron-connect',
'node_modules/eslint-config-makotot',
'node_modules/eslint-plugin-react',
'node_modules/gulp',
'node_modules/gulp-babel',
'node_modules/gulp-eslint',
'node_modules/gulp-if',
'node_modules/gulp-plumber',
'node_modules/gulp-postcss',
'node_modules/gulp-sass',
'node_modules/gulp-watch',
'node_modules/postcss-calc',
'node_modules/postcss-reporter',
'node_modules/rucksack-css',
'node_modules/run-sequence',
'node_modules/stylelint',
'node_modules/stylelint-config-makotot',
'node_modules/vinyl-buffer',
'node_modules/vinyl-source-stream',
'node_modules/watchify'
]
}, function (err, path) {
if (err) {
console.error(err);
}
console.log(path);
done();
});
});
全然まとめられてないですが、こんな感じで作ってみました。