HTML5 を活用した Vyclone の対話型エクスペリエンスの構築
HTML5 を活用した Vyclone の対話型エクスペリエンスの構築
Web がコンシューマーにとってより魅力的で、より生産性の高いものになるとき、そこには、HTML5 の可能性を最大限に広げてくれる開発者の存在があります。このブログ記事では Plain Concepts (英語) の Anton Molleda 氏をゲスト執筆者に迎えて、Vcylone (英語) 開発で体験したことと、そこで得た教訓についてお話していきたいと思います。Vcylone は、HTML5、さらに Internet Explorer 10 を始めとする次世代ブラウザーの数多くの新機能を基盤とするソーシャル ビデオ編集エクスペリエンスです。Vyclone では、ポインター イベント、マルチタッチ ジェスチャ、ハードウェア アクセラレーションを活用する Canvas や CSS3 などの機能を基盤とすることで、よりアプリ ライクな操作感を持つ Web サイトを実現しています。
— Internet Explorer 担当グループ プログラム管理者 Rob Mauceri
皆さんこんにちは。
Plain Concepts (英語) の Anton Molleda です。この数か月間、Internet Explorer のチームには、新たなソーシャル ビデオ サイト Vyclone (英語) の立ち上げ成功に取り組む当社のチームに協力していただいてきました。Web 開発者として、Web の可能性を拡大するこのような機会に立ち会えていることに感動を覚えます。私は幸運に恵まれてこのプロジェクトに参加することができましたが、本日は、この共同開発で発見した重要な教訓のいくつかを皆さんにもお伝えしたいと思います。内容は Vyclone の Web ビデオ エディター (英語) に関するもので、使われたのは HTML5 と JavaScript だけです。
Vyclone は、同じシーンに対する複数の視点を共同で簡単に記録し、同期し、編集できる、ソーシャル ビデオ プラットフォームです。
Vyclone の開発が始まったときに想定されていたのはモバイル デバイスだけでしたが、電話を使って記録できるというエクスペリエンスはすばらしいものの、画面サイズやデバイスのパワーが理由で映像の編集に制約があることにすぐに気付きました。この新たなツールの開発を進めるためのオプションとして HTML5 が選ばれたのは、この数年間の最先端のブラウザーによる進化のおかげです。
Vyclone の Web エディターは以下の 3 つの部分で構成されています。
ビデオ プレビュー: ユーザーが作成中のカットの低画質バージョンが再生されます (左側)。
ビデオグリッド: 利用可能なすべてのソースが、特定の視点と時間で表示されます (右側)。
タイムライン: 本編のビデオの再生中に利用されているソースが、リニア ビューとして表示されます (再生コントロールの上部分)。特定の時間にわたって再生されているソースは、カットと呼ばれます。
ユーザーがビデオを再生して新しいカットをタイムラインに追加すると、ビデオ プレビューが切り替わって新しいソースが反映され、ビデオグリッドのソース ファイルが四隅の三角形によって強調表示され、現在選択中のビデオをユーザーに知らせてくれます。
そして、この動作を構築するにあたっては、大量のビデオ操作、パフォーマンスの低下、ユーザー エクスペリエンスなど、とても重要ないくつかの課題が与えられることになりました。ここからは、この動作を Web で実現するために何を行ったかについてお話ししたいと思います。まず、video、canvas、requestAnimationFrame (RAF) (英語) を使います。これによって、バックグラウンドにあるビデオが再生され、それぞれの RAF の内部でアクティブなソースを Canvas 内 (ビデオ プレビュー内) に描画するか、ビデオグリッドの新しいサイズと位置を計算することができます。
ここまでは問題ありません。しかしユーザーがこれに対して対話式操作を行った場合は状況が変わります。たとえばタイムラインの前後移動や、ビデオ ソース (カット) の追加/削除などについて考える必要があります。最初にプロトタイプを作成した際は、イベント呼び出し直後での標準的なアプローチで対応できると考えました。これまでのやり方に従ったということです。
しかし、1 秒間に数十回または数百回もイベント呼び出しが発生される場合、さらにはこれらのハンドラーで UI の更新が必要になった場合は、どうすればよいでしょうか。差分変更が 1 ピクセルにも満たない場合さえ珍しくないのに、レイアウトの更新を本当に 1 秒間に 130 回も強制しなければならないのでしょうか。だとすればパフォーマンス的にはかなり厳しくなります。
i7 および 8 GB の RAM を搭載するマシンを使っていれば処理能力的には対応可能ですが、もっと古いマシンを使っているユーザーや、ARM デバイスの場合は問題は解消されません。これらのユーザーは同じエクスペリエンス得ることができず、この Web サイトの応答時間はどんどん鈍くなります。
私たちが最初に採用しようと考えたアプローチは、アクションをキューとして RAF に追加することでしたが、これには問題がありました。たとえば、同じ "tick" のための同じ関数が RAF に追加されてしまうことになり、これではパフォーマンスはますます悪くなります。この問題を解決するため、最初のアプローチとして、このアクションが既にキューに追加されているかどうかを通知する変数を設定しました。たとえば以下のようになります。
var queued = false; function myAction(){ //your awesome code here queued = false; } function onEvent(evt){ if(!queued){ queued = true; requestAnimationFrame(myAction); } }
このコードは悪くはありませんがまだいくつか問題があります。イベントの位置 (マウスまたはポインター) と差分に関連した操作を実行しようとする場合、このアプローチで解決されない問題が発生します。私たちがタイムラインについて採用した解決策は、イベントの値を蓄積して myAction で処理するという以下のような方法です。
var deltaX = 0, queued = false; function myAction(){ //your awesome code here uses deltaX deltaX = 0; // we reset the deltaX so it can be incremented // next time onEvent is executed queued = false; } function onEvent(evt){ if(!queued){ queued = true; deltaX = evt.translationX; // in the case of a pointer, if you are // using a mouse you will have to do some // magic with pageX or similar :) requestAnimationFrame(myAction); }else{ deltaX += evt.translationX; } }
このアプローチならうまくいきそうと気を良くした私たちは、さまざまな機能を追加していきました。その後新しい問題が浮上します。
これらのイベントが各 requestAnimationFrame で適切に処理されれば、処理能力を犠牲にすることなく高い応答性を達成することができます。しかし requestAnimationFrame は関数を順番に実行するため、クリーンな状態にする前に描画したり、必要のないタイムラインの移動を行った場合などは、キューが簡単にいっぱいになってしまいます。必要な順序で必ず実行されるようにするためには、複雑なコードを大量に記述しなければなりませんでした。
私たちは、コードの使いやすさが足りないことと、別のアクションの実行を待機するサイクルが抜けていることに気付きました。このため、入力の処理方法に関する再度の変更を決定しました。このとき初めて、私たちはこれをゲーム ループとして捉えたのです。シンプルなゲーム アーキテクチャをご存知ない方のために説明すると、ゲーム ループの基本は連続ループで、ユーザーの対話式操作にかかわらず実行され、異なるイベントやアクションが発生すると分岐します。Wikipedia のゲーム プログラミングに関する記事 (英語) を見ると、擬似コードによる簡単なゲーム ループは以下のようになっています。
while( user doesn't exit ) check for user input run AI move enemies resolve collisions draw graphics play sounds end while
求めていたものはまさにこれです。私たちは RAF を利用して、連続実行される tick 関数を作成しました。また、この tick 関数内で、直前のユーザー入力その他の要因に応じて実行する処理を判定できるようにしました。
以下は、ビデオグリッド用の tick を簡単に表現したものです。
function tick(){ //we clean if we've changed the size of the quadrant if(needsClean){ cleanCanvas(); } // if we have to change the quadrant's frame because we are // the active one (or the opposite) if(newFrame){ drawFrame(); // we draw just the frame in a separate canvas so it // doesn't need to be calculated all the time, and it // is still faster than copying from an image } //we draw the new frame if we are playing or seeking if(dirty){ draw(); drawFrameInQuadrant(); } requestAnimationFrame(tick); }
needsClean、newFrame、dirty の値はイベント ハンドラー (ユーザーによるシークやビデオ再生など) で更新されます。
ユーザーの対話式操作をゲーム ループのしくみに入れるという考え方に変えることによって、パフォーマンスを向上しながら、エディターのコードを簡素化することができました。
高度な対話処理が要求され、多くのユーザー入力が発生する機能を開発する場合は、ゲーム ループを使って処理を簡素化できる余地について考えてみることを、今回の教訓として強くお勧めします。私たちにはこれが本当に効果的でした。Vyclone の新しく美しい Web エディター (自分で言いますが) をまだご覧になったことがない方は、ぜひ触れてみてください! Vyclone.com にアクセスして、お好きなビデオの [Remix] をクリックすると、Web エディターが表示されます。マウスでもタッチでも同様に軽快に操作できます。最もお勧めしたいのは Surface Pro での動作です。
ぜひお楽しみいただけますとさいわいです。ご質問などがありましたら、以下のコメント欄で何でもお知らせください。
— Plain Concepts、Anton Molleda
Comments
- Anonymous
March 24, 2013
まだまだHTML5は発展途上中な感じですね。 ほとんどのサイトは未だ旧規格を利用していますけど・・・。 どうなんでしょうねぇ。