2012年7月29日日曜日

プロジェクトごとにタブを管理できるChrome Extension "Project Tab Manager" を公開しました

かなり前から自分で欲しいと思っていた Chrome Extension を、ようやく Chrome Web Store で公開することができましたのでご紹介。

なぜ?

Chrome のブックマークの思想はとてもシンプルです。一度星を付けたら(ブックマークしたら)、基本的には Omnibox (URLバーのところ) から数文字打つだけで候補に出てくる、という極限までスリム化した使い方を想定しています。そのため、ブックマークはカテゴリに分けて整理して使いたい、という方にとっては痒いところに手が届かない部分もあるかもしれません。僕自身もそう感じるひとりです。

ブックマークの整理というと、僕も以前であればカテゴリーで分ける使い方がメインだったのですが、それだと探すのが面倒だし、結局検索してしまったり、直接URLを覚えて入力してしまうことが多々ありました。そこで最近は、カテゴリごとではなくプロジェクトごとにフォルダを作って、必要なブックマークを集めるという使い方をし始めていました。こうしておくことで、必要な時に必要なページをまとめて開く、といった使い方ができるからです。仕事柄複数のプロジェクトを同時並行で進めることが多いので、頭を整理しやすくなりますし、開いているタブの数を減らす効果も得られます。
ただ、このやり方だとプロジェクトに後からブックマークを追加しにくいとか、プロジェクトの一部だけど最初から開かないようにできない、とか、不便な点も少なからず残ります。そこで作ったのがこの Project Tab Manager 。完全に上記の使い方に最適化された Chrome Extension です。

基本的な使い方

インストールして頂くと、Omnibox の右側に Extension のボタン (フォルダの形をしたアイコン) が表示されます。これをクリックすると、何もプロジェクトが登録されていない状態の Project Tab Manager が開きます。

プロジェクトを登録するには、「新しいプロジェクト」に名前を入力して保存するだけ。今開いているウィンドウのタブがまとめてプロジェクトに保存されます。これを繰り返すことで、ウィンドウごとにプロジェクトが管理できるようになります。



次にプロジェクトを開きたい時は、プロジェクト名をクリックすれば、新しいウィンドウが開き、すべての必要なタブがまとめて開きます。ポイントのひとつは、この時にアクティブなタブだけが実際にウィンドウをロードするところ。別のタブを選択すると、そこで初めてページがロードされます (レイジーロード機能)。こうすることで、たくさんのタブを開いても、実際に使い始めるまでの時間は短縮できます。もちろんこの機能は設定でオフにできます。

Project Tab Manager からプロジェクトを開くと、プロジェクトのブックマークと共に、現在開いているまだブックマークしていないタブのページもグレーで表示されます。マウスオーバーした時に右側に表示される + をクリックすれば、そのタブのURLをプロジェクトに追加できます。
既にあるブックマークにマウスオーバーした場合は、四角形と✕が表示されます。四角形はアクティブ・パッシブを切り替えるもので、パッシブ (薄い青) にしておくと、プロジェクトの一部にはなりますが、プロジェクトウィンドウを開いた場合にタブとして追加されません。なので、必要な時だけ開く扱いにできます。✕をクリックすると、ブックマークは削除されます。



Project Tab Manager の特徴のひとつは、これらのブックマークがネイティブ機能のブックマークとして保存される点です。設定を見て頂くとわかりますが、デフォルトでは「その他のブックマーク」に「Project Tab Manager」というフォルダを作って、その下にプロジェクトごとのブックマークが作られることになります。こうすることで、Chrome Sync を利用してさえいれば、Chrome for Android や Chrome for iOS からでもプロジェクトのブックマークを利用可能です。外部のサーバーに保存されるといったことはありません。
また、Project Tab Manager 自体の編集機能は最低限に留め、表示順を変更したり、削除したりといったことはネイティブのブックマークマネージャーから行うことができます。



Project Tab Manager のもうひとつの特徴は、各プロジェクトにどれだけの時間を費やしたのかをあとで振り返ることができる点です。ウィンドウの右上に表示される時計アイコンをクリックすると統計画面が開き、どの時間帯にどのプロジェクトウィンドウを使っていたのか、どのプロジェクトに何割くらいの時間を割いたのかを確認することができます。

各プロジェクト名にマウスオーバーした場合は、ピンアイコンとゴミ箱アイコンが表示されます。ピンアイコンはウィンドウにプロジェクトを紐付けます。こうすることで、このウィンドウはこのプロジェクト用ですよ、ということを Project Tab Manager に教えることができます (ここはできれば自動化したい部分)。これは統計機能で必要になります。
ゴミ箱アイコンは、プロジェクトを削除するのではなく、アーカイブします。ブックマークの中に __Archives__ というフォルダがあるのですが、そこに移動されるだけですので、必要な時に元に戻すこともできます。

技術的な部分

この Extension は元々素の JavaScript で作ったものだったのですが、HTML5とか勉強会での講演のために、AngularJS ベースに変更しました。おかげでコードは格段に整理できましたし、AngularJS に興味がある方には面白いものだと思います。ソースコードはこちらで公開しています。
AngularJS とは Google のエンジニアも関わっている JavaScript のフレームワークで、他のフレームワークとはちょっと違ったアプローチが体験できます。詳しくはまた別の機会にブログを書きたいと思っていますが、興味のある方は先述の HTML5とか勉強会で僕がやったライブコーディング込みの講演のビデオが参考になると思います。15分程度ですのでぜひご覧頂ければと思います。

フィードバック求む!

リリース直前に手を入れた部分でいくつかバグが出ているようなのですが、まだ整理できていない状態です。バグを見つけた方はぜひ github に issue として登録して下さい。また、機能面でもフィードバックを頂けると嬉しいです。

Project Tab Manager で皆さんの生産性が少しでも上がればと思います。

Special Thanks to Shinsuke Okamoto for icon designs.

2012年5月5日土曜日

Sublime Text 2 のススメ

皆さんはコーディングの際、どのエディタを使っていますか?僕はもっぱらTerminalでvimゴリゴリ派だったのですが、人によってはEmacsだったり、MacのアプリだとCodaとかTextMateが人気でしょうか。最近だとWebStormとかKomodo Edit辺りも人気あると聞きます。手に馴染んだエディタがあると、なかなか変える気にならないですよね。そんな中で僕が最近本気で使い始めているのが、最近海外のフロント系エンジニアの間で人気が高まっている Sublime Text 2 というエディタです。今日はこの Sublime Text 2 ( 以下 ST2 ) を紹介してみます。

Sublime Text 2 の魅力

僕の場合、vimがすっかり手に馴染んでしまっていたので、乗り換える必要性を強くは感じていなかったのですが、やはりプロジェクト単位で作業する場合は、左側にプロジェクトのファイル一覧が表示されてたりすると便利ですよね ( NERD tree は試したのですが、なかなか使い方が覚えられず、断念してしまいました。) 。タブでファイルを複数開いておくとか、日本語も十分対応してくれてる必要があります。何よりもvimと同じ操作で使えることも、僕にとっては超重要でした。そんな条件でいくつか試していた中で、たまたま同僚がまとめて購入するからというので使い始めたのが、ST2 です。

ST2 のサイトに書いてあるウリはこんな感じ:
  • シンプルで高速なインターフェース
  • ミニマップで全体像を把握
  • テキストのマルチセレクトが可能
  • マクロを使った操作の自動化が可能
しかしながら、ぶっちゃけどれも僕には響きませんでした。そもそもウリの魅力が理解できないだけでなく、GUIで設定の変更もできないし、正直なところ何が人気の秘密なのかすぐには分からず、vimモードで使えてるからまあいいかー程度で使っていました。しかし Sublime Package Control を入れてから、段々これにハマり始めています。

Package Control を入れてできるようになったことは、例えば
  • LESSのコンパイル (?) がショートカットキーひとつでできるようになる (からやっと LESS を使い始めた)
  • git コマンドがエディタ内から打てるようになった
  • HTMLタグの補完をしてくれるようになった
  • CDNのURLを簡単に入力できるようになった
など。これだけで魅力的に感じる人もいるかもしれません。Package Control はエディタ内でレポジトリからプラグインを手軽にインストールできるプラグインで、TextMate 互換、かつ Python で書くことができます。

ST2 に魅力を感じた人は、早速インストールしてみましょう。お試しはもちろん無料で出来ますし ( 時間制限付き? )、購入する場合は $59 です。

Sublime Text 2 の便利な使い方

英語ですが、既に素敵なまとめ記事がありますので、そこから拝借 + ちょっと味付けして、僕がグッときた Tips をいくつか紹介しましょう。

コマンドパレット (Command Pallet) を使う

TextMate と同様、ST2 では Command + Shift + p でコマンドパレットを開くことができます。コマンドパレット上でタイプすると絞りこまれていくので、必要なコマンドを選択して実行できます。

Package Control をインストールする

何はともあれこれ。これを入れることで ST2 は比較にならないくらいパワーアップします。インストールするには Control + `、日本語キーボードの場合 Control + Shift + @ を押すと、画面下にコンソールが開きますので、下記のコマンドをコピペして入力します。
import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp) if not os.path.exists(ipp) else None;open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read())
実行後再起動すれば、Package Controlが使えるようになります。コマンドパレットを開き、install と入力すると、レポジトリからパッケージのリストが読みこまれ、好きなものをインストールできるようになります。

Vimモードを有効にする

メニューの Preferences から Settings - Default を選択すると Preferences.sublime-settings というファイルが開きますので、一番下の "ignored_packages": [“Vintage”] の部分を "ignored_packages": [] に変更して保存します。これでVim互換モードが利用できるようになります (あくまで互換なので、完璧ではないです。徐々に改善している模様) 。

スニペットを活用する

まだ自分でどうやって作るかなどまで掘り下げられてないですが、必要に応じて各種 Snippet のパッケージをインストールすることで、手軽に定型の文字列を入力することができるようになります。例えば li と入力してタブキーを押せば、
  • li
    と表示されて、タブを押すごとに必要な属性値を補完していくことができます。利用できるスニペットはコマンドパレットで snippet と入力することで確認できます。

    おすすめパッケージ

    僕が気に入って使ってるパッケージは以下の通り
    • LESS-build (要node.jsですが、Command + b だけで.lessのファイルを.cssにコンパイルしてくれます)
    • HTML5
    • Git
    • cdnjs
    • SFTP
    • Tag
    • SublimeCodeIntel
    • Nuttuts+Fetch (自分で作ったプロジェクトテンプレートセットをさくっと組み込んでコーディングが開始できます)
    • Terminal (今のディレクトリで Terminal を開いてくれます)
    他にもたくさんパッケージがあるので、チェックしてみてください。

    コミュニティ

    最後にこの ST2 ですが、日本ではまだまだユーザーが少ないようで、日本語の情報はあまり多くありません。Chrome の Google API Expert である @yoshikawa_t さんが Google Group を作ってくれていますので、興味のある方はぜひそちらで情報交換をしましょう。他にもいくつか有益なリンクを載せておきます。

    2012年5月2日水曜日

    HTML5Rocksが日本語に対応 (2013/12更新)

    ※この記事は 2012年5月2日に公開されたものを一部アップデートしたものです。

    HTML5Rocks というサイトをご存知ですか?HTML5Rocks は、世界でも有数の HTML5 関連情報が集まったサイトで、チュートリアルやサンプル、スライド、動画などで構成されています。このサイトは Google の Chrome Developer Relations チームが中心になって作っているのですが、英語のみだったサイトが、2012 年 5 月に i18n ( internationalization = 国際化 ) を果たし 9 ヶ国語に対応、日本語のコンテンツも追加し始めました。

    まだまだ量は多くありませんが、これまで英語を読むことに抵抗のあったディベロッパーの皆さんにも、まとまった情報源として利用して頂けるようになったのではないかと考えています。既に翻訳されている記事からいくつかご紹介しましょう。

    JavaScript で File API を使用してファイルを読み取る

    File / FileList / FileReader API を使用し、JavaScript でバイナリ ファイルの処理と読み取りを行うためのスタート ガイドです。

    ネイティブ HTML5 ドラッグ&ドロップ

    ドラッグ&ドロップも HTML5 の主要機能の 1 つです。この記事では、ネイティブ ドラッグ&ドロップ機能を追加してウェブ アプリケーションを強化する方法について説明します。

    File System API について知る

    File System API により、アプリケーションでは、ユーザーのローカル パソコン上の保護されサンドボックス化されたファイル システムに対してファイルやディレクトリの読み取り/書き込みを行うことができます。

    Contribution 受付中!

    この HTML5Rocks ですが、実はサイト自体がオープンソースで開発されているということをご存知でしたか?レポジトリは以下になります。

    https://github.com/html5rocks/www.html5rocks.com

    そして実は(ここからが本題なのですが) HTML5Rocks では記事の日本語訳を募集しています。多言語に翻訳されるドキュメントでは一般的に言われることですが、翻訳を常に最新の状態に保つのは、非常に難しいのが現実です。そこで HTML5Rocks では、オープンソースであるという利点を生かし、皆さんの翻訳を受け付けています。既に @kuuさん、@ukyoさん、@Layzieさん、@pikoteaさん、@iizukakさんといった方々から contribution 頂いていますが、新し目の記事に関してはまだまだ手が付けられていません。

    少しでもご興味を持たれた方は、まずは Style Guide をお読み頂き、もし実際に翻訳をされた場合は、Pull Request を送って頂ければと思います。また、最近デザインが変わり、翻訳者のクレジットも入れられるようになりました。Style Guide の Translations の項を参考に、ご自分の名前を入れて下さい。

    以前「他の人の翻訳とかぶってしまう可能性を考えると、翻訳していいものか迷ってしまう」という問い合わせがあったのですが、現在は github に移行しておりますので、翻訳を始めた段階で Issue を立てて頂ければ、Pull Request の受付はその順番を考慮させて頂きます。

    何か質問などありましたら、Google+ や Twitter で気軽に話しかけて下さい。みなさんからの contribution をお待ちしております!

    2012年3月14日水曜日

    HTML5開発者に便利なツール14個

    先日Google+でAddy Osmani氏が紹介していたオンラインツールが便利そうだったので紹介します。

    DocHub.io


    CSS, HTML, JavaScript, DOM, jQuery, PHP, PythonのAPIリファレンス。インクリメンタルサーチできるので、開発中は常に開いておきたい。

    Dillinger.io


    最近GitHub関連で使われるようになって流行り始めたMarkdown記法を、視覚的に確認しながら入力できるツール。DropboxやGitHubのアカウントで繋ぐと、アップロードできるようです。Mac用デスクトップアプリのMouというのも合わせて紹介されていました。

    HTML5Please


    今から使えるHTML5機能のリストとブラウザの対応状況に応じたアドバイス、そしてPollyfill(未対応ブラウザ向けに機能を補完するJSライブラリ)などを教えてくれる便利サイト。

    そしてそのAPI。ここで作ったウィジェットを自分のサイトに埋め込むことで、未対応の機能がある場合に教えてあげるUIを簡単に作れます。ブラウザの対応状況は caniuse.com を使うことで常に最新の状態だとか。

    CSS3Please


    このページに表示されているCSSをインラインで編集することで、表示内容を確認しつつコピペできる便利サイト。

    CSS3 Playground


    表示内容を確認しながらCSS3のプロパティをいじることができるプレイグラウンド。グラデーション、テキストプロパティ、トランスフォームなどに対応しています。

    Responsive


    ワンソースでスマートフォンからデスクトップまで幅広い閲覧環境に対応するレスポンシブウェブデザインというコンセプト。そんなレスポンシブウェブデザインを使って作られたサイトの表示内容をまとめて確認できるツール。

    CanIUse.com


    既にご存じの方も多いと思いますが、HTML5のブラウザごとの対応状況を調べるサイトといえばここです。

    HTML5 Readiness


    こちらもHTML5の対応状況を知るには有名なサイトですが、個別のAPIについて調べるためというよりも、HTML5の実装が進行していることをビジュアルで実感できることに主眼が置かれています。

    Browserstack


    新旧各種ブラウザのテストができる有料サービス。

    Grunt

    JavaScriptプロジェクト向けのコマンドラインビルドツール。ファイルの連結からバリデート、圧縮(難読化)、ユニットテストまでできます。まだベータ版だけどとてもパワフル。

    jsPerf


    様々なJavaScriptのテストケースを作成・共有できるサイト。既存のテストケースだけでも参考になります。Browserscope.orgを使ったブラウザごとの比較も。

    jsconsole


    JavaScriptコンソール兼デバッガのウェブアプリ。Adobe Shadowのようなツールが出てきた今どの程度役に立つのかは不明ですが、iOSやAndroidのブラウザに接続して使えるデバッガ機能が素敵です。

    Chrome DevTools AutoSave

    http://addyosmani.com/blog/autosave-changes-chrome-dev-tools/
    Chrome DevToolsの変更履歴管理機能と保存機能は便利ですが、この拡張機能を使うと自動で保存してくれるようになり、実質的にChrome DevTools上で開発を進めることが可能になります。Addy Osmaniがスクリーンキャストで分かりやすく説明してくれています。

    Codiqa


    jQuery Mobileサイトをビジュアルで構築できるウェブアプリ。

    2012年3月6日火曜日

    WebSocketのバイナリメッセージを試したら、ウェブの未来が垣間見えた

    長い記事なので、先に結論だけ書いておきます。WebSocketのバイナリメッセージ機能は、これまでのインターネットのあり方をひっくり返します。「そんなの知ってるよ」という方もいるとは思います。僕も理屈では分かってたつもりだけど、実際にアプリを作ってみて、具体的にそれを感じることができたので、ちょっと長いですがどういうことなのか説明してみます。

    WebSocketとは

    WebSocketは、HTML5関連の中でも特に注目を集めている技術の一つです。通常のHTTP通信であればクライアントからのリクエストなしにサーバーは応答しませんが、WebSocketを使うことでクライアントとサーバーの間で双方向の通信が可能となります。これを利用することで、今後様々なリアルタイム性の高いサービスを構築することが可能になるでしょう。
    そんなWebSocketですが、これまで波乱の道を歩んできました。数年前から様々なブラウザで利用可能な状態ではあったのですが、セキュリティ上の理由などで大幅な仕様の見直しが入り、昨年末ようやくひとつの完成形を見せ、複雑な仕様もひとまず落ち着いたところです。現在Google Chromeですべての仕様をサポートしていますが、まもなくFirefox11、Internet Explorer10でもそれらが利用できるようになると言われています。
    WebSocketが持つ機能の中で僕が最も注目しているのが、バイナリ送信機能です。これまではテキストしかやりとりできなかったのですが、最新の仕様ではバイナリのメッセージも送れるようになりました。従来からBase64などのエンコーディングを使うことで、テキストとして送ることはできましたが、バイナリをそのまま送れば、約30%のオーバーヘッドを節約できます。もちろんバイナリ送信機能の価値はそれだけではないのですが、それはこの記事を最後まで読めばもう少し分かると思います。

    Audio Stream Experiment

    ここで最近僕が作った、WebSocketのバイナリメッセージング機能を使ったデモをご紹介しましょう。このデモでは、ユーザーが手元に持っているオーディオファイルをリアルタイムにストリーミング配信することができます。ここからはアプリの作りの話をしますので、WebSocketでウェブがどう変わっていくのかだけに興味のある方は、「WebSocketのバイナリメッセージが意味するもの」まで読み飛ばしてしまっても構いません。

    このアプリで使われている技術に興味のある方は、何はともあれ、試してみて下さい(要Chrome)。ソースコードもgithubで公開しています。
    適当な名前を入れてconnect後、mp3, wav, m4aなどのオーディオファイルをデスクトップからドラッグドロップしたら、playボタンを押すことで、オーディオのストリーミング再生を開始できます。もしAttendeeリストに他に人がいなければ、自分で2つのウィンドウを開いて同じサイトにアクセスすることで、どのようにオーディオがストリーミングで配信されているか、試すことができます。

    アーキテクチャ

    ここで使っているオーディオ再生の仕組みは、Web Audio APIと呼ばれるものです。Googleのエンジニアが中心になって作っている仕様で、現在Chromeのみで利用できますが、いずれはSafariなど他のWebkit系ブラウザでも利用できるようになるでしょう。今も標準化の議論が進んでいますが、2012年3月現在、Webkit系以外で実装するという話は聞いていません。Firefoxにも同種のAudio Data APIと呼ばれるものがありますが、こちらは標準化の予定はないようです。Web Audio APIについて詳しい説明はここでは割愛しますが、以前HTML5とか勉強会で使った資料がありますので、よければそちらを参照して下さい。
    サーバーはnode.jsを使っています。ノンブロッキング・非同期入出力が特徴のnode.jsは、現在最も注目を集めているサーバー技術で、JavaScriptで記述することができます。2012年3月現在、どの言語の実装でもバイナリを扱えるWebSocketライブラリは数多くありませんが、node.js向けの中からwsというライブラリを選びました。
    node.jsのサーバーは、ファーストサーバー社の提供しているnode-ninja(node.jsの総本山とも言えるJoyentの開発したSmartMachinesという仮想サーバー使用)を使わせてもらいました。
    今回のデモでは、このnode.jsで構築されたサーバーから受け取ったオーディオデータをブロードキャストすることで、すべての接続しているユーザーに同じオーディオストリーミング環境を提供しています。

    各クライアントはPlayerとListenerと呼ばれる、合計2つのオーディオ再生機構を持ちます。デスクトップからドラッグドロップされたオーディオファイルは、Playerを使って再生されると同時に、node.jsのサーバーにWebSocketを使って転送されます。サーバーは受け取ったオーディオデータをすべてのクライアントに即時にブロードキャストします。各クライアントは、サーバーから受け取ったオーディオデータをListenerに流しこみ、音声を再生します。
    ちなみにデモには、オーディオストリーミングだけでなく、簡単なチャット機能も実装してあります。

    WebSocketの基本

    まずはWebSocketの基本的な使い方から。

    テキストメッセージのやりとり

    ブラウザで使えるWebSocketのAPIは非常にシンプルです。
    まずはソケットを開きます。
    var ws = new WebSocket(‘ws://localhost:3000’);
    サーバーでコネクションを受け付けると、openというイベントが返ってきます。
    ws.onopen = function() {
    ...
    }
    メッセージを送信する際は
    ws.send(message);
    とします。
    メッセージを受信する場合は、messageというイベントで受け取ります。
    ws.onmessage = function(msg) {
    ...
    }
    コネクションが閉じられた場合も同様にcloseというイベントが発生します。
    ws.onclose = function(event) {
    ...
    }
    WebSocketのAPI自体は非常にシンプルだということがことが分かりましたでしょうか?
    問題は、このシンプルさゆえの、その上に乗るプロトコルの重要性です。
    WebSocket APIにはSub Protocolという仕様が含まれていますが、これは標準化とサーバーへの実装が必要であるにも関わらず、実のところ2012年3月現在IANAにはSOAPしか登録されていませんし、実装しているサーバーもありませんので、実質的に使えるものではありません。つまり、WebSocketを使って複数種類の命令を送信したい場合は、何かしらの決まりを自分で作ってあげなければならないのです。
    例えばこのデモでは
    • セッションを開始するconnectメッセージ
    • 他のユーザーの参加状態を知らせるconnectionメッセージ
    • テキストメッセージを送受信するmessageメッセージ
    • コネクションを切らせないためのheartbeatメッセージ
    • 誰が音楽を再生し始めたのか知らせるためのstart_musicメッセージ
    これだけの種類のメッセージを扱っています。ここに、「誰が送信したのか」「どんなメッセージを送信したのか」「誰が今コネクションを張っているのか」などの付随情報を追加する必要がありますので、JSONなどの構造化されたメッセージを送ることが必須であることは言うまでもありません。

    バイナリメッセージのやりとり

    オーディオデータを送信する際はバイナリメッセージを使います。最初甘く見ていたのですが、バイナリメッセージの送受信は、テキスト以上に一筋縄では行きません。WebSocketでメッセージの構造化が必要なのはテキストだけでなく、バイナリの場合でも同様なのです。実はWebSocketは、テキストメッセージとバイナリメッセージを混ぜて送信することができません。バイナリはバイナリ、テキストはテキストとして、別々に送信する必要があるのです。つまり、バイナリメッセージに何か付随情報を追加したい場合は、何かしらの工夫をする必要があります。
    このデモを作る際、実際にバイナリメッセージに付随させたかったのは
    • オーディオを再生したユーザーのID
    • オーディオのチャンネル数
    • オーディオのバッファ長
    • 実際のオーディオバッファ×チャンネル数分
    これだけの情報を他のクライアントに送り届けなければならないのです。
    WebSocketの持つシンプルなAPIの制約から、これを実現するための方法としてパッと思いつくのは3つしかありません。
    • テキストメッセージとバイナリメッセージを一組にして送る
    • クライアントごとに別々のWebSocketコネクションを張り、テキスト+バイナリを送る
    • 付随情報をバイナリに埋め込んで送る
    まずは一つ目。これはクライアントからサーバーに送るだけなら、アリな方法です。サーバーは仕様上、誰が繋いでいるのか把握できていますので、1つ目のテキストメッセージを受け取ってから次に届くべきバイナリメッセージを待ち受けることができます。しかし、サーバーがこれを受け取った順にそのままブロードキャストする場合、このままでうまくいくでしょうか?クライアントに他に誰が繋いでいるかを把握させることは可能ですが、ランダムに届く2つ一組の情報を、順番を入れ替えずにクライアント側でマッチングさせるのは、簡単ではありません。うまく工夫すれば不可能ではないですが、それではnode.jsのノンブロッキングという最大の特徴を犠牲にせざるを得なくなってしまいます。
    2つ目のクライアントごとに別々のWebSocketコネクションを張り、テキストとバイナリを送る、という方法は、上記の問題を解決します。コネクション自体がユーザーを表す付随情報として扱える上、メッセージの組み合わせを保証できます。ただ、繋いでいるユーザーが増えれば増えるほど、消費されるリソースは指数的に増えていきます。Multiplexing Extensionが使えるようになればこれも解決できるかもしれませんが、使えない現時点では、あまり良い選択肢とは言えません。
    そして3つ目が、バイナリ自体を操作して付随情報を埋め込むという方法です。このやり方であれば、ひとつのメッセージに実際のデータと必要な情報を組み合わせて送信できるため、バイナリ操作の実装は面倒ですが、その他の部分がだいぶ楽になります。今回のデモではこの方法を採りました。

    JavaScriptで扱えるバイナリ

    JavaScriptにおけるバイナリの操作は従来から不可能ではありませんでした。しかしそのために必要なテクニックは複雑で、実行速度も犠牲にしてしまいます。しかし最近登場したバイナリを扱ういくつかの仕様によって、それは格段に楽になりました。新しくJavaScriptで利用できるようになったバイナリには大きく2種類あります。それがBlobArrayBufferです。

    Blob

    Blobはバイナリの塊ですが、中身はいわゆるファイルと思って差し支えありません。FileオブジェクトはBlobから継承されたものですので、input[type=”file”]で入力されたファイルや、ドラッグドロップされたファイルなどはこれと同様に扱うことができます。FileReader APIを使うことで、ArrayBufferやData URLに変換することもできます。
    今回のデモではドラッグドロップした時点でBlobのファイルを、FileReaderのreadAsArrayBuffer関数を使ってArrayBuffer化するのに使っています。
        updatePlayer: function(file, callback, playEndCallback) {
          var that = this;
          var reader = new FileReader();
          reader.onload = function(e) {
            ac.decodeAudioData(e.target.result, function(buffer) {
              that.audioReady = true;
              if (that.audioPlayer) that.audioPlayer.stop();
              that.visualizer.disconnect();
              that.audioPlayer = new AudioPlayer(that.audioMerger);
              if (playEndCallback) that.audioPlayer.onPlayEnd = playEndCallback;
              that.audioPlayer.load(buffer, that.websocket);
              that.visualizer.connect(that.audioMerger, ac.destination);
              callback();
            }, function() {
              throw 'failed to load audio.';
            });
          };
          reader.readAsArrayBuffer(file);
        },

    ArrayBuffer

    ArrayBufferもバイナリの塊ですが、こちらは型付き配列(TypedArray)を使って、配列として扱うことができるのが特徴です。型付き配列はArrayBufferから符号なし整数(Uint8Array)や浮動小数点数(Float32Array)などいくつかの「ビュー」を使って切り出すことができます。Web Audio APIではオーディオデータがFloat32Arrayをバッファ長分、チャンネルごとに格納されるので、これを扱うことになります。
    JavaScriptのいわゆる配列(array)とは違い、ArrayBufferには任意のバイトを必要に応じて追加・削除する機能がありません。つまり最初に必要なメモリ領域を確保して、任意の値を埋めていくというアプローチを取らなければなりません。また、異なる型のTypedArrayをArrayBufferに当てはめていくのは、ちょっとコツが必要です。

    Web Audio APIからオーディオデータを引っこ抜く

    先ほどのBlobのサンプルコードではdecodeAudioData関数を使ってWeb Audio APIのAudioBufferオブジェクトに変換しました。AudioBufferオブジェクトは、Float32Arrayのバイナリとしてオーディオデータを扱います。しかしこのFloat32Arrayをそのまま送ったのでは、ストリーミングではなくただのアップロードとダウンロードと変わりありません。このデータを細切れに送信するにはどうすればいいのでしょう?
    幸いWeb Audio APIには、JavaScriptAudioNodeという便利なものがあります。これをルーティングの途中に挿入することで、任意のバッファ長ごとにonaudioprocessイベントを発生させ、通り過ぎようとしているデータを切り出すことができます。ここではこれを使いましょう(ちなみにWeb Audio APIを作っているChris Rogersによると、JavaScriptAudioNodeは非推奨で廃止したいとのこと。同様のニーズを満たす方法は未確認です)(2012/3/14追記:Chris Rogersに再度確認したところ、JavaScriptAudioNodeを廃止する予定はないとのことでした)。
        this.js.onaudioprocess = function(event) {
          var buffers = [];
          for (var i = 0; i < that.audioBuffer.length; i++) {
            buffers.push(that.audioBuffer[i].shift() || new Float32Array(BUFFER_LENGTH));
          }
          if (that.type == 'Player') {
            if (that.audioBuffer[0].length == 0) {
              that.stop();
            } else {
              var msg = AudioMessage.createMessage({
                user_id:UserManager.getUserId(),
                buffer_length:BUFFER_LENGTH,
                buffer_array:buffers
              });
              that.socket.send(msg.buffer);
            } 
          }
          for (var i = 0; i < buffers.length; i++) {
            event.outputBuffer.getChannelData(i).set(buffers[i]);
          }
        };
    バッファ長(BUFFER_LENGTH)は2048を指定しています。既存のオーディオファイルを再生したいだけの場合、AudioBufferSourceNodeを使うのが通例ですが、ここではJavaScriptAudioNodeにバッファを直接挿し込むアプローチを採っています。その際、ついでにWebSocketに送信する準備をしています。

    バイナリを操作する

    取り出した2048のFloat32Arrayは2チャンネル分ですので、これを他の情報と組み合わせてバイナリに固めます。
          createMessage: function(msg_obj) {
            var bl = msg_obj.buffer_length;
            var ch_num = msg_obj.buffer_array.length;
            var ab = new ArrayBuffer(4 + 1 + 4 + (bl * ch_num * 4));
            var view = new DataView(ab);
            var offset = 0;
            view.setUint32(offset, msg_obj.user_id);
            offset += 4;
            view.setUint8(offset, ch_num);
            offset += 1;
            view.setUint32(offset, bl);
            offset += 4;
            for (var i = 0; i < ch_num; i++) {
              for (var j = 0; j < bl; j++) {
                view.setFloat32(offset, msg_obj.buffer_array[i][j]);
                offset += 4;
              }
            }
            return new Uint8Array(view.buffer);
          },
    バイナリに複数の型を含めたい場合、DataViewを使います。これにより、JSONオブジェクトを元にして、何バイト目からこの型でこの値を埋める、といった指定が可能になります。これで複数のデータを埋め込んだバイナリができあがりましたので、そのままWebSocketに乗せて送ります。node.jsは単純に受け取ったものをブロードキャストするだけですので、受け取ったクライアントはこれをパースして音を鳴らすだけ、ということになります。詳細はソースコードを読んで下さい。

    WebSocketのバイナリメッセージが意味するもの

    ここまで読んできて、「うぉーめんどい!」と思った方、その通りだと思います。でもこういうものは、きっと近いうちにライブラリが解決してくれるでしょうし、プロトコルも整備されれば、使う側は何も考えなくても良くなるはずです。それこそ(まだTypedArrayに対応してませんが)JSON Schemaとか、Protocol Bufferなんかは、僕がまっさきに思いついた、これらの諸問題を解決してくれるものです。
    例えばnode.jsでは、Socket.IOというライブラリが既に存在しています。まだバイナリメッセージに対応していませんが、v1.0では対応すると表明していますし、時間の問題でしょう。そういったライブラリを使えば、ディベロッパーはどんなプロトコルで通信しているのかすら意識することなく、データのやり取りが可能になります。そして、今回僕が垣間見た未来はここに存在しています。
    今後インターネットでスピードを追求していく上で、WebSocketはなくてはならない存在になるでしょう。リアルタイムなサービスを作る必要がなくても使われるテクノロジーになります。理由は大きく2つ。WebSocketの持つオーバーヘッドの削減と、データの圧縮という特徴です。
    例えば頻繁に通信を要するサービスでWebSocketを使ってるとしたら、別途Ajaxを利用する意義は存在するでしょうか?以前Google API Expertの小松さんがWebSocketとAjaxの通信速度を比較する興味深い記事を公開されています。これを見るだけでも、WebSocketのコネクションがもうあるなら、わざわざAjaxを別途やるメリットないよね、と思うはずです。突き詰めていけば、サーバーが一度ウェブページをレンダリングしたら、あとはWebSocketでコネクションを張ってすべての通信を制御し、Ajaxは全く行わない、というアーキテクチャは割と早い段階で登場してくると思います。
    それからデータの圧縮。バイナリ化することで、テキストを送る場合と比べて送るデータ量を小さくできることは、疑う余地もありません。それに加えてこれから登場するWebSocket Deflate Extensionが加われば、さらに圧縮され、データは小さくなるでしょう。
    加えてこの記事でも検証した通り、バイナリーを送るためにはバイナリーに付随情報を追加する必要があります。だったら、別途テキストデータを送る意義ってなんでしょう?最初から全部バイナリーで送ることでスピードをアピールするライブラリが登場するのは時間の問題です。僕はインターネット上でWebSocketを使って流れるデータが、将来的に全部バイナリーになる可能性すらあるのではないかと思っています。

    まとめ

    少し大げさに言うと、これはウェブにおけるパラダイムシフトになり得ます。ウェブで使われるプロトコルがHTTPからWebSocketに乗る何かしらのプロトコルに置き換わる可能性は、全くないとは言い切れないのではないでしょうか。

    2011年4月10日日曜日

    HTML5でメトロノームを作ってみた

    HTML5を使ってメトロノームを作ってみたのでご紹介します。


    http://demo.agektmr.com/metronome/


    動作確認はChrome12、Firefox4、Safari5、Opera10で行いました。iOS4のSafari、Android2.3の標準ブラウザでも音は出ないけど動くことは確認しています。

    使ったHTML5関連テクノロジーは

    • Application Cache

    • CSS3 transform, transition, box-shadow

    • Web Audio API

    • Audioエレメント

    • Drag

    • WebFonts


    といったところでしょうか。画像は一切使っていません。Chrome12の場合、Omnibox(URLの欄)にabout:flagsと入力してWeb Audio APIを有効にすることで、割と安定した動作をするはずです。無効な場合はAudioエレメントを使うようフォールバックしますが、Chromeだと不安定なようです。

    トリックは単純で、transformとtransitionを組み合わせてメトロノームの棒を左右に揺らすアニメーションを一定間隔で繰り返しているだけです。反転するタイミングでWeb Audio APIまたはAudioエレメントを使ってクリック音を鳴らしています。

    ボタンをクリックまたはスペースキーでメトロノームをスタート・ストップできます。重石を上下に動かせば、スピードを調節できます。

    Enjoy!

    2010年8月11日水曜日

    iPhoneでモバイルウェブアプリ

    お久しぶりです。すっかりウェブアプリな毎日を過ごしているえーじです。今回はモバイルウェブアプリでこれは!というネタを拾ったのでご紹介。

    OpenAppMkt


    http://openappmkt.com/

    iPhone向けのサービスです。登録なしで利用できるので、iPhoneをお持ちの方は、取り敢えず試してみてください。



    iPhoneでアクセスするといきなり「ホーム画面にブックマーク」しろと出て来るので、やってみます。すると当然ながらホーム画面にアイコンが出現します。問題はここから。



    アイコンをクリックして起動してみると、ブラウザではなく、ネイティブアプリらしきものが起動します(!)。これはアプリをインストールするためのアプリなので、試しにFacebookをインストールして、起動してみます。こちらのインストール手順も同じくホーム画面にブックマーク。



    Facebookアプリを起動するとこれまた不思議とネイティブアプリらしきものが起動。しかし内容はどう見てもスマートフォン版Facebook・・・。

    実はこれ、ウェブアプリなんです。ナビゲーションやメニューを表示していないだけで、Safariが起動している様子。iPhoneでこんな事できたんですね。

    モバイルウェブアプリケーション


    実際に使ってみると、これだけでユーザーエクスペリエンス(主に気分)が大分違うことに気付くと思います。なぜ今までほとんどのiPhoneウェブアプリがこの方法をとってこなかったのか不思議なくらい。Safariで見るとただのウェブサイトでも、こうしてナビゲーションなしで画面が見えると、普通のアプリとして扱うことにためらいを感じません。

    レスポンスもしっかりしているので、動作の軽いアプリであれば、ウェブアプリであることを意識しないで利用できるし、何よりもウェブを作る感覚でiPhone用アプリが作れるのは、ウェブディベロッパーにとって大変ありがたいお話です。これまでの資産を活かすこともできるという意味でも、単純なことながら見逃せないテクニックと言えます。

    ここまで来ると、後の問題はディストリビューション。どうやって作ったウェブアプリを配布するか。そこを担うのがOpenAppMktの役割、という訳ですね。ひとことで言えば「iTunes Storeのウェブアプリ版」。あ、これも忘れちゃイヤですけどね。詳細は見ていませんが、課金もできるようです。

    HTML5と組み合わせて、究極のウェブアプリを


    上記のように、Safariのメニューやステータスバーを消すには、HTMLにapple-mobile-web-app-capableというメタタグを入れるだけで、非常に簡単に実現できるようです。詳細はこちらを御覧ください。

    実はこの機能、OS2.1からできていたらしく、僕も小耳に挟んだ記憶はあったのですが、実際に動いているウェブアプリを目にしたのは初めてでした。ある意味この機能が日の目を見るのに、iPhoneはHTML5の登場を待っていた、という部分もあるかもしれません。

    例えば、これとApplicationCacheやWeb SQL Databaseを組み合わせたところを想像してみてください。基本的なリソースはAppCacheですべてローカルにキャッシュ。動的なデータは、オフライン時はWeb SQL Databaseに保存、オンラインになったことを検知してクラウドに同期、といった形にすれば、完全にオフラインで動作可能なウェブアプリができあがります。

    色々夢が広がってしまって困りますね・・・