まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:html5

JavaScript(HTML5) の機能だけで、PC に付属したマイクから音声を入力(つまり PC の前で喋った音声データを取り込む)してみました。なお、この機能を使うには UserMedia に対応したブラウザが必要です。以下のコードは Windows 版の FireFox で動作確認をしています。

サンプルのコードはこんな感じになります:
<html>
<head>
<title>HTML5 Audio</title>
<script type="text/javascript"> 
//. ブラウザによる差異を吸収
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
 
//. バッファサイズ等
var audioContext = new AudioContext();
var bufferSize = 4096;
var cnt = 0;
 
//. 音声処理
function onAudioProcess( e ){
  //. 取得した音声データ
  var input = e.inputBuffer.getChannelData(0);

//. ↑この input に音声データが入っているので、これをストリーミングなどで処理すればよい。
//. 以下は実際にデータが入っていることを確認するためのサンプル処理
//. 音声データの最大・最小値を求める var mx = 0, mn = 0; for( var i = 0; i < bufferSize; i ++ ){ if( mx < input[i] ){ mx = input[i]; } if( mn > input[i] ){ mn = input[i]; } } //. 一度に取得した音声データの最大・最小値を求める(特に意味は無いが、データが取得できている確認) cnt ++; console.log( "[" + cnt + "] min = " + mn + ", max = " + mx ); } //. 音声処理開始 function initialize(){ navigator.getUserMedia( { audio: true }, function( stream ){ //. 音声処理 var javascriptnode = audioContext.createScriptProcessor( bufferSize, 1, 1 ); var mediastreamsource = audioContext.createMediaStreamSource( stream ); window.dotnsf_hack_for_mozzila = mediastreamsource; //. https://support.mozilla.org/en-US/questions/984179 mediastreamsource.connect( javascriptnode ); javascriptnode.onaudioprocess = onAudioProcess; javascriptnode.connect( audioContext.destination ); },function( e ){ console.log( e ); } ); } </script> </head> <body onload="initialize()"> </body> </html>

余談ですが、上記コードの赤字部分は今回一番迷った所。どうも FireFox のバグらしく、取得したメディアストリームがいつの間にか消えて(音声入力処理が止まって)しまう、という現象に遭遇しました。そのバグを回避するため、無理やり取得した値をグローバル変数化して消えないようにしています。バグの詳細についてはこちら:
https://support.mozilla.org/en-US/questions/984179


この HTML をマイク入力を ON にした状態で対象ブラウザで開くと、「PC のマイクをこのアプリと共有しますか?」という確認メッセージが表示されます。ここで「共有」を選択してください:
2016030301

共有すると画面に FireFox がマイクを利用中であること(知らないうちに音声を取得しているわけではないこと)を知らせるマークが表示されます。このマークが出ている間は PC のマイクで拾った音声をこのウェブページで共有していることを表しています:
2016030302


ここで F12 キーを押すとデバッグ画面が表示され、コンソールタブを見ると取得した音声データ1まとまり(4Kbyte)単位での最大値と最小値(簡単にいうと音の大きさ)を表示し続ける、というアプリになっています。
2016030303


実際には取得した音声データを保存(録音)して再生できるようにするとか、ストリーミングで処理してテキスト化するとか、・・・ という使い方になると思いますが、その入力を HTML で行う方法の紹介でした。

243 ゲームをご存知でしょうか?
2015092901


2048 ゲームは(そこそこ)有名だと思いますが、そのクローンゲームです。2048 ゲームは以下のルールで遊ぶものです:
(1) 矢印キーを使って、画面内のタイルを4方向にスライドさせる
(2) 同じ数字のタイルをぶつけると、数字が倍の1つのタイルができる
(3) ぶつけて出来上がったタイルの数字が得点になる(2048 タイルを作ることが1つの目標)


243 は 2048 と異なり、3つの同じ数字を重ねることで1つにまとまります。2048 よりは難易度高くなっています。具体的なルールは以下になります:
(1) 矢印キーを使って、画面内のタイルを4方向にスライドさせる
(2) 同じ数字のタイルを3つぶつけると、数字が3倍の1つのタイルができる
(3) ぶつけて出来上がったタイルの数字が得点になる(243 タイルを作ることが1つの目標)



で、このゲームの HTML5 が Github 上で公開されていました。これを使って IBM Bluemix 上で 243 ゲーム環境を作ってみます(まあ、特にトリッキーなこともないんですが・・)。

まず Bluemix 上に 243 ゲームが動くランタイムを作成します。サーバーサイドで動くモジュールがないので、Github の staticfile ビルドパックを使っても動くと思いますが、Bluemix ダッシュボードから PHP ランタイムを選んでも(事実上 PHP を使わないので、かなり贅沢な環境ですが)動きます。今回は PHP ランタイムを使う方向でいきましょう:
2015092902


並行して 243 ゲームの zip モジュールを GitHub からダウンロードします:
2015092903


ダウンロードした zip モジュールを展開します。index.html が存在するディレクトリがドキュメントルートになります:
2015092904


ここまでできたら、cf コマンドラインツールを使って、このドキュメントルートを PHP ランタイムにプッシュするだけ、です:
# cd (展開した index.html があるディレクトリ) ドキュメントルートへ移動
# cf login -a https://api.ng.bluemix.net/    cf で Bluemix にログイン(米国DCの場合)
# cf push (PHP ランタイムの名前)        cf で Bluemix に 243 ゲームをプッシュ

プッシュが成功したら、PHP ランタイムの URL にブラウザからアクセスすると 243 ゲームが始まります:
2015092905


ぎゃー、惜しかったけど無理。難しい!!

NHK が最近また流行りだしたワンクリック詐欺サイトのニュースを流していました:
スマホに「消せないメッセージ」 注意を


(2015/02/06 追記)
NHK のニュースページが消えてしまい、リンク切れになっていたので、同様のニュースを紹介しているページのリンクを貼っておきます:
【注意!】スマホに消せない画面を表示し金を奪うサイトが続出!
(2015/02/06 追記終わり)


ニュースの内容によると、どうもこれは
 ・(アプリとかではなく)特定のウェブページを
 ・スマホを使って
 ・閲覧した瞬間に
起こる仕組みがある、というものでした。

試しに見てボタンをクリックしたら、ではなく、見た瞬間に発生するらしいです。なので詐欺とわかっていても何が起こるかわからないので試してみるのも怖い、、、、ですよね。

せっかくなので(笑)、勇気を出して比較的安全と思われる方法で何をやっているのかを調べてみました。

このニュースで報じられていたサイトは、どうやら http://XXXXX-avnavi.net/ というサイトです(XXXXX 部分は僕の判断で伏せ字にしてます)。 結論から言うと、このサイトは PC のブラウザでアクセスすれば、単に JavaScript の alert が繰り返し表示されるだけの迷惑ページで済むのですが、最初は何が起こるかわからないので PC ブラウザも使いません。Linux 環境から基本の wget でソースコードだけいただきます:
(トップページの HTML ソースコードだけいただいて X.html という名前で保存)
# wget http://XXXXX-avnavi.net/ -O X.html

取り出した内容を vi で表示します。何よりも驚いたのは丁寧でわかりやすいコメントが大量に書かれていました。おかげで何をやっているのか、とても理解しやすかったです(笑)。この姿勢に限っては真似してもいいと思う。

さてその内容ですが、まず最初にこんな JavaScript コードが実行されてました(コメントもそのまま):
<script>
// History API が使えるブラウザかどうかをチェック
if( window.history && window.history.pushState ){
  //. ブラウザ履歴に1つ追加
  history.pushState( "nohb", null, "" );
  $(window).on( "popstate", function(event){
    //. このページで「戻る」を実行
    if( !event.originalEvent.state ){
      //. もう一度履歴を操作して終了
      history.pushState( "nohb", null, "" );
      return;
    }
  });
}
</script>

非常に丁寧なコメントですよね~(苦笑)。実を言うとこの時点で「おや?」と気付くこともあったのですが、その内容は後述します。

肝心な内容は JavaScript で HTML5 の History API が利用できるブラウザかどうかをチェックした上で、popState/pushState を操作して「戻る」を実行しても戻れないように(要するに「戻る」の無効化)操作しています。このページを見て、怖くなって「戻る」を押しても戻れないように予め設定されてしまうのです。用意周到ですね。。

ちなみにここのテクニックに関しては以前に僕のブログエントリでも紹介しています。興味ある方はそちらを参照ください:
HTML5 の pushState/popState でヒストリバックを無効にする


そして、ページを戻れなくした上で行っているのがこちらのコードです。赤字部分は僕が加えた説明コメントです:
<script type="text/javascript">
client_id1 = getCookie('id'); //. クッキーから(別途設定した) ID を取得
console.log("in template"); //. この2行要らないと思う
console.log(client_id1);
var HogeTimer = setInterval("hogemoge()",1000); //. 1秒毎に以下を実行
function hogemoge(){
(この一行があまりにも長くて見にくかったので改行しています。
 問い合わせ先電話番号 NNNNNNNNNNNNN はこちらで伏せ字にしました)

alert('【御登録完了】\nお申込み承諾致しました。\n 動画再生準備完了中。\n\n 【18禁アダルト動画サイト】\n 【365日間の視聴期間】\n\n ★只今お客様還元祭★\n ★キャンペーン期間中で割引適用中★\n 99800円\n 会員ID:' +client_id1 +'\n問い合わせ先\nNNNNNNNNNNNNN\n ※キャンペーン期間内にご精算下さい※\n\n ※誤作動登録の場合※\n →24時間以内に登録削除(自動処理)へご連絡ください。\n\n ※24時間経過後※\n→誤作動登録の場合でもご精算頂きます。');
//. ↑怖いメッセージを表示した後に、、 location.href = "tel:NNNNNNNNNNNNN"; //. スマホの場合、ここで電話をかけようとする

//. 終了しても1秒後にメッセージ表示から繰り返される } </script>

まず別途設定したクッキーを使って、お客様番号っぽい情報を作り、それを取り出します(お客様番号でも何でもありません)。 その直後の console.log 2行は気持ちは分かるけど消しておいた方がいいと思う(苦笑)。

そして JavaScript の setInterval を使って、1000ミリ秒(1秒)毎に実行する処理を指定しています。ここでいう「1秒毎」は、この処理が実行されて、最後まで実行して、そこから1秒たったらまた初めから実行、です。要は1秒の間をとりながら延々と繰り返される処理、ということになります。ここまではよくあるブラクラと同じ原理です。

気になる肝心の処理内容ですが、(1)怖いメッセージを表示して(「OK」ボタンだけ表示されるので押すしかない)、(2)「OK」を押すと(スマホでは)特定の番号に国際電話をかけようとする です。実際には「電話をかけますか?」という確認メッセージが表示されて、かけるかどうかを選択できるのですが、書けずに終了しても、1秒後にまた (1) へ戻るだけです。ちなみに電話をかけても (1)へ戻ります。

「怖い」と感じて、前のページに戻ろうとしても前述のように「戻る」は無効化されています。普通にホームボタンを押してブラウザを閉じてしまうことはできますが、(別の目的で)ブラウザを使おうとすると、履歴が残っているのでまたこの画面に戻ってしまいます。パニックにさせて、「電話をかけるしかないのか?」と思わせようとしてるんですかね。ちなみに電話をかける実験まではしてないので、どういう内容かはわかりません。 (^^;


もしも、間違ってこのページを PC ブラウザで開いてしまった場合は、CTRL+ALT+DEL キーを押してタスクマネージャーを出すなどして、ブラウザごと終了してしまいましょう。

スマホでこのページを見ちゃった場合はこちらのページを参考に履歴ごと消してしまってください:
http://did2memo.net/2013/11/15/iphone-endless-pop-up-message-page/



サイトの仕組みの説明は以上です。この下は気付いた雑感を2つほど。

まず、このページの HTML / JavaScript は非常に読みやすいです、メンテナンスしやすそう(苦笑)。ある意味で優秀なエンジニアが関わってるんだろうなあ、と思いました。デバッグ用と思われる console.log が残っているのはご愛嬌ですが、JavaScript 以外の HTML でも例えばこんな感じの記述がされています:
2015013101


いやあ、わかりやすい(笑)。 <div> でちゃんとパーツ化されている上に HTML コメントが非常に適切で、実際の画面を見なくてもどういうページを作ろうとしているのか想像できます。感心してる場合じゃないけど、これ作った人とは話が合いそう。。 (^^;

そしてもう1つ気になったこと。それは上記で紹介した僕のブログエントリ
HTML5 の pushState/popState でヒストリバックを無効にする

で紹介している JavaScript の内容と、このサイトで実際に使われている JavaScript の内容が変数やコメントの使い方のレベルで非常に似ている、というか似すぎている!? あれ?もしかして参照してくれた!? ということに気付いてしまいました。 良い子はこういう使い方に応用しないでね。 d(o^ )

#念のため補足しておくと、僕が紹介した無効化は問い合わせフォームなどで 入力→確認→送信 みたいなページ遷移をする時に 送信後のページから確認ページに戻られると色々不都合があるので、そういった挙動をさせなくするための手段として紹介したつもりでした。


というわけで、メンテナンス性も含めた色んな意味で意外と完成度の高い詐欺サイトでした。




 

HTML5 の File API を使った、OS からウェブ画面へのファイルドラッグ&ドロップを実現する方法を紹介します。

まず、何も準備しないでウェブ画面へファイルをドラッグ&ドロップすると、デフォルト挙動によって、そのファイルがブラウザで開かれます。例えば画像ファイルをウェブ画面にドラッグ&ドロップすると、その画像ファイルがブラウザ内で開かれて表示されます。

ドラッグ&ドロップを実現するには、まずブラウザのこのデフォルト挙動を上書きして、別の挙動にするよう指定する必要があります。そのためにファイルドロップのエリア(<div>)への drop イベントに対して、preventDefault(); を指定しておく必要があります。これでファイルをドロップしてもブラウザでは開かなくなります。

また、ドロップイベントのパラメータである event の event.dataTransfer.files プロパティにはドラッグ&ドロップしたファイルの情報が配列で入ってきます。このオブジェクトの内容が画像であるかどうかを調べた上で、画像と判別できたら Dynamic JavaScript を使って <img> タグを追加し、innerHTML でその画像を表示する、という挙動を実装しています。こうすることで動的に(リロードなしに)そのドロップされたファイルをブラウザ内に表示することができるようになります。

この挙動を実装したのが以下です。ピンクの枠(<div id="droparea">)がドラッグ&ドロップのドロップイベントを制御するエリアになっていて、この中に画像ファイルを(ドラッグ&)ドロップすると、その画像をリロードなしにその直下の <ul id="files"></ul> の中で表示します:


ここにファイルをドロップ

    この HTML および JavaScript は以下になります:
    <script type="text/javascript">
    window.onload=function(){
     var URL_BLANK_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
     var elDrop = document.getElementById('droparea');
     var elFiles = document.getElementById('files');
    
     elDrop.addEventListener('dragover', function(event) {
      event.preventDefault();
      event.dataTransfer.dropEffect = 'copy';
      showDropping();
     });
    
     elDrop.addEventListener('dragleave', function(event) {
      hideDropping();
     });
    
     elDrop.addEventListener('drop', function(event) {
      event.preventDefault();
      hideDropping();
    
      var files = event.dataTransfer.files;
      showFiles(files);
     });
    
     document.addEventListener('click', function(event) {
      var elTarget = event.target;
      if (elTarget.tagName === 'IMG') {
       var src = elTarget.src;
       var w = window.open('about:blank');
       var d = w.document;
    
       d.open();
       d.write('<img src="' + src + '" />');
       d.close();
      }
     });
    
     function showDropping() {
      elDrop.classList.add('dropover');
     }
    
     function hideDropping() {
      elDrop.classList.remove('dropover');
     }
    
     function showFiles(files) {
      elFiles.innerHTML = '';
    
      for (var i=0, l=files.length; i<l; i++) {
       var file = files[i];
       var elFile = buildElFile(file);
       elFiles.appendChild(elFile);
      }
     }
    
     function buildElFile(file) {
      var elFile = document.createElement('li');
    
      if (file.type.indexOf('image/') === 0) {
       var elImage = document.createElement('img');
       elImage.src = URL_BLANK_IMAGE;
       elFile.appendChild(elImage);
    
       attachImage(file, elImage);
      }
    
      return elFile;
     }
    
     function attachImage(file, elImage) {
      var reader = new FileReader();
      reader.onload = function(event) {
       var src = event.target.result;
       elImage.src = src;
       elImage.setAttribute('title', file.name);
      };
      reader.readAsDataURL(file);
     }
    }
    </script>
    
    <div  effectallowed="move" id="droparea">ここにファイルをドロップ</div>
    <ul id="files"></ul>
    
    

    参考にしたのはこちら:
    http://jsfiddle.net/ginpei/bSF9z/
     

    Firefox OS 向けのネイティブアプリケーションを開発する手順を紹介します。

    「ネイティブアプリケーション」と言うと、開発言語は iPhone だと Objective-C や Swift、Android だと Java 、といった具合にネイティブ開発用のコンパイル言語が一般的ですが、Firefox OS アプリの場合は HTML5 でアプリケーションを開発します。HTML5 で作る、ということは HTML やら JavaScript や CSS を組み合わせて UI やロジックを作っていく、ということを意味しています。これは C や Java で開発する場合と比べて、非常にハードルが低く、より多くのエンジニアが参画できるのではないかと感じます。

    ただ Firefox OS の開発ではマニフェストファイルの存在や、その中で API の許可設定を行うこと、そして Android で言うところの「インテント」に近い連携が可能になっていることなど、Android の開発手法と似ている所が多い、という事実もあります。開発言語こそ Java と HTML5 の違いはありますが、アプリケーションパッケージの考え方としてはかなり近いという印象を持っています。そのため Android での開発経験があるエンジニアからすると、「開発言語が Java から HTML5 になった」という感じで、非常にとっつきやすいのではないかとも感じています。 


    そんな Firefox OS 向けアプリケーション、まずは Hello World レベルのものを作ってみます。

    事前に用意する必要があるのは Firefox OS シミュレータ、これは動作確認用です。Firefox OS シミュレータの導入方法については以前の私のブログエントリを参照してください:
    Firefox OS シミュレータ

    そしてテキストエディタです。もちろん Eclipse などの統合開発環境でも構いませんが、この Firefox OS 用アプリは HTML5 で作るので、HTML や JavaScript、CSS が記述できるエディタであれば何でも構いません。あまり複雑でないうちは Windows のメモ帳でも充分だと思います。


    では Firefox OS 向け "Hello World" アプリケーションを作っていきます。これから作るアプリは3つのファイルから構成されます:
    - helloworld.html (アプリケーション本体)
    - manifest.webapp (マニフェストファイル)
    - icon.png (アイコン画像ファイル)
    

    helloworld.html はアプリケーションの本体となるファイルです。通常は JavaScript や CSS を外出しにして外部から読み込む形になるのが一般的ですが、今回は(それほど複雑な内容ではないので)全て1ファイル内に定義することにします。

    manifest.webapp がマニフェストファイルです。アプリケーションの属性を指定するファイルです。Android アプリケーション開発の経験がある方であれば、同様のマニフェストファイルの存在をご存知だと思います。

    そして icon.png はアプリケーションのアイコンです。無くても(デフォルトのアイコンが使われるだけなので)いいんですが、アプリをいくつも作っているとアイコンだけで区別できなくなってしまうので、とりあえず用意しておきました。今回は128x128 ピクセルのファイルを用意しました。Firefox OS アプリで使う画像ファイルには何種類かありますが、マーケットにリリースする際には 128x128 の画像ファイルが必要になります。その意味もあって最初に用意しておきました。なお、このファイルはマニフェストファイルで指定するので、ファイル名は何でも構いません。今回はこのようなシンプル(?)な画像を用意しました。
    2014110401


    icon.png は画像ファイルなので、あらかじめ用意しておく必要があります。では残る2つのテキストファイルをこれから作っていきましょう。


    まずはこのアプリケーションプロジェクトを作るために専用のフォルダ(ディレクトリ)を1つ作っておきます。例えば c:\tmp\helloworld といったフォルダを作ります。これから3つのファイルをこのフォルダ内に作ります(icon.png はこのフォルダ内にコピーしておきます)。Eclipse を使う場合は "Static Web Application" のプロジェクトを作成して、全てのファイルをプロジェクトフォルダの直下に配置することになります。最終的にはこの図のようなフォルダ/ファイル構成になるように作っていきます:
    2014110402


    ではフォルダ直下に helloworld.html という名前の HTML5 ファイルを以下の内容で作成します。なお、日本語を使う場合のエンコードは UTF-8 にしてください:
    <!DOCTYPE html>
    <html lang="ja">
    <head>
    <meta charset="UTF-8"/>
    <title>Hello World</title>
    <script>
    function sayHello(){
    	alert( "Hello World!" );
    }
    </script>
    <body>
    <header>
    <h3>Hello World.</h3>
    </header>
    <p><button onclick="sayHello();">メッセージ</button></p>
    </body>
    </head>
    </html>
    

    この HTML5 ファイルの内容についてはあまり解説は要らないと思っています。画面上に <h3> によるタイトルと、ボタンを1つ配置し、そのボタンがクリックされると sayHello() 関数が呼ばれ、画面に "Hello World!" というアラートダイアログが表示される、というだけのものです。

    続いてマニフェストファイルを作成します。フォルダ直下に以下の内容で manifest.webapp という名前で、JSON 形式のテキストファイルを作成します:
    {
      "name": "Hello World App",
      "description": "FireFox OS 用 Hello World アプリ",
      "launch_path": "/helloworld.html",
      "icons": {
        "128": "/icon.png"
      }
    }
    

    この manifest.webapp ファイルの中ではアプリケーションの情報を指定しています。まず "name" でアプリケーションの名前を128文字以内で定義します(必須)。"description" では、これがどのようなアプリケーションなのか、その説明を1024 文字以内で記述します(必須)。 "launch_path" では最初に読みこむファイルを指定します(必須)。

    "icons" ではアプリケーションのアイコンを配列型で指定します。サイズに応じた指定が可能になっており、1つしか登録しない場合でも配列にする必要があります。この例では "128" を指定しているので 128x128 のアイコンが登録されています。マーケットプレースに登録する場合は最低でも 128x128 のアイコンが必要です。

    マニフェストファイルに指定する項目は他にもあるのですが、今回はこれだけで動きます。では実際に Firefox OS シミュレータを使って動かしてみましょう。こちらを参考にして Firefox OS シミュレータのダッシュボード画面を起動します。そして "Add Directory" ボタンをクリックして、上記で作った manifest.webapp ファイルを指定します:
    2014110403


    manifest.webapp に書かれた内容に従ってアプリケーションやアイコンが読み込まれ、ダッシュボードに表示されます。このアプリケーションが読み込まれた状態で左の "Simulator: Stopped" と書かれた箇所をクリックして Firefox OS シミュレータを起動します:
    2014110404


    HelloWorld アプリケーションが Firefox OS シミュレータの中で実行されている状態で起動します:
    2014110405


    「メッセージ」ボタンをクリックすると、(JavaScript でプログラミングした通りに)メッセージボックスが表示されるはずです:
    2014110406


    これだけ、HTML5 で作ったアプリケーションが Firefox OS(シミュレータ)内で動作するところまでを確認できました。内容がシンプルという面もありますが、特別な環境なしのメモ帳程度のテキストエディタだけで作れるし、そんなに難しくなさそうですよね。

    もちろん実際にアプリを作ろうとすると、CSS やライブラリを使ってモバイル端末に最適化したUIを用意したり、各種 API を利用するための権限設定をしたり、データの受け渡しの方法を設計した上でマニフェストに加えたり、と色々やることはあります。 ただベースとなるアプリはこの程度のコーディングで実現できる、というのはやはり魅力だと思っています。


     

    このページのトップヘ