flash - ActionScript (後編)

前編ではyoutube APIを用いて動画キーワード検索を行い、検索結果部分にサムネイルで動画検索結果が表示できるところまで作りました。今回は、仕上げととして、サムネイルをダブルクリックすると右側の再生部分で動画再生させるところまでを作ります。完成図は以下の形です。

flexで動画を流すのは flv 形式のファイルをVideoDisplayというコンポーネントで再生する形を採用します. youtubeの各動画のflv形式をファイルの取得はAPIでは応答されません。そのため、一度youtubeの再生ページのHTMLを別途取得して
HTMLを解析しflv動画ファイルのダウンロードURLを取得することにします。youtubeの動画再生ページのURLは

http://www.youtube.com/watch?v=n0fhz1SGnSE

であり、下記の画面です。この画面のplayerもflvをyoutubeサーバからflvをダウンロードしてplayerで再生しているflashアプリケーションです。よってこのページにflvの在処の手がかりがあります。

この部分はGoogle社が公式にはアナウンスしていないので詳細の説明は省きますが、実際には

http://www.youtube.com/get_video?video_id=n0fhz1SGnSE&t=OEgsToPDskKEImaKT-gE2L7iHGQnGnGt

というURLでflvが取得しできます。このURLのパラメータvideo_idおとびtはHTMLのソース上に記載されています。下記はHTMLの該当の部分

var swfArgs = {"usef": 0, "BASE_YT_URL": "http://www.youtube.com/", "vq": null, "sourceid": "yw", "video_id": "n0fhz1SGnSE", "l": 15, "sk": "rkW--sJoRE6d6nxtGT6xGz3jFddjU0CAC", "fmt_map": "6/720000/7/0/0,34/0/9/0/115", "t": "OEgsToPDskKEImaKT-gE2L7iHGQnGnGt", "hl": "ja", "plid": "AARdWT8FsjBhDANPAAACgAAoAAA", "sdetail": "rv%3An0fhz1SGnSE"};

これを取得しflvダウンロードURLをXMLで応答するproxyを作成し、こちらのplayerから取得する方式をとります。例えば単純なものだと以下のようなCGIになります。

bash-3.2$ cat prog10/youtube_proxy.php 
<?php
    $curl = curl_init();
    curl_setopt ($curl, CURLOPT_URL, "http://www.youtube.com/watch?v=${_GET['video_id']}");
    curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
    $html = curl_exec ($curl);

    preg_match('|"video_id": "(.*?)"|', $html, $v);
    preg_match('|"t": "(.*?)"|', $html, $t);

    print "<url>";
    print "http://jp.youtube.com/get_video?video_id=${v[1]}&t=${t[1]}";
    print "</url>";
?>

これにvideo_id(APIから取得可能)を引数にアクセスすると

<url>
http://jp.youtube.com/get_video?video_id=n0fhz1SGnSE&t=OEgsToPDskKEImaKT-gE2L7iHGQnGnGt
</url>

とflvの所在のURL結果が応答されます.
さて、FlexBuilderに戻ります。検索結果のサムネイルをダブルクリックした場合に動画が再生されるようにしたいので、 まず、サムネイルのダブルクリックを検知し、関数が呼ばれる仕組みを実装しましょう.
TileListのプロパティに

doubleClickEnabled="true" doubleClick="playmovie()"

を加えます。 doubleClickEnabledを trueにするのを忘れないようにしてください。それで playmovie関数を定義すればそれが呼ばれます。今回はもう少し突っ込んで進みましょう。最終的に やりたいことはタブルクリックしたら

  1. ダブルクリックしたサムネイルの video_idをしらべて
  2. そのvideo_idを先ほどのflvが取得できるURLを獲得できる自作のapiに投げて
  3. その応答のURLを動画再生のsourceにセットする

です。なので、1.のダブルクリックしたサムネイルのvideo_idまで取得できるように実装しましょう.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
        <![CDATA[
            ....省略....
            public function playmovie():void {
                var tmp:Array = movielist.getItemAt(tilelist.selectedIndex).id.split('/');
                var video_id:String = tmp[tmp.length-1];
                Alert.show(video_id);
            }
        ]]>
    </mx:Script>
            ....省略....
    <mx:HDividedBox width="100%" height="100%">
        <mx:TileList width="100%" height="100%" dataProvider="{movielist}" 
              itemRenderer="movieitem"
              doubleClickEnabled="true" doubleClick="playmovie()" 
              id="tilelist" />
    </mx:HDividedBox>
            ....省略....    
</mx:Application>

playmovieの関数で, tilelist.selectedIndex が何番目の listを今選択しているかが取得できます. (TileListのidをtilelistにしました.) movielist.getItemAt(tilelist.selectedIndex).id でその選択している entryの idタグ部分が 取得できます。このidは

<id>http://gdata.youtube.com/feeds/api/videos/_EEx3VhloNg</id> 

となっている部分なので、実際のvideo_idは最後の/の後ろの部分なので、 / でsplitして、最後の部分を取得しています方法をとりました。これで実行してサムネイルをダブルクリックしてみてください. video_idが表示されるはずです.
次にvideo_idが取得できたら、これを自作のAPIに投げて flvのURLが取得できるところまで実装しましょう。APIへのリクエスト&受信はまったくyoutube API の部分と同じ感じです。一気に実装します.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
        <![CDATA[
            import mx.rpc.events.ResultEvent;
            import mx.controls.Alert;
            import mx.collections.ArrayCollection;

            [Bindable]
            public var movielist:ArrayCollection = new ArrayCollection;            

            public function send_request():void {
                var uv:URLVariables = new URLVariables;
                uv.vq = keyword.text;
                youtubeapi.request = uv;
                  youtubeapi.send();
            }
            
            public function youtubeapi_result(event:ResultEvent):void {
                movielist.removeAll();            
                for(var i:int=0;i<event.result.feed.entry.length;i++) {
                    movielist.addItem(event.result.feed.entry[i]);
                }
            }
            
            public function playmovie():void {
                var tmp:Array = movielist.getItemAt(tilelist.selectedIndex).id.split('/');
                var video_id:String = tmp[tmp.length-1];
                var uv:URLVariables = new URLVariables;
                uv.video_id = video_id;
                flvurlapi.request = uv;
                flvurlapi.send();                
            }
            private function flvurlapi_result(event:ResultEvent):void {
                Alert.show(event.result.url);
            }
        ]]>
    </mx:Script>
    <mx:ApplicationControlBar width="100%">
        <mx:TextInput id="keyword" />
        <mx:Button label="youtube search" click="send_request()"/>
    </mx:ApplicationControlBar>
    <mx:HDividedBox width="100%" height="100%">
        <mx:TileList width="100%" height="100%" dataProvider="{movielist}"
             itemRenderer="movieitem" doubleClickEnabled="true" doubleClick="playmovie()" 
             id="tilelist" />
    </mx:HDividedBox>
    
    <mx:HTTPService id="youtubeapi" 
                    url="http://gdata.youtube.com/feeds/api/videos" method="GET"
                    result="youtubeapi_result(event)" />
                    
    <mx:HTTPService id="flvurlapi" 
                    url="http://localhost:8888/prog10/youtube_proxy.php" method="GET"
                    result="flvurlapi_result(event)" />
</mx:Application>

youtubeAPIにアクセスしたときと同じです。もう一つ HTTPServiceをつくって、ダブルクリックで呼ばれる関数で send して 応答を受信する。event.result.url で flvファイルのダウンロードURLが取得できます.
次にplayerを作ります。playerは VideoDisplayという 非常に便利なコンポーネントがありますのでこれを使います. メインの画面にこの VideoDisplayを 設置して id="player" とつけて player.source = で先ほどのflv のURLをセットすればそれで 再生できてしまいます。それだと応用性に欠けるのでplayerを独立したモジュールで作ってみましょう。簡単に再生したい方は上の方法をトライしてみてください.
flvのplayerは別につくるアプリでも使う可能性がある. とか、 再生とか停止とかタイムとかそういうデザインをメインのmxmlに加えると混乱するなどという場合に mxmlファイルを別に定義してそれを読み込む形ができます。ちょうど検索結果タイルで使用したの itemRenderer と似ているような感じです。早速playerを独立したmxmlファイルで定義してみましょう.
まず、左上のflexナビゲータのsrcのフォルダを右クリックして「新規」-「フォルダー」で module という名前のフォルダーを作ってください. 実際にはこの名前は何でもいいです.次に, 先ほどの movieitem.mxmlを作っときと同様にメニューから 「ファイル」-「新規」-「MXMLコンポーネント」を選びます.

ファイル名: player ベース: VBox 幅: 250 高さ:250

とします.作成先が module のフォルダの下になるように設定してください.最終的には下のようになればokです

さて、ではこのplayer.mxmlに 動画プレイヤーの各機能を作っていきます。
一般的な動画再生サイトのplayerはグラフィカルな停止再生ボタンや、音量調節、再生ヘッド位置とか 色々表示されていますが、今回はUI的なことに凝ると本題から外れるので最低限の以下の機能&情報を 表示することにしましょう.

  • 動画再生画面
  • 中断/再生ボタン
  • 読み込み済みbyte数 と Total byte数
  • 再生の経過時間 と Total再生時間

です。 とりあえず上の情報を並べると、例えば

というレイアウトでmxmlファイルは下記のようになります。

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" 
    width="250" height="250">
    <mx:Label text="my youtube player"/>
    <mx:VideoDisplay id="vd" width="100%" height="100%" />
    <mx:HBox>
        <mx:Button label="play" />
        <mx:Button label="stop" />    
    </mx:HBox>
    <mx:HBox>
        <mx:Label text="Loading Info:" />
        <mx:Label id="byteloaded" />
        <mx:Label text="/" />
        <mx:Label id="bytetotal" />
    </mx:HBox>
    <mx:HBox>
        <mx:Label text="Playing Info:" />
        <mx:Label id="playtime" />
        <mx:Label text="/" />
        <mx:Label id="totaltime" />
    </mx:HBox>
</mx:VBox>

という風になります。idは必要な箇所にすでに割り当てました.動画プレイヤー風になるように デザインビューなどをみてみて調節ください.さて、このプレイヤーを本体 (youtube_player.mxml) に設置するにはどうすればいいでしょう.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:module="module.*" layout="vertical">

まず、1行目に

mlns:module="module.*" 

という記述でmodule(フォルダ)以下のコンポーネントを読み込みますという宣言をします
するとmxml上で

    <mx:HDividedBox width="100%" height="100%">
        <mx:TileList width="100%" height="100%" dataProvider="{movielist}" itemRenderer="movieitem" doubleClickEnabled="true" doubleClick="playmovie()" id="tilelist" />
        <module:player id="flvplayer" />
    </mx:HDividedBox>

という風に配置することができます(TileListと並列に並べてみました)
さて、これで実行してみてください。基本のロジックは変えていませんが、一番最初に HDividedBox を設置してから右側に何も無かった部分にplayer.mxmlで定義したモジュールが加わったと思います。
もう少しです。まずは、ダブルクリックで再生できるようにしてしまいましょう. playerで定義した VideoDisplayのsourceに先ほどAPIで取得した flv のURLをセットすれば再生されます.
これをどのように実装するかは下記になります. Bindable で 変数 flvurl を定義しそれを VideoDisplayの sourceにセットする. こうすると youtube_player.mxml で先ほど定義した flvplayer から flvplayer.flvurl でアクセスできます.
player.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="250" height="250">
    <mx:Script>
        <![CDATA[
            [Bindable]
            public var flvurl:String = new String;  
        ]]>
    </mx:Script>
    <mx:Label text="my youtube player" />
    <mx:VideoDisplay id="vd" width="100%" height="100%" source="{flvurl}"/>
    <mx:HBox>
        <mx:Button label="play" />
        <mx:Button label="stop" />    
    </mx:HBox>
    <mx:HBox>
        <mx:Label text="Loading Info:" />
        <mx:Label id="byteloaded" />
        <mx:Label text="/" />
        <mx:Label id="bytetotal" />
    </mx:HBox>
    <mx:HBox>
        <mx:Label text="Playing Info:" />
        <mx:Label id="playtime" />
        <mx:Label text="/" />
        <mx:Label id="totaltime" />
    </mx:HBox>
</mx:VBox>

そのあとに本体のyoutube_player.mxmlyoutube_proxyのAPI受信関数に

            private function flvurlapi_result(event:ResultEvent):void {
                flvplayer.flvurl = event.result.url;
            }

とします。flvurlがpublic変数なので外部からアクセスでき、またBindableなので、VideoDisplayのsourceプロパティと変数が同期されます。
実行してみてください。 検索してサムネイルをダブルクリックすると動画がplayerで再生されます.
最後のplayerの装飾はplayer.mxml内部で完結します

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="250" height="250" creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import mx.events.VideoEvent;
            [Bindable]
            public var flvurl:String = new String;
            
            private function init():void {
                vd.addEventListener(ProgressEvent.PROGRESS, function():void {
                    byteloaded.text = vd.bytesLoaded.toString();            
                    bytetotal.text  = vd.bytesTotal.toString();
                    playtime.text   = vd.playheadTime.toString();
                    totaltime.text  = vd.totalTime.toString();
                });
                vd.addEventListener(VideoEvent.PLAYHEAD_UPDATE, function():void {
                    byteloaded.text = vd.bytesLoaded.toString();            
                    bytetotal.text  = vd.bytesTotal.toString();
                    playtime.text   = vd.playheadTime.toString();
                    totaltime.text  = vd.totalTime.toString();
                });
            }
        ]]>
    </mx:Script>
    <mx:Label text="my youtube player" />
    <mx:VideoDisplay id="vd" width="100%" height="100%" source="{flvurl}"/>
    <mx:HBox>
        <mx:Button label="play" click="vd.play()"/>
        <mx:Button label="stop" click="vd.pause()"/>    
    </mx:HBox>
    <mx:HBox>
        <mx:Label text="Loading Info:" />
        <mx:Label id="byteloaded" />
        <mx:Label text="/" />
        <mx:Label id="bytetotal" />
    </mx:HBox>
    <mx:HBox>
        <mx:Label text="Playing Info:" />
        <mx:Label id="playtime" />
        <mx:Label text="/" />
        <mx:Label id="totaltime" />
    </mx:HBox>
</mx:VBox>

playとstop(pause)のボタンへの割当はソースの通りButtonのclickに割り当てます。動画読み込み中、再生中に 各種データ(読み込みbyte, 再生時間の更新をするために, VideoDisplayの addEventListener の

  • ProgressEvent.PROGRESS = FLV ファイルが完全にダウンロードされるまで継続的に送出されます。
  • VideoEvent.PLAYHEAD_= UPDATE ビデオの再生中に 0.25 秒ごとに送出されます

というイベントでUpdate通知させます. 各種情報は VideoDisplayのリファレンスから抜き出せばもっとリッチなplayerがつくれます.

さて、長々説明してきた Flex builder で作る youtubeが検索できるplayer付きアプリ もこれで完成しました。かなり応用ができる範囲まで説明したと思いますので、それぞれ楽しいアプリケーションが作れることを期待します。

今回のプログラム完成版は
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog10
にありますので、必要に応じて参照してください。