Jake's BLOG

最近のIT技術動向と社会問題など・・・・

Chromecast Audio のラインアウト(アナログ)は使えるか?

答え:使えない

Chromecast Audioではかの有名のIFIXITの分解によると旭化成のDAC AK4430というチップが使われています。192kHz 24bit対応の高性能です。

一方で、Chromecastは出力として、ステレオミニジャックで、アナログと光出力の2系統を用意しています。

想定できる接続形態は

1. Chromecast Audio → (内臓DACのアナログ出力)→ (メイン)アンプ → スピーカー

2. Chromecast Audio → (内臓光出力)→ (外部)DAC →(メイン)アンプ → スピーカー

の2通り考えられます。

私自身は、2の構成、AVアンプをDAC兼プリアンプとして使っていたので光出力での音質を堪能していたのですが、小さなChromecast Audioに内蔵されているAK4430なるDACのそのままの出力をアンプに直結してもそれなりにいい音が出るのではないかと思い試してみました。

結果は見事玉砕。ぺしゃんこの音になってしまいました。Chromecast Audioをいい音で聞きたければ是非とも光出力を利用しましょう。

Bluetooth aptX vs Chromecast Audio 音質対決

Bluetooth aptXでも音質劣化は避けられない

これまで Google Play Music のHiFiアンプへの再生方法として Bluetooth aptX で再生機器へ飛ばし、光デジタル出力をAVアンプの入力として再生していました。Google Play Musicが一般のDLNA機器へ直接飛ばせないのは、音楽コンテンツの著作権保護があるのだろうなと推察していますが、なにぶん、Bluetoothで飛ばしたところで、MP3 320kbps相当といわれるGoogle Play Musicの音質が損なわれていることで不満を持っていました。

Chromecast Audioなら高音質でGoogle Play Music再生

Chromecast Audio は Androidなどで対応したアプリから直接音声を飛ばせる機器で、通常のDLNAデバイスとしても機能します。24bit 96kHzのいわゆるHDクォリティをサポートしており、MP3 320Kbps 相当といわれる Google Play Music を最大限の音質で再生するための一番よいデバイスということなになります。

Google Play Music なら aptX でもほぼ変わらない音質だった

今回 Google Chromecast Audio を導入したので聞き比べをしてみました。

  • Google Play Music (MP3 320kbps相当)
  • FLAC 16bit 44khz(CD 無圧縮相当)
  • FLAC 24bit 96kHz(HDサウンド)

で比べてみましたが、私の(いいと思っている)耳で判別できる限りではGoogle Play Musicの音質はChromecastでもBluetooth aptXで聞いてもほぼ同じレベル。Chromecastはおそらく中でMP3をそのままデコードしているだけですから、一方で、いったんデコードされた音声を aptXで再圧縮して送ったにもかかわらず、それなりの音質を保っていることからいかに aptX が優秀かがわかります。

差が出たのは、FLAC 16bit 44.1kHz(いわゆるCDの無圧縮音質)から上の領域でした。私の(いいと思っている)耳でも明らかにその差がわかります。(24bit 96kHzの領域は音源が少ないのと、今回使ったスピーカーの限界でよくわかりません。)



Google Play Music
(MP3 320kHz)
FLAC 16bit 44.1kHz
FLAC 24bit 96kHz

Bluetoothレシーバー(aptX)




Chromecast Audio









今回使った機器は次のとおり

Chromecast Audio

https://www.google.co.jp/intl/ja_jp/chromecast/speakers/

Hanwha apt-X/AAC対応 Bluetoothレシーバー HS-BMR002

http://www.amazon.co.jp/dp/B00I04Z314/ref=cm_sw_r_tw_dp_KHruxb0F6SAB2

ONKYO GX-D90

http://www.jp.onkyo.com/pcaudio/poweredspeaker/gxd90/


Javascriptで作るFlashAir(TM)アプリ 4種

TOSHIBA FlashAir(TM) 職人心をくすぐる製品でしたので勢いあまって作ったユーティリティーをオープンソースコミュニティの役に立てればと放出します。

FlashAir Javascript client library

JavascriptからFlashAirのAPIをたたきます。ajaxの通信、日付、各種ステータス情報を、オブジェクト化およびカプセルしています。クライアントクラスは command.cgi?op=121 によるSDカードの更新状況の自動通知機能も装備されています。

ソースコードは

  • TypeScript(.ts)のソースコード
  • .tsをコンパイルした Javascript(.js) ファイル
  • Javascript(.ts) を uglify で圧縮した (.min.js)ファイルと .map ファイル
をパッケージしています。


以下のアプリはこのライブラリを使って作られています。

FlashAir List.htm カスタマイズ

FlashAirの隠しフォルダ SD_WLAN に List.htm というファイルを作ることでウェブブラウザ経由のページをカスタマイズできます。レスポンシブデザイン、およびサクサクパフォーマンスのカスタマイズを公開しています。ぜひお試しください。

利用はGitHubから [Download ZIP] でZIPファイルをダウンロードして SD_WLAN ディレクトリを上書きします。

FlashAirList (GitHub)





Yokin's FlashAir Sync (Chromeアプリ版)

同種の名前がいくつかあって、かぶりますが、その名の通り、クライアントとなるPCとFlashAirのファイル転送を手助けするツールです。Chromeアプリとして作られているので、Google Chromeの動作するWindows Linux Macなどで動作します。入手はGoogle WebStoreからどうぞ。ソースコードも公開(準備中)しています。


FlashAir Sync (Windows Store版)

同じJavascriptのライブラリを使用して作れるという点で、Windows 8専用のWindowsストアアプリが WinJSというJavascriptの開発プラットフォームを利用します。

https://www.microsoft.com/ja-jp/store/apps/yokins-flashair-sync/9nblggh1mtvm


FlashAir Sync (Cordova版) 希望

同じJavascriptの開発プラットフォームならAndroid iOS対応のアプリも開発ができるはずです。・・・目下遠い目標として。


Javascrip で作る FlashAir (TM) クライアントアプリ - FlashAir W-03 の挙動まとめ

東芝 FlashAir W-03 の挙動まとめ

「FlashAirをいじってわかったこと。」

https://sites.google.com/site/gpsnmeajp/electricmemo/flashair
にいろいろとまとめてくださってあり、非常に参考になります。


ここでは、ウェブプログラマ的視点で、私が気が付いたことを、ネタがかぶらない範囲でまとめておきます

ちなみに私の手持ちのFlashAir W-03のファームウェアバージョンは

FA9CAW3AW3.00.00

GET /command.cgi のキャッシュ防止には &TIME=xxxx を付ける。 → jQueryの {cache:false}は使えない。


HTTPのGETメソッドは通常のアクセスと同様に、ブラウザがキャッシュすることで同じページを2度目に開くときに時間を短縮します。が、時によってこの機能が邪魔な場合があります。
FlashAirの場合 command.cgi?op=121 でSDカードの更新状態を定期的にチェックするのですが、ブラウザがこの呼び出しの結果をキャッシュしてしまうとSDカードが更新されてもずっと同じ結果(タイムスタンプ)が返ってくるため期待した結果と異なることになります。
そこで、簡単な解決策としてアクセスするURLを少しずつ変えることでいつも新しいページを開くようにブラウザのキャッシュを回避する方法があります。
GET /command.cgi?t=10000
とアクセスした後
GET /command.cgi?t=10001
と適当に異なる番号をつければ、ブラウザは別のページだと解釈してかならず、サーバーから新しいコンテンツを取り寄せます。

ところが、FlashAirの /command.cgiは ?TIME=xxxx という書式は受け付けますが ?t=xxxx という書式だとなぜかエラー。
さらに困ったことに、、
jQuery.ajax には  { cache: false } というオプションがあって先に説明した方法でブラウザのキャッシュを回避するオプションがあるのですが、これが t=xxxx という書式でURLの末尾に追加する方法なのです。このオプションはFlashAirに対しては使えません。

SDカードにカンマ(,)を含むディレクトリ名があるとそのページの自動更新が失敗する。


ウェブブラウザで接続した際の単純なバグです。

https://flashair-developers.com/ja/documents/api/commandcgi/#100
ではカンマを含むファイル名、ディレクトリ名について言及しているだけに残念です。
List.htmに挿入するための <!--WLANSDJLST--> というキーワードは" "で囲まれた書式のため心配が要りませんが
http://flashair/command.cgi?op=100&DIR....
の結果はCSV表現なのですがカンマの含まれる名前はEXCELなどでなされる""で囲むなどの処理がされていないため、読み込みプログラム側で柔軟に解析しないと失敗します。拙作の FlashAir List.htmChromeアプリ版 FlashAir Sync はこれの対策を入れてあります。

同時にダウンロードできるファイルは1つだけ ← HTTP(WiFi)経由でファイルをダウンロード中は API の応答が停止する。


ApacheやIISなどモダンなウェブサーバーになれているとハッとさせられる挙動です。大きな画像ファイル、デジカメのRAWファイルなどある程度転送の時間がかかる場合に注意が必要です。
ファイルは1つずつしか転送できません。APIにアクセスするアプリが同時に多数ある場合は、応答のパフォーマンスに配慮する必要があります。
当初、私は、複数のディレクトリを指定してゲリラ的にローカルと同期させるようなアプリを考えていましたが、この制限によって、ファイルの自動転送は、ディレクトリごと1つ1つが正しい作法だということになりました。

※WiFi経由の転送は同時に1つに制限されますが、その間SDカードへの直接書き込みはできまるから、デジカメでの撮影が滞ることはありません。

ブラウザユーティリティー <!--WLANSDJLST--> より command.cgi?op=100 のほうが速い


SDカードに内蔵のウェブサーバーは、小電力の限界もあってパフォーマンス的に期待するのは酷なのは想像に難くありません。
結論としては、SDカード側で難しいことはさせず、複雑な処理はできる限りクライアント側でやらせるというアプローチがいいことになります。

当方では List.htm のカスタマイズを公開していますが、当初 <!--WLANSDJLST--> を使ったアプローチで作成しましたが、
ディレクトリを移動するごとに HTMLのすべての転送、js/cssなどのファイルの転送が発生し、ページの表示が完了するまで数秒の待ちが発生してしまいました。
FlashAirオリジナルの List.htm の場合も同様です。
そこで、改良版として List.htm に <!--WLANSDJLST--> を一切含まないものを作成して、ファイルのリストはAPIの
/command.cgi?op=100&DIR=....
で取得するように変更しました。
その結果ページの表示に関して2割程度のスピードアップ。さらに、ディレクトリの移動はURLをリンクするのではなく、
ページ内のJavascriptからAJAXでAPIからファイルリストを取得しなおす「シングルページアプリ」として作ることで、
ディレクトリの移動も含め劇的に快適なスピードでアクセスできるようになりました。

FlashAirのウェブサーバーはブラウザのキャッシュ機構に優しくない・・・

ブラウザカスタマイズで List.htm を作成した場合、関連する jsやcssファイル、画像ファイルなどがSDカード側からロードされるのですが、どうやら、サーバー(FlashAir)のレスポンスに、サーバーがLast-Modifiedなどキャッシュ関連のヘッダーが付いておらず、唯一ETag がついているもののウェブブラウザからの If-None-Match に応答している気配がありません。したがって、ページを開くたびに関連するファイルがすべて転送されることとなり、ページが表示しきるまでの待ち時間がそれなりに生じてしまいます。

拙作 List.htm カスタマイズではディレクトリの移動を ajax でのみ更新するシングルページアプリとしたことでこの問題を回避しています。

luaスクリプトは・・・将来に期待


電子工作などの活用でいろんな記事を見ていると可能性があって面白いです。ただ、「写真を共有」「ファイルをダウンロード」的な典型的な使い方の範囲では先述の理由もあって、パフォーマンス、電力消費、安定性などからAPIを軽くたたきつつ、重い処理はウェブブラウザで・・・というアプローチが現状快適です。

たとえば、command.cgi?op=121 更新タイムスタンプの取得 はSDカードのどこかのディレクトリに変更があった場合に更新されますが必ずしも現在注目している(ブラウザで開いている)ディレクトリであるとは限りません。luaとの組み合わせで、特定のディレクトリのみをモニターする仕組みを考えたのですが、command.cgi?op=100 で変更があったかもないかもしれないファイルリストの列挙を呼び出すだけのほうが実はFlashAirの負荷が少ないんじゃないかと思ったりします。

一方で、1つのディレクトリに1万個以上の大量のファイルがある場合に、ファイルリスト取得のAPIは毎回巨大サイズのCSVファイルを転送することになり非効率です。欲を言えば、 command.cgi?op=100にページングのオプションが付けられないでしょうか。




jCryption.NET HTTP FORM POSTの暗号化

jCryptionとは

http://www.jcryption.org/

jCryptionはウェブページのフォームデータをHTTPでPOSTする際に、データをRSA/AESで暗号化してサーバーに送信するjQueryのプラグインライブラリです。

jCryption.NETはjCryptionの送信した暗号データをサーバーで復号化するためのASP.NET用のライブラリです。

背景

ログインフォーム、問い合わせフォーム、アンケートフォームなど、ウェブページ上で利用者の入力を受けサーバーへデータを送信する場面は多くあります。一方で、個人情報、IDやパスワードなど他者に見られたくない内容を含む場合、そのフォーム送信は「暗号化」によって保護された状態で送信されるべきで、通常、平文通信であるHTTPでの送信は避けHTTPSで保護されるべきです。しかし、HTTPS/SSLの通信をサポートするためには高額(ベリサインの場合8万円程度)のサーバー証明書のための費用とウェブサーバーのIPアドレスを一つ独占するというコストがかかり、ちょっとしたアンケートフォームなどの目的には導入を躊躇する場面もあります。

本格的にウェブサーバーとの通信にHTTPS/SSLを導入することなく、クライアントからサーバーへ送信される情報を暗号化により保護しようというのがjQueryのプラグインであるjCryptionというJavascriptのライブラリのコンセプトです。

一般にHTTPS/SSLには

  1. 通信の相手であるサーバーが本物であるかどうかを保証すること(サーバー証明書)
  2. 通信中のデータの暗号化

の2種類の目的がありますが、jCryptionは2つ目の通信中のデータの暗号化のみをサポートします。

jCryptionが使用する暗号は RSA/AESで、RSAはクライアント側で生成されたAESの鍵を暗号化してサーバーへ送信するために使われます。実際にPOSTされるフォームのデータはAESで暗号化されて送信されます。

これから先のjCryptionそのものの詳しい内容は本家のウェブサイトに譲ります。http://www.jcryption.org/(英語)

ここでは、jCryption.NETを使った例を紹介します。

jCryption.NET

ソースコードとサンプルはGitHub で公開しています。

クライアント側の使用法は本家で解説されているように、jQueryとjCryptionのライブラリを<script>で読み込み
 $(“#form”).jcryption(….)
で指定したフォームをjCryption対応にセットします。
このフォームを送信するにあたって、暗号の鍵の交換、データの解読にサーバー側のプログラムが必要になります。本家のパッケージではPHPやPerl CGIによる実装がすでに含まれています。ここでは、Windows Server + IIS + ASP.NETな環境でサーバー側の実装を紹介します。

想定環境

  • IIS6以上のウェブサーバー(Windows 2003 Server以降)
  • .NET 4.0 以上の.NET Framework
  • ASP.NET WebPagesと呼ばれる cshtml ファイルによるページ

ASPXページの場合

ページをjCryption.SecurePage を継承させることで、暗号処理を処理させられます。javascript部分は手作業で挿入します。
<%@  Language="C#" Inherits="jCryption.SecurePage" %>
<html>
<head>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script type="text/javascript" src="Scripts/jquery.jcryption.3.1.0.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#normal").jCryption({
                getKeysURL: "<%= Request.Path %>?getPublicKey=true",
		            handshakeURL: "<%= Request.Path %>?handshake=true"
		        });
		    });
    </script>
</head>

CSHTMLページの場合

  1. jCryption.csファイルを App_Codeフォルダへ配置します。
  2. フォームのあるcshtmlページの先頭部分に@using jCryption を記述します。
  3. 鍵の交換、復号化などを同じページ上(URL)で行うため、処理の初めに
    jCryption.HandleRequest(Request);
    を呼び出します。この呼び出しの後で、Request.Formは復号化されたデータにアクセスできます。
  4. HTML内で、依存するjqueryを読み込みます。jquery.validateも読み込み可能です。
  5. HTML内、上記のjqueryの読み込みの後、適当な場所に
    @jCryption.RenderScriptFor(“#login”, src:”/Scripts/jquery.jcryption….js”);
    を記述します。jcryptionのjsファイルを直接<script>で指定しないのは、このページがHTTPSでアクセスされた場合に、自動的にjcryptionを無視して読み込まない動作を行うためです。

@using jCryption
@{
    jCryption.HandleRequest(Request);
}
<!doctype html>
<html>
   <head>
      <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
      @jCryption.RenderScriptFor("#normal", src: "Scripts/jquery.jcryption.3.1.0.js")
   </head>
   ......
   <form id="normal">
      .....
   </form>
   ......

フォームの初期データ、部分的にHTMLを暗号化して読み込む機能

jCryption.NETをcshtmlで使用する場合に、フォームデータと、HTMLテキストの暗号化にも対応しています。もともとjCryptionはクライアントからサーバーへ送信されるデータの保護を目的としていますが、ログインIDやパスワード、住所氏名などはフォーム上であらかじめわかっている内容を入れてHTML出力されることがあります。この場合HTTPのままですと、データは平文で保護されておらず、片手落ちとなります。

  1. フォームの各要素を次のように記述します。
    <input type=text name=Name value=”山田” />は
    <input type=text @jCryption.SecureNameValue(“Name”,”山田”) />

    チェックボックスやラジオボタンは
    <input type=”checkbox” @jCryption.SecureNameValueCheck(“Sex”,”Male”, true ) />

  2. HTMLまたはテキスト領域を暗号化で保護したい場合は
    @jCryption.SecureHtml()
    @jCryption.SecureText()
  3. 上記の呼び出しの後に
    @jCryption.LoadSecureContents()
    を呼び出します。ここに、一連の復号化処理、サーバーとの通信処理が記述されます。

ライブラリリファレンス

@jCryption.RenderScriptFor( String selector, String src = null, String script = null )

jCryption を利用するFORMをjQueryのセレクタで指定します。この呼び出しは通常 head タグか body 内最後の方に配置します。jquery.validate と同時に使用する場合は jquery.validate を読み込む script タグの後になるようにします。

selector: jCryptionで初期化する対象のFORMを jQuery のセレクタで指定します。

src: jCryption のjsファイルへのパスを指定します。

script: その他のjavascriptが必要な場合に <script>...</script>を含むHTMLを指定します。


@jCryption.SecureNameValue( String name, String value )

フォーム要素の初期状態で表示されるデータを保護します。<input> タグ内に記述します。.

 <input type='text' @jCryption.SecureNameValue("Name", "Smith") />

input タグの name と value を引数に指定します。


@jCryption.SecureNameValueCheck( String name, String value, bool check )

input の checkbox radiobutton に対応したバージョンです。
    <input type='checkbox' @jCryption.SecureNameValueCheck("Animal", "Dog", true ) />
    <input type='checkbox' @jCryption.SecureNameValueCheck("Animal", "Cat", false ) />

@jCryption.SecureText( String text )

暗号化されたテキストコンテンツを設定します。HTML内の任意の位置に記述可能です。<div>で囲まれ暗号化データとともにHTMLとして出力されます。

@jCryption.SecureHtml( String html )

暗号化されたHTMLコンテンツを設定します。HTML内の任意の位置に記述可能です。<div>で囲まれ暗号化データとともにHTMLとして出力されます。

@jCryption.LoadSecureContents()

SecureNameValue/SecureNameValueCheck/SecureText/SecureHtml の内容を保護された実際のデータをDOM要素中に読み込むことを指示します。上記4種類メソッド呼び出しの一番最後にこの呼び出しを追加してください。

jCryption version 3.1.0に内在するいくつかの技術的問題点

最後に本家jCryptionに見られるいくつかの技術的問題点を指摘しておきます。

  1. AESの鍵が<form>単位で異なる問題
    クライアントはformのsubmit時にAESキーを生成しajax呼び出しでサーバーに通知します。この時サーバーはSessionにAES鍵を保存します。同じSession上で同時にformのsubmitが起こることはまれですが、原理上、クライアントとサーバーで鍵の1対1の整合性が保てていません。
  2. 先の理由により、<form> POST後にブラウザのF5(更新)によって再度POSTした場合にSession上に保存された鍵との一貫性の保証がなく、再POSTがエラーとなります。これには、POSTデータに暗号化されたフォームデータとともに暗号化されたAES鍵も同時に送信されるようにすべきです。
  3. jquery.validateライブラリと一緒に使用したときの問題があります。jCryptionはSubmitボタンclickイベントをフックして暗号化処理ののち改めてformのsubmitを呼び出します。一方で、jquery.validateはformのsubmitイベントでvalidationを実行しています。処理順序が逆転することで2つのライブラリを同時に使用することができません。(jCryption.NETではこの点を改善するjavascriptのコードを出力しています。)
※上記の問題を修正したバージョンを jquery.jcryption.3.1.0.mod.js として同梱しています。


Fork me on GitHub

ASP.NET WebPagesと相性のよい w2ui grid

ASP.NET WebPages と Razor(cshtml)の組み合わせに相性のよいTableソリューションを求め歩いていたところ DataTablase に続いて w2ui という優れたライブラリを発見。

w2ui はレイアウト、グリッド、ツールバー、サイドバー、タブ、フォーム、ポップアップその他のユーティリティで構成されるライブラリですが、軽量設計で使いやすくまとまっています。

今回Gridを試したわけですが、このGridの優れたところは、ページングの概念を捨てて「無限スクロール」を導入したこと。しかもその動作は軽快です。

DataTablesと同様にサーバーへは form data、サーバーからはJSONによる応答です。form dataの仕様がこちらのほうが簡単で使いやすいかもしれません。また、開発中の Ver 1.4 には拙翻訳を提供したので日本語表記の利用も可能です。

jQuery DataTablesでやったことと同じことを w2ui を使って再現してみます。

1:  @{  
2:       if (IsPost)  
3:       {  
4:            if (UrlData[0] == "GetList" )  
5:            {  
6:                 var all = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures).AsEnumerable<System.Globalization.CultureInfo>();  
7:                 var filtered = all;  
8:                 int iDisplayStart = Request["offset"].AsInt(0);  
9:                 int iDisplayLength = Request["limit"].AsInt(10);  
10:                 bool sorted = false;  
11:                 for (int i = 0; Request[String.Format("sort[{0}][field]", i)] != null; i++)  
12:                 {  
13:                      String fld = Request[String.Format("sort[{0}][field]", i)];  
14:                      bool asc = Request[String.Format("sort[{0}][direction]", i)] == "asc";  
15:                      switch( fld)  
16:                      {  
17:                           case "Name":  
18:                                filtered = asc ? filtered.OrderBy(m => m.Name) : filtered.OrderByDescending(m => m.Name);  
19:                                break;  
20:                           case "DisplayName":  
21:                                filtered = asc ? filtered.OrderBy(m => m.DisplayName) : filtered.OrderByDescending(m => m.DisplayName);  
22:                                break;  
23:                           case "TwoLetterISOLanguageName":  
24:                                filtered = asc ? filtered.OrderBy(m => m.TwoLetterISOLanguageName) : filtered.OrderByDescending(m => m.TwoLetterISOLanguageName);  
25:                                break;  
26:                           case "ThreeLetterISOLanguageName":  
27:                                filtered = asc ? filtered.OrderBy(m => m.ThreeLetterISOLanguageName) : filtered.OrderByDescending(m => m.ThreeLetterISOLanguageName);  
28:                                break;  
29:                      }  
30:                      sorted = true;  
31:                 }  
32:                 if (!sorted) { filtered = filtered.OrderBy(m => m.Name); }  
33:                 for (int i = 0; Request[String.Format("search[{0}][field]", i)] != null; i++)  
34:                 {  
35:                      String fld = Request[String.Format("search[{0}][field]", i)];  
36:                      String sSearch = Request[String.Format("search[{0}][value]", i)];  
37:                      if (!String.IsNullOrEmpty(sSearch))  
38:                      {  
39:                           switch (fld)  
40:                           {  
41:                                case "Name":  
42:                                     filtered = filtered.Where(m => m.Name.Contains(sSearch));  
43:                                     break;  
44:                                case "DisplayName":  
45:                                     filtered = filtered.Where(m => m.DisplayName.Contains(sSearch));  
46:                                     break;  
47:                                case "TwoLetterISOLanguageName":  
48:                                     filtered = filtered.Where(m => m.TwoLetterISOLanguageName.Contains(sSearch));  
49:                                     break;  
50:                                case "ThreeLetterISOLanguageName":  
51:                                     filtered = filtered.Where(m => m.ThreeLetterISOLanguageName.Contains(sSearch));  
52:                                     break;  
53:                           }  
54:                      }  
55:                 }  
56:                 var output = new  
57:                 {  
58:                      status = "success",  
59:                      total = filtered.Count(),  
60:                      records = filtered.Skip(iDisplayStart).Take(iDisplayLength).AsEnumerable().Select((o, i) => new { recid = o.Name, o.Name, o.DisplayName, o.TwoLetterISOLanguageName, o.ThreeLetterISOLanguageName })  
61:                 };  
62:                 Response.ContentType = "application/json";  
63:                 Response.Write(Json.Encode(output));  
64:                 Response.End();  
65:                 return;  
66:            }  
67:       }  
68:  }  
69:  <script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>  
70:  <link rel="stylesheet" href="http://w2ui.com/src/w2ui-1.3.min.css" type="text/css" />  
71:  <script src="http://w2ui.com/src/w2ui-1.3.min.js"></script>  
72:  <script>  
73:    $(document).ready(function () {  
74:         $('#sampleTable').w2grid({  
75:              name: 'grid',  
76:              header: 'List of Names',  
77:              show: {  
78:                   toolbar: true,  
79:                   footer: true  
80:              },  
81:              columns: [  
82:                      { field: 'Name', caption: 'Name', size: '20%', sortable: true, attr: 'align=center' },  
83:                      { field: 'DisplayName', caption: 'DisplayName', size: '30%', sortable: true, resizable: true },  
84:                      { field: 'TwoLetterISOLanguageName', caption: 'TwoLetterISOLanguageName', size: '30%', sortable: true, resizable: true },  
85:                      { field: 'ThreeLetterISOLanguageName', caption: 'ThreeLetterISOLanguageName', size: '30%', resizable: true }  
86:              ],  
87:              searches: [  
88:                      { field: 'Name', caption: 'Name', type: 'text' },  
89:                      { field: 'DisplayName', caption: 'DisplayName', type: 'text' },  
90:                      { field: 'TwoLetterISOLanguageName', caption: 'TwoLetterISOLanguageName', type: 'text' },  
91:              ],  
92:              sortData: [{ field: 'Name', direction: 'ASC' }],  
93:                 url: '@Href(Request.AppRelativeCurrentExecutionFilePath,"GetList")'  
94:         });  
95:    });  
96:  </script>  
97:  <div>  
98:       <div id="sampleTable" style="height: 600px;">  
99:       </div>  
100:  </div>  

ASP.NET WebPagesと相性のよい jQuery DataTables

ASP.NET WebPages (MVCではない)で、Razor(cshtml)で書くのがマイブームです。ASPXより圧倒的に書きやすく、MVCほどコードの分散が起こらず、一人でコードを書くには最適な選択だと感じています。

さて、cshtml上で、サーバーサイドのデータでもって、ページング、ソート、フィルター付きのテーブルを探していた時に jQuery DataTables にたどり着きました。ほかにも類似のTableソリューションはたくさんありますが、これが、軽量、簡単で ASP.NET Web Pagesと非常に相性がいいと思ったので紹介します。

「DataTables https://datatables.net/index はjQueryのプラグインライブラリで、HTMLのtableに機能を追加する形で動作します。」

詳細は原典を参照してもらうことにし、ASP.NET Web Pages から使用する場合のキーポイントに絞ると

  • AJAXによるサーバーサイドからのデータの取り出しに対応。
  • サーバーサイドのページング、ソート、フィルター(検索)に対応している。
  • サーバーへのリクエストは FORMエンコーディングで、サーバー側のパラメータの取り出しに Request.Form が使える。
  • クライアントへの応答は json で、サーバーからは Json(System.Web.Helpers.Json)で簡単に応答できる。
  • 受け渡すデータは型が規定されていない(ただの配列渡し)のでルーズだが、簡単。サーバーサイドでLINQと anonymous object を組み合わせれば非常に簡単。

サンプルとしてシステムにある System.Globalization.CultureInfo を列挙してテーブルに表示するコードを書いてみました。1ページアプリとしてもこんなに簡単に短く、高機能テーブルを書くことができました。

http://www.yo-ki.com/study/ASPNET/ExperimentDataTables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@{
    if( IsPost ){
        if( UrlData[0] == "GetList" ){
            String sEcho = Request.Form["sEcho"];
            int iDisplayStart = Request.Form["iDisplayStart"].AsInt(),
                iDisplayLength = Request.Form["iDisplayLength"].AsInt(),
                iSortingCols = Request.Form["iSortingCols"].AsInt();
            String sSearch = Request.Form["sSearch"];
            var data = new List<object>();
            var all = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures);
            var filtered = all.AsEnumerable<System.Globalization.CultureInfo>();
            if( !String.IsNullOrEmpty(sSearch) ){
                filtered = filtered.Where(p => p.DisplayName.Contains(sSearch) || p.Name.Contains(sSearch) || p.ThreeLetterISOLanguageName.Contains(sSearch) || p.TwoLetterISOLanguageName.Contains(sSearch));
            }
            for( var i=0; i<iSortingCols; i++ ){
                var iSort = Request.Form["iSortCol_" + i].AsInt(-1);
                var ascending = Request.Form["sSortDir_" + i];
                if( ascending == "asc" ){
                    filtered = filtered.OrderBy(p => iSort == 0 ? p.Name : iSort == 1 ? p.DisplayName : iSort == 2 ? p.TwoLetterISOLanguageName : p.ThreeLetterISOLanguageName);
                }
                else
                {
                    filtered = filtered.OrderByDescending(p => iSort == 0 ? p.Name : iSort == 1 ? p.DisplayName : iSort == 2 ? p.TwoLetterISOLanguageName : p.ThreeLetterISOLanguageName);
                }
            }
            var result = filtered;
            foreach( var ci in filtered.Skip(iDisplayStart).Take(iDisplayLength) ){
                var row = new List<object>();
                row.Add(ci.Name);
                row.Add(ci.DisplayName);
                row.Add(ci.TwoLetterISOLanguageName);
                row.Add(ci.ThreeLetterISOLanguageName);
                data.Add(row);
            }
            Response.Clear();
            Response.ContentType = "text/json";
            Response.Write( Json.Encode(new {
                sEcho = sEcho,
                iTotalRecords = all.Count(),
                iTotalDisplayRecords = filtered.Count(),
                aaData = data
            }));
            Response.End();
            return;
        }
    }
}
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>

<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css" type="text/css"/>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.js"></script>

<script>
    $(document).ready(function () {
        $("#sampleTable").dataTable({
            "bProcessing": true,
            "bServerSide": true,
            "bPaginate": true,
            "sPaginationType": "full_numbers",
            "sServerMethod": "POST",
            "sAjaxSource": '@(Request.Path)/GetList'
        });
    });
</script>
<div>
    <table id="sampleTable">
        <thead>
            <tr><th>Name</th><th>DisplayName</th><th>TwoLetterISOLanguageName</th><th>ThreeLetterISOLanguageName</th></tr>
        </thead>
        <tbody>
        </tbody>
    </table>
</div>

VAIO DUO 11をWiMAX - WiFi アクセスポイントにする方法(マニュアル設定編)

ここでは WiMAX 搭載 SONY VAIO DUO 11 を前提に、WiFiアクセスポイントとして動作させるための方法を紹介します。

特別に必要なもの

  • USB WiFi アダプター

私は Logitechの LAN-W300N/U2S というUSBアダプターを使いました。一般的に動作可能であればなんでもいいでしょう。

前置き

VAIO DUO 11ではオーダーメイドで構成としてWiMAX搭載を選択することができます。VAIO単体で電源オンからすぐにネットへつながる便利さは格別です。DUO11を中心にネット環境を充実させるべく次に考えることが、DUO11をWiMAX母艦にして、周辺のデバイス(スマートフォンやタブレットなど)からWiFi経由でインターネットを利用したいということです。

ところが、DUO11には、これを実現するために致命的な問題が1つあります。「WiMAXを使用中はWiFiデバイスは使用できない」というもの。つまりWiMAXとWiFiは排他利用だということです。これは、Intel のワイヤレスデバイスの仕様によるもので、OSやソフトなどではどうしようもないのです。

そこで、次善のアイデアとして別途USBでWiFiアダプタを追加してやればそれを通じてWiMAXを共有できるかもしれないというものです。

手順

ここで紹介する手順はコマンドプロンプトを管理者モードで操作するもので、自信のある方のみ自己責任でチャレンジしてください。

1.USB接続のWiFiアダプタが正常にインストールされているか確認

2.本体に内蔵のWiFiアダプタを「無効」に

 

3.コマンドプロンプトを管理者モードで開きます。

C:\Windows\system32>netsh wlan set hostednetwork mode=allow ssid="DUO11WiMAX" key="ILoveDuo11"

ssidはその名の通りアクセスポイントの公開名称となるSSID。keyは接続するときのパスワードになります。正常に実行されれば次のように表示されます。

ホストされたネットワーク モードは許可に設定されています。
ホストされたネットワークの SSID が正常に変更されました。
ホストされたネットワークのユーザー キー パスフレーズが正常に変更されました。

次のコマンドでWiFiアクセスポイントをスタートさせます。

C:\Windows\system32>netsh wlan start hostednetwork

ホストされたネットワークが開始しました。

この時点で、「ネットワーク接続」にSSIDの名前とともに新しい接続(ここでは「ローカルエリア接続16 Microsoft Hosted Network Virtual Adapter 」という名前)が1つ増えていることを確認します。

4.WiMAXのアダプタを「インターネット共有」モードに設定します。

「ローカルエリア接続 Intel Centrino  WiMAX 6150」を右クリックしてプロパティを開き、共有のタブをクリックします。

「ネットワークの他の・・・許可する」をチェックして、ホームネットワーク接続から先ほど確認した Microsoft Hosted Network Virtual Adapter である「ローカルエリア16」を選択します。

OKを押してプロパティを閉じるとようやくWiFi経由でWiMAXが使えるようになります。

 

 

5.元に戻す場合

使い終わって、元に戻す場合は次の3点

  1. コマンドプロンプトから
    C:\Windows\system32>netsh wlan stop hostednetwork
  2. 共有プロパティからチェックをはずしてOKを押す。
  3. 「無効」設定したWiFiアダプタを再び「有効」に戻す。

 

 

 

 

 

Yokin's 2ch Browser を Windows ストアアプリで公開

2015年3月から2ch専用ブラウザに関して2ch運営元の方針変更により当アプリはWindowsストアから撤去する予定です。ご利用ありがとうございました。

Windows 8/RT のいわゆるストアアプリ (Windows Store App)のプログラミングから登録手続きまでの流れの学習を兼ねて作成しました。2chリーダーです。アプリストアで検索したところでは、いまだ2chリーダーは登録されていないようでしたので、気合を入れて作ってみました。Androidでは 2chMate にいつもお世話になっているので、仕様は2chMateを強く意識した作りにしました。

http://apps.microsoft.com/windows/ja-JP/app/yokins-2ch-browser/203ac772-a869-4c3d-ac09-0e5d9b4499a1

主な実装済みの機能は

  • 板一覧 -> スレッド一覧 -> 記

    事 までの UIをWindowsアプリの作法でナビゲーション

  • 各板のバナー画像を自動的に取得
  • URLからブラウザを起動
  • 書き込み内の画像リンクとYoutubeリンクのサムネイルを表示

近く実装したい機能は

  • スレッドのソート
  • 記事のソート機能(タグによる参照順など)
  • IDによるフィルター
  • 各種設定画面の追加

いずれ必要な機能は

  • Sync2ch 対応
  • 書き込みの機能

各所でお世話になっていることもあり、アプリ自体はフリーですが、Microsoft Advertising の広告を使用しています。Microsoftアプリストアを通じてダウンロード数や、ユーザーの反響などが得られるようなので、そちらを通じても、応援いただけたら機能追加の励みになります。

 

 

SONY VAIO DUO11の個人的評価

購入から1か月ほど経過したため簡単に個人的な評価を書いてみようと思います。

プロセッサ・メモリ

ネットの閲覧、動画・音楽の再生など通常の使用には問題なし。

ストレージ

オフィスを追加的に入れても空き容量は50GB。巨大な動画ファイルや大量の画像を保存するわけでもないので問題なし。

WiMAX

docomo LTEのXperia GXでテザリングして使う予定で、その保険としてWiMAXもつけました。携帯でのテザリングの不安定さに辟易した結果WiMAXを契約。オプションとしてつけておいて正解。LTEと比べると今や速度的にはそれほどアドバンテージがあるとはいなくなりましたが、接続の安定性は抜群。安心して使えます。ただIntelのWiMAXチップセットはWiFiと排他利用となるため、DUO11自身をWiMAX回線のアクセスポイントとして使うことができません。小型のUSB WiFiアダプターを追加すればできるかもしれませんが、未確認です。

USB WiFiアダプタを使ってWiMAXのアクセスポイントにする実験成功しました。

GPS×

Androidタブレットのノリでつけてみましたが、どうもGPSを使えるWindowsのアプリ(標準の地図プリを含む)がほとんどなく、宝の持ち腐れとなりました。

HDウェブカメラ×

これも、Androidタブレット的に、簡単にTwitterなどへ投稿するための写真が撮れたらと思って背面のカメラをつけてみましたが、本体が想像以上に重く、気軽に持ち上げて写真を撮る気も起らず、また、画質も最近の携帯についているものと比べるとありえないほど悪くがっかりでした。

キーボード○

個人的な趣味になりますが、英字キーボードを選択しました。おかげで、海外通販でキーボードカバーを入手して使うことができました。ご興味の方はAliexpressからどうぞ。

http://ja.aliexpress.com/wholesale?SearchText=SONY+VAIO+DUO11&catId=0

 

 

キーボードバックライト

こちらも、趣味の範囲と思い、省略。

拡張シートバッテリー○

かつてSONYストアで5000円で追加できた時期もあったようですが、私の時はそうではなく、また、巷で購入してもほとんど定価売り。ヨドバシカメラなら10%がポイントで戻ってくるので、結局あとで勝ったほうがお得ということで先延ばしにしました。DUO11を使い始めてからはやはりバッテリの持続時間は3時間前後といったところで、1日外にいるときなど、電源なしではとてもやってられません。そこで、シートバッテリですが、つけてさらに重く、背面のカメラで撮影とは、片手で支えるには重すぎの状態になりますが、それでも、フル充電で7時間前後の残り時間が表示されひとまず安心といったところです。日中の外出はあえてアダプターを置いて出ても大丈夫そうです。