初めまして。
プログラマもすなるQiitaというものを(以下略)。
Google Chrome限定だけど、HTMLとJavaScriptだけで音声入力が実現できてしまうらしい。
というわけで、作ってみた。
この記事は、スマホとタブレット上でのGoogle Chrome にて、
音声入力して<input type="text">
にテキスト入力するのが目的。
テキスト取得までをクラス(はJavaScriptでは無いけれど)化して、
以下のように簡単に呼び出すのが目的。
(※今回のは、自サイトで公開済みの内容と一部被ります)
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]
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>音声入力中・・・ <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追記)。何で最初は上手く判定できなかったんだろう?