LoginSignup
22
22

More than 5 years have passed since last update.

HTMLとJavaScriptだけで音声入力

Last updated at Posted at 2016-05-15

初めまして。
プログラマもすなるQiitaというものを(以下略)。

Google Chrome限定だけど、HTMLとJavaScriptだけで音声入力が実現できてしまうらしい。
というわけで、作ってみた。

この記事は、スマホとタブレット上でのGoogle Chrome にて、
音声入力して<input type="text">にテキスト入力するのが目的。
テキスト取得までをクラス(はJavaScriptでは無いけれど)化して、
以下のように簡単に呼び出すのが目的。
(※今回のは、自サイトで公開済みの内容と一部被ります)

speech.html
speech_input = new SpeechInputWs( null );

// 録音開始ボタンの動作
$("#id_start_button").click(function(){
    var dfd = speech_input.start();
    dfd.done( function( text ){
        $("#id_output").val( text );
    });
});

AndroidスマホとタブレットのChrome 47で動作確認。

参考にさせていただいたのは、以下のサイト様。
http://qiita.com/inouet/items/2c9e218c05f547bb6852
http://www.cyokodog.net/blog/web-speechi-api/

SpeechInputWs() クラス(はJavaScriptでは無いけれど)の、
実装は下記。
(ところで、JavaScriptにおいて
 こういうクラスっぽいものを、なんと呼称すればよいのだ?)

SpeechInputWs()で、やってることは、
参考にしたサイト様( http://qiita.com/inouet/items/2c9e218c05f547bb6852 )の
コードをラッパーして、
音声取得中のオーバーレイdivで「音声入力中・・・」表示を行い、
完了したら候補を5個まで表示して、
選択されたradioボタンにてテキストを確定して、
jQueryのdeferred.done( function(text){} )の形式で返す、って内容。
「音声入力中・・・」の通知を出すdivも自前で描画。

speech_input.js
/*
    [speech_input.js]
    encoding="UTF-8"
*/


// Googleの音声入力用のクラス定義
//   ref. http://qiita.com/inouet/items/2c9e218c05f547bb6852
//   ref. http://www.cyokodog.net/blog/web-speechi-api/
//   ref. https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html 【公式Spec】
// 
// noticeOverlay = null指定可能。その場合は、通知用divを自動生成。
// ,start() で、deferred が返るので、.done( text )で取得して任意に設定する。
//
// ※音声入力の実行可能不可能の自前判定は無いので、別途「スマホ/タブレットのみOK」などの
//  処理を実装の事。
//
var SpeechInputWs = function( noticeOverlay ){
    var self = this;
    this.itsNoticeOverlayDiv = noticeOverlay;

    // 通知用のオーバーレイdivが無ければ自前で生成する。
    if( !this.itsNoticeOverlayDiv ){
        $("body").append( "<div id=\"_id_overlay_notice\">hoge</div>" );
        this.itsNoticeOverlayDiv = $("#_id_overlay_notice");
        this.itsNoticeOverlayDiv.css({
            "display" : "none",
            "width"   : "280px",
            "height"  : "160px",
            "text-align" : "left",
            "position"   : "fixed",
            "top"  : "24px",
            "left" : "24px",
            "z-index" : "100",
            "background-color": "#cccccc"
        });
    }

    // 一応、毎回初期化???
    window.SpeechRecognition = window.SpeechRecognition || webkitSpeechRecognition;

    // オブジェクト生成
    this.itsRecognition = new webkitSpeechRecognition();
    this.itsRecognition.lang = 'ja';
    this.itsRecognition.maxAlternatives = 3;

    // 終了時に呼び出されるメソッドを設定
    this.itsRecognition.addEventListener("result", function(event){
        // この場面でのthisはグローバル扱いの可能性があるので、クロージャーのselfを経由しておく。
        self._selectTextFromSpeechResult( event.results );
    });
    this.itsLastDfd = null;
};
SpeechInputWs.prototype._selectTextFromSpeechResult = function( speechResult ){
    // ※予め、this.itsLastDfd にDeferred::promise() が入っている事。
    var dfd_notice = this.itsLastDfd;
    var candidate = speechResult.item(0);
    var i = candidate.length;
    var str;

    if ( i == 0 ){ 
        dfd.resolve( candidate.item(0).transcript );
        return dfd.promise();
    }

    // 【FIXME】あれ? .maxAlternatives=3 が効いてない???
    if( i > 5 ){ i = 5; }

    // 候補を表示してユーザー入力を待つ。
    str = ""; // 下記で作成済みのformへ追加するスタイル。
    while( 0 < i-- ){
        str += "<div><input type=\"radio\" name=\"candidate\" value=\"" + candidate.item(i).transcript + "\">";
        str += candidate.item(i).transcript;
        str += "</input></div>";
    }
    this.itsNoticeOverlayDiv.find("form").eq(0).append( str );

    this.itsNoticeOverlayDiv.find( "form div" ).each( function(){
        $(this).click( function(){
            dfd_notice.resolve( $(this).find("input").eq(0).val() );
        });
    });
    this.itsNoticeOverlayDiv.find( "form input[type='button']" ).each( function(){
        // 動作を変更する(stop()処理が不要になる。また投げる先のdfdが異なる)。
        $(this).click( function(){
            dfd_notice.reject( null );
        });
    });

    // 戻り値なし。
};
SpeechInputWs.prototype.start = function() {
    var self = this;
    var dfd_sound = new $.Deferred();
    var str = " <form>音声入力中・・・ &nbsp; <input type=\"button\" value=\"中止\"></input><br>";
    str += "</form>";

    // 音声入力の受付を開始
    this.itsRecognition.start(); 

    // ユーザー通知系の処理
    this.itsNoticeOverlayDiv.empty();
    this.itsNoticeOverlayDiv.append( str );
    this.itsNoticeOverlayDiv.show();
    this.itsNoticeOverlayDiv.find("form input[type='button']").each( function(){
        $(this).click( function(){
            self.itsRecognition.stop();
            self.itsNoticeOverlayDiv.hide();
            dfd_sound.reject( null );
        });
    });

    // 録音終了時トリガーを受けるDeferredを設定
    this.itsLastDfd = new $.Deferred();
    this.itsLastDfd.done( function( text ){
        dfd_sound.resolve( text );
    }).fail( function(){
        dfd_sound.reject( null );
    }).always( function(){
        self.itsNoticeOverlayDiv.hide();
    });

    return dfd_sound.promise();
};

※なお、「スマホ/タブレットからのアクセスか?」で判定して音声入力ボタンを有効/無効で切り替えるとよいかも? 当方の場合、アクセス判定には https://w3g.jp/blog/js_browser_sniffing2015 のコードを利用させてもらっている。サンクス!
※何も考えずに、new new SpeechInputWs( null );のところで、try{}catch (e){}するだけで、実行可能環境か否かの判定できた(2016.06.03追記)。何で最初は上手く判定できなかったんだろう?

22
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
22