中級
これまで、ユーザーのマウスやキーボード操作を記録・再生するコンテンツはいくつもリリースされてきました。例えば、最近のサイトだと「MORISAWA FONTPARK2.0」や「UT LOOP」、数年前だと「flash effects」の作品集の中にもあります。
しかし、「ユーザーアクションの記録・再生」機能を持つコンテンツのニーズは高い反面、そういった機能やライブラリはあまり公開されていなかったため、各デベロッパーがコンテンツに合わせて独自の仕様を決め、実装する必要がありました。
今回、あるコンテンツの作成の際にこの記録・再生する機能が必要になり、実装することになりました。その時、こういう「ちょっとしたニーズを満たす機能をライブラリ化すれば、みんな便利かも」と思い、他の人でも簡単に機能を実装できるように汎用性をもたせたライブラリ「Recorder Lib」を作成しました。そして、Spark Projectの理念である「皆でソースコードやノウハウを共有して、幸せになろうよ!」という考えに共感し、Spark Projectにコミットさせていただきました。Recorder Libは、以下のリポジトリから入手できます。
MEMO:様々なライブラリが公開されていますが、こういう「単機能だけど、アイデア次第でいろいろ使える」系のものほど多くの方に利用されていると思われます(TeraClock、Threadなど)。
Recorder Libの機能は非常にシンプルで、以下の3つの作業を行うことができます。
記録するプロパティを指定すると、毎フレームごとに記録し続けます。また、記録する時間やフレーム数を指定することができます。 現バージョンで記録できるデータは、「オブジェクトのプロパティ」「イベント」「キーコード」「BitmapData」です。特にイベントデータは、dispatchEventのタイミングでEventクラスのインスタンスを保持するので、再生用のメソッドを用意しなくても、再生時にそのまま同じイベントを起こすことができます。
MEMO:BitmapDataの記録は生のデータを保持し続けるため、非常にメモリを消費します。
記録したデータを、別のオブジェクトのプロパティに割り当てることができます。例えば、マウスの動作を記録して、オブジェクトのx・y座標に割り当てることで、マウスの軌跡を辿ることができます。また、x・y座標の割り当てを逆にしたり、rotationに割り当てるなど、アイデアに次第で様々な組み合わせが行えます。
フレームごとに、バインドしたプロパティにデータを送り続けます。また、時間やフレーム数を指定して再生したり、記録しながら一定時間後に再生するというタイムシフト再生も可能です。
このように「ユーザーアクションの記録・再生」機能を3段階に分けることで、記録するデータの種類を増やしたり、再生時に対応付けるオブジェクトを変えたりといった作業が行いやすくなっています。
Recorder Libの構成は、プログラミングの「MVC(モデル・ビュー・コントロール)」の考え方をもとにそれぞれを切り分けています。そのため、みなさんが自分の開発するコンテンツに合わせて独自に拡張しやすくなっています。
Recorder Libを使えば、どのようなコンテンツを作成できるのか。それをお見せするために、マウス操作行動判定コンテンツ「マウスハンテイ」を作ってみました。これは、ユーザーのマウス操作(移動やクリック)を10秒間記録し、その挙動をもとにコメントを出すといコンテンツです。
「マウスハンテイ」
「スタート」を押して、マウスを自由に10秒間操作してください。結果発表画面では、マウス操作の再現、移動距離と速さなどの分析結果、簡単なコメントが表示されます
このようなコンテンツも簡単に作成することができます。それでは、Recorder Libの実装方法を詳しく解説していきましょう。
記録の準備として、以下の手順を行います。
//インスタンスの作成
var recorder:Recorder = new Recorder(stage);
recorder.addEventListener(RecordingEvent.STOP_REC, recStopHandler)
//記録するプロパティの追加
recorder.addRecProperty("mouseX", this, "mouseX");
recorder.properties["mouseX"].addEventListener(PropertyEvent.CHANGE,
changeHandler);
//バインドするプロパティの設定
recorder.bind("mouseX", mc, "x");
値は生データで記録・再生されるので、閾値が異なるデータの場合は、一度別のプロパティに割り当て、変換を行う必要があります。(例:x座標をアルファに変える際など)
記録はrec()メソッドを実行することで始まります。また、recInTime(ミリ秒)メソッドを使えば指定時間で、recInFrame(フレーム数)メソッドを使えば指定フレーム数で記録をコントロールできます。
//記録の開始
private function mouseDownHandler(e:MouseEvent) :void {
//記録の開始
recorder.rec();
//時間(ミリ秒)を指定して記録の開始
//recorderTest.recInTime(2000);
//フレーム数を指定して記録の開始
//recorderTest.recInFrame(120);
}
記録・再生の停止はstop()メソッド、再生はplay()メソッドで行うことができます。
//記録の開始
private function mouseUpHandler(e:MouseEvent):void {
//記録の停止
recorder.stop();
//再生の開始
recorder.play();
}
また、playTimeShift(秒)メソッドで遅延する時間を指定してタイムシフト再生を行うことができます。記録時ではミリ秒単位で指定しましたが、このメソッドでは秒単位で指定します。単位が異なるのは、たいていの場合、ミリ秒がフレーム数に追いつかないからです。
//記録の開始
private function mouseDownHandler(e:MouseEvent) :void {
//記録の開始
recorder.rec();
//遅延する時間(秒)を指定してタイムシフト再生を開始
recorder.playTimeShift(1);
}
Recorderの基本動作サンプル。右側の赤い録画ボタンを押して、赤い矢印をドラッグしてください。右から2つのストップボタンを押して記録を停止し、再生ボタンを押して下さい
rec()メソッド実行後、イベント発生時にrecEvent(イベント)メソッドを実行することで、タイミングとイベントが記録されます。再生時には、同じイベントインスタンスが実行されます。
//イベント記録の準備
private function setRecorder() {
//レコーダーの準備
recorder = new Recorder(stage);
//記録の開始
recorder.rec();
//マウスイベントの設定
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
}
//マウスを押した際の動作
private function mouseDownHandler(e:MouseEvent) :void {
//イベントの記録
recorder.recEvent(e);
//マウスダウン時の処理
e.target.x += 10;
}
キーコードはプロパティではなく関数にのみバインドします。そのため、再生時には「Aキーを押したタイミングでpressA()を実行する」といった対応付けを行うことができます。
//インスタンスの作成
var recorder:KeyRecorder = new KeyRecorder(stage);
recorder.addEventListener(RecordingEvent.STOP_REC, recStopHandler)
//バインドするキーコードと、再生時に実行する関数を設定
recorder.bindKeyCode(65, keyTest);
private function keyTest():void{
trace("Press A")
}
キーコード記録のサンプル。右側の赤い録画ボタンを押して、矢印キーを使ってオブジェクトを移動させます。右から2つのストップボタンを押して記録を停止し、再生ボタンを押して下さい。
BitmapDataを記録するには、以下の準備が必要です。
//記録するDisplayObjectと、バインドするBitmapObject
var recBitmap:Sprite = new Sprite();
var playBitmap:Bitmap = new Bitmap();
//インスタンスの作成
var recorder:BitmapRecorder = new BitmapRecorder(recTargetDisplayObject);
recorder.addEventListener(RecordingEvent.STOP_REC, recStopHandler)
playBitmap = new Bitmap();
//バインドするBitmapObjectを設定
recorder.bindBitmap("play1", playBitmap);
new BitmapRecorder(記録するDisplayObject)により、記録するBitmapDataをキャプチャするDisplayObject を指定します。続いて、bindBitmap(id,再生するBitmapオブジェクト)メソッドにより、再生時に描画するDisplayObjectを指定します。後は、同じくrec()、stop()、play()を実行することで、画像を記録することができます。
BitmapData記録のサンプル。右側の赤い録画ボタンを押し、しばらくしてストップボタンを押して記録を停止します。再生ボタンを押すと、左側のオブジェクトの動きを再生します
また、Webカメラの映像を表示するvideoクラスのインスタンスをSpriteの子として登録し、Spriteを記録することで、カメラ映像の記録、再生ができます(ただし、重いです)。
private function setRecorder() {
//"recContainer"という名前のDisplayObjectを記録
recorder = new BitmapRecorder(recContainer);
//"playBitmap"という名前のDisplayObjectにバインド("video"は識別用の任意のID)
recorder.bindBitmap("video", playBitmap);
}
private function setBitmap():void {
//Webカメラの準備
camera = Camera.getCamera();
if (camera != null) {
video = new Video(320,240);
video.attachCamera(camera);
//記録するDisplayObjectの子に追加
recContainer.addChild(video);
}
//再生用BitmapDataの準備
playBitmap = new Bitmap();
//再生するDisplayObjectの子に追加
playContainer.addChild(playBitmap);
}
Webカメラ映像記録のサンプル。右側の赤い録画ボタンを押し、しばらくしてストップボタンを押して記録を停止します。再生ボタンを押すと、Webカメラの映像が再生します
Recorder Libの今後の予定ですが、以下の機能を追加したいと思っています。
差し当たって、テキストデータの圧縮機能を追加すれば、サーバへの記録時の容量削減などが行えて、利用の幅が広がると思いますので、今後進めていきたいと思います。