Friction River Software

  • お問い合わせ

CakePHP5入門【コラム⑬】faviconと音声入力

A子

favicon作成のための元となる画像ファイルをWindowsの「ペイント」を使って作ってみたよ

ちなみに、解像度は256×256ドットね

C菜

チャットっぽくて良いと思います~

B美

faviconの作成手順は分かってるわよね?

A子

あったりまえよ

Faviconジェネレーター」を使えば良いんでしょ?
(クリックすると別ウィンドウで開きます)

16×16」「32×32」「48×48」を選んで、マルチアイコンにしてみたわ

C菜

それでは、できたアイコンファイル(favicon.ico)をダウンロードしてから、「authapp/webroot」の中にコピーするです~

ブラウザを強制更新([Ctrl]+[F5])すると~

A子

うん、バッチリだね

あ、あとさー
スマホでチャットするときってフリック入力してるんだけど、これって声で入力することはできないかな?

B美

まぁ、やろうと思えばできるわよ

PHP言語じゃなくて、JavaScriptのコードになるけど…

A子

おぉ、できるんだ

んじゃ、ぜひやろう!
(いちいち入力するのが面倒くさいんだもん…(苦笑))

B美

基本的には二つのJavaScript関数を用意すれば良いわ

一つは「マイクが存在するかのチェック関数」で、もう一つは「マイク(音声)入力を処理する関数」ね

C菜

二つ目の「マイク(音声)入力を処理する関数」はボタンの「onClick」イベントに割り当てれば良いと思うんですけど~

一つ目の「マイクが存在するかのチェック関数」はいつ実行するのでしょうか~?

B美

良い質問ね

チャットページがロードされた際(画面表示されたとき)に実行されるよう、「DOMContentLoaded」イベントに追加すれば良いのよ

A子

具体的なコードを示してくれないと、わっかんねぇ(苦笑)

B美

OK
まずは「マイクが存在するかのチェック関数」だけど…

function checkMicrophone() {
    if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
        document.getElementById('mic-status').textContent = "(音声入力チェックはサポートされていません)";
        return;
    }

    navigator.mediaDevices.enumerateDevices()
        .then(function(devices) {
            const hasMicrophone = devices.some(device => device.kind === 'audioinput');
            if (hasMicrophone) {
                //マイクがある場合、ボタンを表示
                document.getElementById('start-recognition-btn').style.display = 'inline';
            } else {
                document.getElementById('mic-status').textContent = "(マイクが見つかりませんでした)";
            }
        })
        .catch(function(err) {
            document.getElementById('mic-status').textContent = "(エラーが発生しました)";
        });
}

あ、上記コードを見れば分かると思うけど、HTML要素として「id」が「mic-status」であるタグ(pタグまたはdivタグ)と、「id」が「start-recognition-btn」であるボタンが必要だからね

C菜

その二つを「templates/Chat」の中にある「index.php」に追記するです~

<div style="padding: 10px;">
    <div id="mic-status"></div>
    <button id="start-recognition-btn" class="button is-link">音声入力開始</button>
</div>

という感じで、いかがでしょう~?

B美

ふむ、まぁ概ね良いんだけど…

ボタンの初期状態は「非表示」にしておいてね
それとボタンの「onclick」イベントに(このあと記述する)「startRecognition」関数を割り当てておいてちょうだい

C菜

わかりました~

<div style="padding: 10px;">
    <div id="mic-status"></div>
    <button id="start-recognition-btn" onclick="startRecognition()" style="display: none;" class="button is-link">音声入力開始</button>
</div>

で、どうでしょうか~?

B美

OKよ

それじゃ、チャットページが表示されたタイミングで、この関数(マイクが存在するかのチェック関数)が実行されるようにしましょう
てか、すでに存在する「DOMContentLoaded」イベントの最後に一行追加するだけなんだけどね

document.addEventListener("DOMContentLoaded", function () {
    const messageInput = document.getElementById("message");
    const sendButton = document.getElementById("sendBtn");

    //Enterキーで送信(Shift + Enterで改行)
    messageInput.addEventListener("keydown", function (event) {
        if (event.key === "Enter" && !event.shiftKey) {
            event.preventDefault();
            sendButton.click();
        }
    });

    checkMicrophone();
});
赤字が追加した箇所)

C菜

簡単です~

B美

あ、そうだ

「navigator.mediaDevices」という機能って、HTTPプロトコルのもとでは実行できないから注意してね
(これってマイクなどのデバイスリストを取得する機能なんだけど、「HTTPSの環境下じゃないと動作しない」決まりだから…)

なお、HTTPアクセスの場合は「(音声入力チェックはサポートされていません)」って表示されるようにしているわ
(下記はHTTPS環境なので、「(マイクが見つかりませんでした)」を表示 ← マイクの無いデスクトップPCなので…)

A子

次は、さっき言ってた「startRecognition」関数かな?

B美

そうね
あと、入力欄のカーソル位置に挿入するための関数「insertTextAtCursor」を別に作成しておきましょう
(その関数を「startRecognition」関数の中から呼び出している…ってこと)

function startRecognition() {
    if (!('webkitSpeechRecognition' in window)) {
        document.getElementById('mic-status').textContent = "(音声入力はサポートされていません)";
        return;
    }

    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    const recognition = new SpeechRecognition();
    recognition.lang = 'ja-JP';    //日本語を指定
    recognition.interimResults = false;    //暫定結果を無効
    recognition.maxAlternatives = 1;    //最大の候補数を設定

    recognition.onstart = function() {
        document.getElementById('start-recognition-btn').style.display = 'none';
        document.getElementById('mic-status').textContent = "(音声入力中)";
    };

    recognition.onresult = function(event) {
        const speechResult = event.results[0][0].transcript;
        insertTextAtCursor(speechResult);
    };

    recognition.onerror = function(event) {
        document.getElementById('mic-status').textContent = "(エラー: " + event.error + ")";
    };

    recognition.onend = function() {
        document.getElementById('start-recognition-btn').style.display = 'inline';
        document.getElementById('mic-status').textContent = "";
    };

    recognition.start();
}

function insertTextAtCursor(text) {
    const inputField = document.getElementById('message');
    const startPos = inputField.selectionStart;
    const endPos = inputField.selectionEnd;
    const currentValue = inputField.value;

    inputField.value = currentValue.substring(0, startPos) + text + currentValue.substring(endPos);
    inputField.selectionStart = inputField.selectionEnd = startPos + text.length;
    inputField.focus();
}


A子

「二つの関数を追加」って言ってたのに、結局三つじゃん

B美

まぁね(苦笑)

強引に二つにしても良かったんだけど、「insertTextAtCursor」関数を独立させたほうが分かりやすいでしょ?

C菜

それについては同意するです~

B美

それじゃ、テストしてみようか

マイク内蔵のノートPCからアクセスしてみるわね

A子

お、「音声入力開始」ボタンが出現してるよ

B美

そのボタン(「音声入力開始」ボタン)を押すと下の画面が出現するから、「許可」をクリックしてちょうだい
(常に出るわけじゃなく、表示されるのは最初の一回だけだから安心してね)

C菜

何か、しゃべってみるです~

A子

おはよう

C菜

おぉ!

すぐに入力欄に埋め込まれましたよ~
(ほとんどタイムラグが無いですねぇ~)

A子

んじゃ、カーソルが右端にある状態で…

(「音声入力開始」ボタンをクリックして)「こんばんは

C菜

きちんと末尾に追加されてます~

A子

今度は、入力欄上のカーソルを「おはよう。」と「こんばんは。」の間に移動してから…

(「音声入力開始」ボタンをクリックして)「こんにちは

C菜

バッチリです~

きちんと間(カーソル位置)に挿入されました~

A子

いったん「送信」ボタンを押してっと…

次は、漢字変換がうまくいくかを試してみよう
(「音声入力開始」ボタンをクリックして)「これはおんせいにゅうりょくてすとです

C菜

「おんせいにゅうりょく」の箇所が「音声入力」に変換されてますし、「テスト」についてもカタカナに変換されてますね~

なにげに頭が良い感じがします~

B美

ふむ、どうやら大丈夫っぽいわね

あ、ただし…
(日本語変換が割とうまく働くのは確かなんだけど)同音異義語はちょっと難しいかも…

例えば、「せんとう」が「先頭」「戦闘」「銭湯」「尖塔」等のどれかに変換されたとしても、それが自分の思ってるものとは違うかもしれないわ

A子

なるほど、だから「送信」ボタンを押すのはマニュアルなのかー
(自分で入力欄を修正できるように…)

まぁ、それでもめっちゃ楽なのは確かだけどね

B美

あ、一点だけ注意!

Androidスマホで使えるのは確認済みなんだけど、iPhoneで使えるかどうかは検証できていないわ
(だって持ってないんだもの)

A子

私とC菜もAndroidスマホだから、検証できねぇ(苦笑)