kintone上でWebSpeechAPIを利用して音声認識して文字起こしする【改良編】

### 本記事を読むにあたっての注意点
本記事は前回の[kintone上でWebSpeechAPIを利用して音声認識して文字起こしする【動作確認編】](https://qiita.com/jhon931/items/7c093eb2894057c058cd)で作ったものを実装するために改良した部分の紹介と、Web Speech APIの感想をまとめたものです。
まだ読んでいらっしゃらない方はそちらを先に読んでいただければ幸いです。

### コード全文
※紹介しきれなかったコードも含まれます。

<details><summary>全文表示する</summary><div>

```javascript

(function () {
    "use strict";
    kintone.events.on(["app.record.edit.show", "app.record.create.show"], function (event) {

        // start,stop ボタン生成
        const startButton = document.createElement("button");
        startButton.innerText = "録音開始";
        startButton.id = "start-btn";
        startButton.className = "recognition-buttons";

        const stopButton = document.createElement("button");
        stopButton.innerText = "録音終了";
        stopButton.id = "start-btn";
        stopButton.className = "recognition-buttons";

        // 文字起こし用<textarea>作成
        const tempForm = document.createElement("textarea");
        tempForm.id = "temp_input";
        //tempForm.type = "text";
        tempForm.cols = "150";
        tempForm.rows = "10";
        tempForm.innerText = "（録音開始ボタンをクリックで音声受付を開始します）";
        //tempForm.style.resize = "none";
        kintone.app.record.getSpaceElement('form_space').appendChild(tempForm);

        // // cautionフィールドにメッセージを追加
        const cautionElem = document.createElement("p");
        cautionElem.innerHTML = `・録音受付中はブラウザのタブに赤い丸ボタンが出現し点滅します。録音終了ボタン押下または一分放置すると録音が自動終了します。<br>
                                        ・録音終了後に句読点や改行を付けるなどの編集が可能です。<br>
                                        ・保存ボタンを押すと、音声受付フィールドの値が清書後フィールドに転記又は上書きされます。`
        cautionElem.id = "caution-p";
        kintone.app.record.getSpaceElement('caution').appendChild(cautionElem);

        // Web Speech API の設定 (SpeechRecognitionクラス)
        const SpeechRecognition = webkitSpeechRecognition || SpeechRecognition;
        const recognition = new SpeechRecognition();
        recognition.lang = "ja-JP";
        recognition.interimResults = true;
        recognition.continuous = true;

        let resultString = "";
        recognition.onresult = (event) => {
            let intermString = "";
            for (let i = event.resultIndex; i < event.results.length; i++) {
                let transcript = event.results[i][0].transcript;
                if (event.results[i].isFinal) {
                    resultString += transcript + " ";
                } else {
                    intermString = transcript;
                }
            }
            document.getElementById("temp_input").value = resultString + "<" + intermString;
        };

        // 録音ボタンの処理、ボタンの設置
        startButton.onclick = () => {
            // 録音開始時のみメッセージを表示
            tempForm.innerText = "（音声を受け付けています）";
            recognition.start();
        };
        stopButton.onclick = () => {
            recognition.stop();
        };
        kintone.app.record.getSpaceElement('buttonSpace').appendChild(startButton);
        kintone.app.record.getSpaceElement('buttonSpace').appendChild(stopButton);
        return event;
    });

    // 保存実行前に一時保存から取得してフィールドに移す。
    kintone.events.on("app.record.edit.submit", function (event) {
        // 音声入力しなければbreak  
        let inputVal = document.getElementById("temp_input").value;
        if (inputVal === "" || inputVal.slice(0, 8) === "（録音開始ボタン") return event;
        let str = document.getElementById("temp_input").value;
        str = str.slice(0, -1);
        event.record.newField.value = str;
        return event;
    });

    // 一覧画面では<button>と<textarea>のスペースを非表示にする
    kintone.events.on("app.record.detail.show", function () {
        document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[0].style.display = "none";
        document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[1].style.display = "none";
        document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[2].style.display = "none";
    });
})();
```
</div></details>



### Web Speech API（speech recognition）の感想
良い点：無料で利用可能で認識自体が高性能。学習コストも低い。
悪い点：単語登録が出来ないのと、ブラウザのサポート範囲がまだ狭い。
結論　：マルチな機能にはなれないが、単発で使うのには十分な性能。

### 前回時点での課題点
1. 各イベント処理（kintone.events.on）を追加して機能させる
1. 音声入力アウトプット用フィールドを一行から複数行にする
1. 録音前と録音開始時にメッセージを表示する
1. 音声入力をしないで保存した場合に空白("")で上書きされるのを止める
1. レコード詳細表示画面で表示される余計なスペースフィールドを非表示にする

### 改良後イメージ
【Before】
![before1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1233009/9c87ea81-6001-17b6-2245-449bf2d2f2cc.png)
【After】
![after1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1233009/743f4119-56c4-098c-b763-819a6050051c.png)

### 1.各イベント処理（kintone.events.on）を追加して機能させる
こちらは単純。レコード編集と、追加イベントで処理したいので前回に加えて
"app.record.create.show"を配列で追加。

```javascript:
kintone.events.on(["app.record.edit.show", "app.record.create.show"], function (event) {
```
### 2.音声入力アウトプット用フィールドを一行から複数行にする
前回は音声入力のアウトプットに\<input>要素を使っていたが、\<textarea>に変更。
innerText で録音開始前のメッセージも追加。

```javascript:
// 文字起こし用<textarea>作成
const tempForm = document.createElement("textarea");
tempForm.id = "temp_input";
tempForm.cols = "150";
tempForm.rows = "10";
tempForm.innerText = "（録音開始ボタンをクリックで音声受付を開始します）";
//tempForm.style.resize = "none";
kintone.app.record.getSpaceElement('form_space').appendChild(tempForm);
```

### 3.録音前と録音開始時にメッセージを表示する
（録音前のメッセージは 2.で実装済。）
録音開始時なので、Button.onclick()時にデフォルト指定。

```javascript:
// 録音ボタンの処理、ボタンの設置
startButton.onclick = () => {
    // 録音開始時のみメッセージを表示
    tempForm.innerText = "（音声を受け付けています）";
    recognition.start();
};
```
### 4.音声入力をしないで保存した場合に空白("")で上書きされるのを止める
こちら分かりづらいですが、\<textarea>の値が "" 又はデフォルトの"（録音開始ボタン..."だった場合に returnすることで不要な上書きを防ぐことが可能です。

```javascript:
// 保存実行前に一時保存から取得してフィールドに移す。
kintone.events.on("app.record.edit.submit", function (event) {
    // 音声入力しなければreturn  
    let inputVal = document.getElementById("temp_input").value;
    if (inputVal === "" || inputVal.slice(0, 8) === "（録音開始ボタン") return event;
    let str = document.getElementById("temp_input").value;
    str = str.slice(0, -1);
    event.record.newField.value = str;
    return event;
});
```
### 5.レコード詳細表示画面で表示される余計なスペースフィールドを非表示にする
kintone JavaScript APIの標準機能ではスペースは非表示にすることが出来ません。しかし気持ちが悪いのでDOM操作で実現してしまいました。（実装は自己責任で）
【Before】
![before2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1233009/036efc55-b108-c8db-5164-3422bbbd560f.png)
【After】
![after2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1233009/40752fbf-01a1-26b1-77ee-4229fb67e6b9.png)


```JavaScript:
// 一覧画面では<button>と<textarea>のスペースを非表示にする
kintone.events.on("app.record.detail.show", function () {
    document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[0].style.display = "none"; // 録音ボタン設置用スペースフィールド
    document.getElementsByClassName("control-etc-gaia control-spacer-field-gaia ")[1].style.display = "none"; // <textarea>設置用スペースフィールド
});
```