to do something syncronizing sound

Flashで曲の再生に正確に同期してごにょごにょしたい時のメモ。Soundboothを使ってマーカーを埋め込むと曲の再生時間に紐づいてイベントが発行されるのでタイミングのズレがなく同期できる。

まずSoundboothにてキューイベントを発行したい場所にマーカーを配置。

マーカーパネルで名前、パラメータを編集し、マーカーのタイプをイベントにする(←これ重要)。

マーカーの配置が完了したら別名で保存から「FLV | F4V」形式で書き出す。書き出し設定のビデオを書き出しのチェックを外して、マルチプレクサタブのFLVにチェックを入れる。オーディオタブでオーディオの設定をして「OK」で書き出し。

通常通りflvを読み込み、onCuePointにハンドラを張る。onCuePointは再生箇所がSoundboothで設定したマーカーの地点まで来た時に自動で呼び出される。引数に詳細情報を含んだObjectが渡される。

var nc:NetConnection = new NetConnection();
nc.connect(null);
ns = new NetStream(nc);
var video:Video = new Video();
video.attachNetStream(ns);

var myClient:Object = new Object();
myClient.onMetaData = function(info:Object):void
{
}

//ここでonCuePointを受け取る
myClient.onCuePoint = function(info:Object):void
{
       trace("name:"+info.name);
       trace("time:"+info.time);
       trace("parameter:"+info.parameters.tick);
};
ns.client = myClient;
ns.play("sound.flv");

詳細情報を含んだObjectのパラメータはnameにマーカーの名前が、timeにマーカーの配置された時間(ミリ秒)が、parametersにSoundboothのマーカーパネルで設定したパラメータが名前と値が対で含まれている。

このやり方だと外部flvを読み込まなくてはいけないので、サウンドデータをファイルに埋め込んでしまいたい場合はSoundboothのマーカー情報をxmlで書き出し、Flash側でイベントの発行すればいい。

xmlの書き出しはSoundboothでマーカーを設定した後、「ファイル→書き出し」からできる。このxmlにはマーカーごとに時間と名前パラメータなどonCuePointで受け取れる情報はすべて入っている。

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<FLVCoreCuePoints Version="1">
	<CuePoint><Time>1566</Time><Name>0</Name></CuePoint>
	<CuePoint><Time>1813</Time><Name>1</Name></CuePoint>
	<CuePoint><Time>2255</Time><Name>2</Name></CuePoint>
</FLVCoreCuePoints>

あとはFlash側でこのxmlを読み込み、音楽の再生時間を監視し続けながら、マーカーの時間が来たらしかるべき処理をすればいい。

//ライブラリから音楽データを読み込む
music = new Music() as Sound;
//30fpsで現在の再生時間を見にいく
timer = new Timer(1000 / 30);

//マーカー情報を持ったxmlを読み込みパースする
loader = new URLLoader();
loader.addEventListener(Event.COMPLETE, function(e:Event)
{
       var xml:XML = new XML(loader.data);
       var length:int = xml.CuePoint.length();
       var setting:Vector.<Vector.<int>> = new Vector.<Vector.<int>>(length, true);

       for (var i:int = 0; i < length; i++)
       {
               setting[i] = new Vector.<int>(3, true);
               setting[i][0] = int(xml.CuePoint[i].Name);
               setting[i][1] = int(xml.CuePoint[i].Time);
               //通過済みチェックフラグ
               setting[i][2] = 0;
       }

       //音楽と監視開始
       sc = music.play();
       timer.addEventListener(TimerEvent.TIMER, function(e:TimerEvent):void
       {
               for (var i:int = 0; i < length; i++)
               {
                       //再生中の時間がキューポイントの時間を超えて、かつ通過済みでなければ
                       if ( setting[i][1] <= sc.position && !setting[i][2] )
                       {
                               trace("cue name:" + setting[i][0]);
                               trace("cue time:" + setting[i][1])
                               //通過済み
                               setting[i][2] = 0;
                               return;
                       }

                       if ( sc.position > music.length )
                       {
                               trace("再生終了")
                       }
               }
       });
       timer.start();
);
loader.load(new URLRequest("cuePoints.xml"));