Google Home開発入門 / google-home-notifier解説

# はじめに
今年は、Google Home や Amazon Echo が発売されるなどスマートスピーカーが揃ってきた年になりましたね！
自分もGoogle Home Miniを発売日に買って、心の中で「未来を買ってしまった…！」なんて思ったりしてました。

実際のところ、目覚ましにしか使っていませんが…ｗ
（目覚まし止めるのも大変です。中央のボタンが無効化されているので「Ok,Google とめて！」って3回ぐらい言わないと止まってくれません）

# 手軽に開発を始める
これはググるとたくさん出てくることなのですが、便利なライブラリが公開されているみたいです。
よく使われているのは下記のNode.jsのライブラリですね。
[google-home-notifier](https://github.com/noelportugal/google-home-notifier)

簡単に使い方を説明します。

## google-home-notifierのインストール
```shell-session
$ # package.jsonを作ります
$ npm init

$ # google-home-notifierを入れます
$ npm install google-home-notifier

$ # yarn の方は下記のようにします。
$ yarn add google-home-notifier
```

## サンプルコードで喋らせる
[google-home-notifier](https://github.com/noelportugal/google-home-notifier)のGithubに書いてあるサンプルコードを使うことで簡単に喋らせることができます。

### 普通に喋らせる
何箇所か変えて下記のコードで実行してみます。

```js:main.js
const googlehome = require('google-home-notifier')
const language = 'ja';

googlehome.device('Google-Home', language); 

googlehome.notify('こんにちは。私はグーグルホームです。', function(res) {
  console.log(res);
});
```

```shell-session
$ node main.js
Device "XXX-XXX-XXXXXXXXXXXXXXXXX" at 192.168.1.6:8009
Device "Google-Home-XXXXXXXXXXXXXXXXX" at 192.168.1.7:8009
Device notified
```

私の家にはGoogleHome以外にも喋らせることが可能なデバイスがあったようで、2デバイス表示されています。
今回はデバイス名を `Google-Home`と指定しましたので、下のデバイスが選択されGoogleHomeが「こんにちは。私はグーグルホームです」と喋りました。

ここは、デバイス名が完全に一致していなくても、指定したデバイス名が含まれているデバイスに送信されます。

### 音楽を鳴らす

```js:main.js
const googlehome = require('google-home-notifier')
const language = 'ja';

googlehome.device('Google-Home', language); 

googlehome.play('MP3ファイルへのリンク', function(res) {
  console.log(res);
});
```
`notify()` が `play()` に代わり、テキストの代わりにMP3ファイルを渡すようになっただけです。

本当は、コピペで使えるようコードにしたかったのですが音楽ファイルはいろいろ著作権などが面倒そうだったのでやめました。

# google-home-notifierの仕組み
どうやって、 [google-home-notifier](https://github.com/noelportugal/google-home-notifier) はGoogleHomeに喋らせているのでしょうか？

実際に[ソースコード](https://github.com/noelportugal/google-home-notifier/blob/master/google-home-notifier.js)を覗いてみましょう。

## デバイス取得

`notify`や`play`を叩くと、下記のようにデバイス名とIPアドレスが表示されます。
どうやっているのでしょうか？

```shell-session
Device "XXX-XXX-XXXXXXXXXXXXXXXXX" at 192.168.1.6:8009
Device "Google-Home-XXXXXXXXXXXXXXXXX" at 192.168.1.7:8009
```

コードはこんな感じ。

```js:google-home-notifier.js
var notify = function(message, callback = function() {}) {
  if (!deviceAddress){
    browser.start();
    browser.on('serviceUp', function(service) {
      console.log('Device "%s" at %s:%d', service.name, service.addresses[0], service.port);
      if (service.name.includes(device.replace(' ', '-'))){
        deviceAddress = service.addresses[0];
        getSpeechUrl(message, deviceAddress, function(res) {
          callback(res);
        });
      }
      browser.stop();
    });
  }else {
    getSpeechUrl(message, deviceAddress, function(res) {
      callback(res);
    });
  }
};
```

１つ１つ見ていきましょう。
まず、`brwoser`ですが、下記のように定義されています。

```js
var mdns = require('mdns');
var browser = mdns.createBrowser(mdns.tcp('googlecast'));
```

`mdns` というライブラリを使ってキャストできる端末を探します。
(mDNSについては、下記の方の記事がすぐでてきました。
[[Signagify 3] Node.js で Sender アプリ作って mDNS + CastV2 で Chromecast とお話](https://qiita.com/vanx2/items/3c20bf8e4111da9eb68d
)）


```js:google-home-notifier.js
    browser.on('serviceUp', function(service) {
      console.log('Device "%s" at %s:%d', service.name, service.addresses[0], service.port);
      if (service.name.includes(device.replace(' ', '-'))){
        deviceAddress = service.addresses[0];
        getSpeechUrl(message, deviceAddress, function(res) {
          callback(res);
        });
      }
      browser.stop();
    });
```

`serviceUp` イベントを受け取ると、IPアドレス・デバイス名などが取得できるようですね。


```js
service.name.includes(device.replace(' ', '-')
```

その後、指定したデバイス名が含まれているものを検索し、一致するデバイスのIPを `deviceAddress` に格納し、`getSpeechUrl` という関数を呼び出しています。
**つまりキャストできる端末を取得してます。**

## TTSのURLを取得 - getSpeechUrl

```js
var onDeviceUp = function(host, url, callback) {
  var client = new Client();
  client.connect(host, function() {
    client.launch(DefaultMediaReceiver, function(err, player) {

      var media = {
        contentId: url,
        contentType: 'audio/mp3',
        streamType: 'BUFFERED' // or LIVE
      };
      player.load(media, { autoplay: true }, function(err, status) {
        client.close();
        callback('Device notified');
      });
    });
  });

  client.on('error', function(err) {
    console.log('Error: %s', err.message);
    client.close();
    callback('error');
  });
};

var getSpeechUrl = function(text, host, callback) {
  googletts(text, language, 1).then(function (url) {
    console.log(url);
    onDeviceUp(host, url, function(res){
      callback(res)
    });
  }).catch(function (err) {
    console.error(err.stack);
  });
};
```

`googletts` というのを呼び出していますね。

```
var googletts = require('google-tts-api');
```

`googletts` というのは、Google Text To Speachの略です。
つまり、**入力した文字を音声ファイルにしてくれます**。
どうやら、このライブラリはGoogle翻訳の読み上げ機能から音声ファイルを取得しているようです。

## 取得した音声を再生 - onDeviceUp
```js:google-home-notifier.js
var onDeviceUp = function(host, url, callback) {
  var client = new Client();
  client.connect(host, function() {
    client.launch(DefaultMediaReceiver, function(err, player) {

      var media = {
        contentId: url,
        contentType: 'audio/mp3',
        streamType: 'BUFFERED' // or LIVE
      };
      player.load(media, { autoplay: true }, function(err, status) {
        client.close();
        callback('Device notified');
      });
    });
  });

  client.on('error', function(err) {
    console.log('Error: %s', err.message);
    client.close();
    callback('error');
  });
};
```

`client` は `castv2-client` ですね。 [castv2-client](https://github.com/thibauts/node-castv2-client) はChromecastのクライアントのようです。

```js
var Client = require('castv2-client').Client;
```

その後は、普通のChromecast端末と同じように音声ファイルを送信します。

# google-home-notifierの流れまとめ

まとめるとこんな感じです。案外シンプルですよね。

 - キャスト出来るデバイスを探す
 - 取得できたデバイス名から、指定したデバイスを探す
 - Google Text To Speachで、文字を音声ファイルのURLにする
 - 取得したURLをGoogleHomeにキャスト（送信）する

# その他

`google-home-notifier` に `example.js`がありますが、これを起動するとHTTPサーバが立ち上がりリクエストを送信すると喋ってくれます。

```shell-session
$ git clone https://github.com/noelportugal/google-home-notifier
$ cd google-home-notifier
$ npm install
$ node example.js
Endpoints:
    http://192.168.1.20:8091/google-home-notifier
    https://xxxxx.ngrok.io/google-home-notifier
GET example:
curl -X GET https://xxxxx.ngrok.io/google-home-notifier?text=Hello+Google+Home  - to play given text
curl -X GET https://xxxxx.ngrok.io/google-home-notifier?text=http%3A%2F%2Fdomain%2Ffile.mp3 - to play from given url
POST example:
curl -X POST -d "text=Hello Google Home" https://xxxxx.ngrok.io/google-home-notifier - to play given text
curl -X POST -d "http://domain/file.mp3" https://xxxxx.ngrok.io/google-home-notifier - to play from given url
```

これを利用して、IFTTTから自発的に喋らせるということをやってる人もいるみたいですね
[Twitterの通知をGoogle Homeに教えてもらう](https://kotodama.today/?p=744)

# 最後に
今回はNodeで試しましたが、Chromecastのライブラリを使用すればどの言語でも比較的簡単に作成できると思います。
**つまり、音声ファイルをGoogleHomeに送信してあげるだけです。**(たぶん）
Actions on Googleなどの記事は他の方が上げてくれると思います。

もっといい感じのライブラリでてきたら、教えてください。