Angular2の環境で、ブラウザの音声認識と音声合成の機能を導入する


## ブラウザでおしゃべりさせようと思ったら

ブラウザも進化して、音声で入出力できる機能ができましたね。すごい時代です。初老の自分には隔世の感があります:older_man_tone1:

いわゆる音声認識と音声合成と呼ばれる機能ですが、Angular2の環境でやってみようと思ったら、Javascriptで解説されているページのサンプルコードを貼っても動作しませんでした…

そこで、今回は[こちら](https://hassantariqblog.wordpress.com/2016/12/04/angular2-web-speech-api-speech-recognition-in-angular2/)のページを参考に、Angular2の環境で音声認識と音声合成を試してみようと思います。

## 準備

lodashというものをインストール、importする必要があります。
方法に関しては[こちら](https://hassantariqblog.wordpress.com/2016/10/15/angular2-import-lodash-into-angular2-application-using-typescript/)と[こちら](https://medium.com/@gtsopour/importing-lodash-into-angular-2-typescript-application-94590365f46d#.bibhgidss)を参考にしました。

インストールは、下記のコマンドを実行してください。

``` console:console
npm install --save lodash
npm install --save-dev @types/lodash
```

importは、音声認識のサービス内に記載しました。

``` ts:speech-recognition.service.ts
import * as _ from 'lodash';
```

こちらのサービスモジュールは、次の節で出てきます。


## Angular側のプログラム

プログラムは3つ用意しました。

1. マイクとスピーカーを使うcomponent
2. 音声認識service
3. 音声合成service

1のcomponentから2,3のserviceを呼出す形です。


### 音声認識service

参考にさせていただいたサイト通りで完璧でした。
英語でしたので、分かりにくい場合に参照いただけるように、こちらに再掲します。
（please refer to [the original site]((https://hassantariqblog.wordpress.com/2016/12/04/angular2-web-speech-api-speech-recognition-in-angular2/))）
私は日本語で使ったので、langだけja-JPに変更しました。

``` ts:speech-recognition.service.ts

import { Injectable, NgZone } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import * as _ from "lodash";

interface IWindow extends Window {
    webkitSpeechRecognition: any;
    SpeechRecognition: any;
}

@Injectable()
export class SpeechRecognitionService {
    speechRecognition: any;

    constructor(private zone: NgZone) {
    }

    record(): Observable<string> {

        return Observable.create(observer => {
            const { webkitSpeechRecognition }: IWindow = <IWindow>window;
            this.speechRecognition = new webkitSpeechRecognition();
            //this.speechRecognition = SpeechRecognition;
            this.speechRecognition.continuous = true;
            //this.speechRecognition.interimResults = true;
            this.speechRecognition.lang = 'ja-JP'; // original:en-us
            this.speechRecognition.maxAlternatives = 1;

            this.speechRecognition.onresult = speech => {
                let term: string = "";
                if (speech.results) {
                    var result = speech.results[speech.resultIndex];
                    var transcript = result[0].transcript;
                    if (result.isFinal) {
                        if (result[0].confidence < 0.3) {
                            console.log("Unrecognized result - Please try again");
                        }
                        else {
                            term = _.trim(transcript);
                            console.log("Did you said? -> " + term + " , If not then say something else...");
                        }
                    }
                }
                this.zone.run(() => {
                    observer.next(term);
                });
            };

            this.speechRecognition.onerror = error => {
                observer.error(error);
            };

            this.speechRecognition.onend = () => {
                observer.complete();
            };

            this.speechRecognition.start();
            console.log("Say something - We are listening !!!");
        });
    }

    DestroySpeechObject() {
        if (this.speechRecognition)
            this.speechRecognition.stop();
    }

}
```

### 音声合成service

なんと、先ほどのサイトは、音声認識だけで音声合成の記述がありませんでした。どこかからサンプルプログラムを参照させて頂いたのですが、今は見つかりませんでしたので、手元のプログラムを貼り付けておきます。

``` ts:speech-synthesis.service.ts

import {Injectable, NgZone} from '@angular/core';

interface IWindow extends Window {
  webkitSpeechSynthesis: any;
  SpeechSynthesis: any;
  SpeechSynthesisUtterance: any;
}

@Injectable()
export class SpeechSynthesisService {
  speechSynthesis: any;

  constructor(private zone: NgZone) {
    if ('speechSynthesis' in window) {
      console.log('Your browser supports speech synthesis.');
    } else {
      alert('Sorry! Your browser does not support speech synthesis. Try this on' +
        ' <a href="https://www.google.com/chrome/browser/desktop/index.html">Google Chrome</a>.');
    }
  }

  speak(text: string): void {
    const { SpeechSynthesisUtterance }: IWindow = <IWindow>window;

    let utterance = new SpeechSynthesisUtterance(text);
    utterance.lang = 'ja-JP';
    (<any>window).speechSynthesis.speak(utterance);
  }

}
```

### 音声認識と音声合成を利用するcomponent

テキストのチャットボットなどの呼出を行なう想定です。
画面には、ブラウザからマイクを使うボタンを置いてください。

``` ts:your-component.ts

import { Component, OnInit, Input } from '@angular/core';
import { SpeechRecognitionService } from './speech-recognition.service';
import { SpeechSynthesisService } from './speech-synthesis.service';


@Component({
  selector: 'app-xxx',
  templateUrl: './your.component.html',
  styleUrls: ['./your.component.css']
})
export class YourComponent implements OnInit {

  speechData: string;

  constructor(private speechRecognitionService: SpeechRecognitionService,
              private speechSynthesisService: SpeechSynthesisService
  ) {
    this.speechData = "";
  }

  ngOnInit() {
  }
  ngOnDestroy(){
    this.speechRecognitionService.stop();
  }

  // 音声認識用マイクのON/OFF 
  micOn: boolean = false;
  MicButton(state: boolean): void {
    this.micOn = state;
    if(this.micOn){
      this.activateSpeech();
    } else {
      this.deactivateSpeech();
    }
  }


  activateSpeech(): void {

    this.speechRecognitionService.record()
      .subscribe(
        //listener
        (value) => {
          this.speechData = value;
          console.log(value);
          yourChatBotFunction(value); // ★ここで独自関数を呼出す
        },
        //errror
        (err) => {
          console.log(err);
          if (err.error == "no-speech") {
            console.log("--restatring service--");
            this.activateSpeechSearchMovie();
          }
        },
        //completion
        () => {
          this.showSearchButton = true;
          console.log("--complete--");
          this.activateSpeechSearchMovie();
        });
  }


  deactivateSpeech(): void {
    this.speechRecognitionService.stop();
  }

  // ★独自関数
  // 引数のmessageには、音声認識で認識されたテキストが入る
  // chatbotサーバにテキストを送信し、戻りのテキストを読み上げる
  yourChatBotFunction(message: string): void {
    serverCall(message)
      .then(res => {
        this.speechSynthesisService.speak(res);
      });
  }

}

```

これで、画面でマイクボタンを押して、話かけて、（マイクを止めて）チャットボットから返事が来て、それをブラウザで読み上げるアプリケーションができました。

今回は以上です。お疲れ様でした:grinning:
そのうち、WebAudioAPIなど、ブラウザでできることを試してみたいと思います。

（終わり）