electronでローカルファイルから音声再生を試してみる


[JavaScriptで音声再生完了タイミングを取得したい。](http://qiita.com/icbmuma/items/3270ed25a3f3221125c9)
で音は鳴らせたので、それを試しにelectronで動かしてみる。
XMLHttpRequestではなくて
nodeのreadFileでローカルファイルを読んで音声鳴らしたい。

# electronのインストール

```
npm install -g electron
```

# プロジェクトの作成と初期設定

```
mkdir electron-test
cd electron-test
npm init -y
```

出来上がったpackage.jsonについて編集する。
mainを`main.js`としておき
`main.js`ファイルを作成する

```javascript:package.json
{
    ...
    "main": "main.js",
    ...
}
```

```javascript:main.js
'use strict';

var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

var mainWindow = null;

app.on('window-all-closed', function() {
  if (process.platform != 'darwin')
    app.quit();
});

app.on('ready', function() {

  // ブラウザ(Chromium)の起動, 初期画面のロード
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});
```

# メインの処理の移植

index.html, 1.wav, 2.wavは
前回のものをそのままコピーする。
test.jsについてローカルファイルから読み込むように修正

```javascript:test.js
'use strict';

var electron = require('electron');
var remote = electron.remote;
var fs = remote.require('fs');

window.AudioContext = window.AudioContext || window.webkitAudioContext; 
var context = new AudioContext();

var toArrayBuffer = function(buf){    
    return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
var schedule = [
    {"time":0, "file":"1.wav"},
    {"time":5, "file":"2.wav"}
];
var plans = []
var getAudioBuffer = function(schedule, index){
    if(index >= schedule.length){
        document.getElementById('btn').disabled = false;
        return;
    }
    plans[index] = {
        "time":schedule[index]['time'],
        "buffer":null
    };
    fs.readFile(schedule[index]['file'], function(err, data){
        context.decodeAudioData(toArrayBuffer(data), function(buffer){
            plans[index]['buffer'] = buffer;
        });
        getAudioBuffer(schedule, index+1);
    });
};

var planid = 0;
var playSound = function(){
    var source = context.createBufferSource();
    source.buffer = plans[planid]['buffer'];
    source.onended = function(){
        planid++;
        if(planid < plans.length){
            setTimeout(playSound, plans[planid]['time']*1000);
        }
    };
    source.connect(context.destination);
    source.start(0);
}

window.onload = function(){
    getAudioBuffer(schedule, 0);
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        planid = 0;
        playSound();
    }
}
```

## ローカルファイルの読み込み

fsライブラリを利用してfileの読み込みをするので
requireする。

```js
var electron = require('electron');
var remote = electron.remote;
var fs = remote.require('fs');
```

remote.requireについては
electronはマルチプロセスで動いていて
electronの描画用のプロセスではなく
読み込んで表示するブラウザ側のプロセスで動かすものは
remote経由で読み込む必要があるとの事。
ブラウザ側で動かす話なので、remote付けてrequireしている。

ファイルの読み込みは、前回はXMLHttpRequestで行っていたが
同部分はfs.readFileに変更する。
decodeAudioDataに渡すには、ArrayBufferでないといけないので
受け取ったBufferをArrayBufferとして扱うようにする。
node 4.x以降ならBuffer.bufferでArrayBufferを取得できる様子。
それをsliceでオフセット考慮して中身取り出す形で対応。

```js
var toArrayBuffer = function(buf){    
    return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
```

```js
    fs.readFile(schedule[index]['file'], function(err, data){
        context.decodeAudioData(toArrayBuffer(data), function(buffer){
            plans[index]['buffer'] = buffer;
        });
        getAudioBuffer(schedule, index+1);
    });
```

# 実行

```
electron .
```

exeで纏めたりする等やることはまだ残っているけども
一応ローカルファイルを読み込んで音声再生はできた。


# 参考

[Electronでアプリケーションを作ってみよう](http://qiita.com/Quramy/items/a4be32769366cfe55778)
[Convert a binary NodeJS Buffer to JavaScript ArrayBuffer](https://stackoverflow.com/questions/8609289/convert-a-binary-nodejs-buffer-to-javascript-arraybuffer)