YouTube API 2
- 2010 年 5月 6 日
前のエントリーの続き。クロムレスプレイヤーを試します。
クロムレスプレイヤーをロードするAPIは、プレイヤーがロードされた段階ではビデオは表示されないので、ロードされたらビデオを渡します。
VIDEO_IDで渡す
player.cueVideoById(videoId:String, startSeconds:Number, suggestedQuality:String):Void player.loadVideoById(videoId:String, startSeconds:Number, suggestedQuality:String):Void |
URLで渡す
player.cueVideoByUrl(videoId:String, startSeconds:Number, suggestedQuality:String):Void player.loadVideoByUrl(videoId:String, startSeconds:Number, suggestedQuality:String):Void |
このAPIの挙動が微妙に違っていてcueVideoById、cueVideoByUrlは自動的に再生しないがloadVideoById、loadVideoByUrlは自動的に再生される。
第2・第3引数は共通で、startSecondsは再生を開始する位置。suggestedQualityはビデオの解像度です。
ビデオを読み込み後は、FLVPlayerbackコンポーメントなんかと同様に、Player APIを使ってイベント処理や制御を組み込みます。
Player APIではプレイヤーの状態に応じて、
onStateChange
が発生します。
FLVPlaybackのように、再生ヘッドのインターバルだとか、バッファのインターバルのような、細かいイベントはないので、このonStateChangeを使って処理する形になりますが、肥大化しすぎのFLVPlaybackよりむしろ使いやすく感じました。
APIを組むのに補完が出ないのが面倒だったので、Proxyを作ってみた。
package video{ import flash.display.*; import flash.utils.*; public class YouTubePlayer extends Proxy{ public static const ON_READY:String = "onReady"; public static const ON_STATE_CHANGE:String = "onStateChange"; public static const ON_PLAYBACK_QUALITY_CHANGE:String = "onPlaybackQualityChange"; public static const ON_ERROR:String = "onError"; public function YouTubePlayer( object:DisplayObject ){ _object = object; } private var _object:DisplayObject; override flash_proxy function callProperty(methodName:*, ... args):* { var res:*; res = _object[methodName].apply(_object, args); return res; } override flash_proxy function getProperty(name:*):* { return _object[name]; } override flash_proxy function setProperty(name:*, value:*):void { _object[name] = value; } public function cueVideoById(videoId:String, startSeconds:Number, suggestedQuality:String):void{ flash_proxy::callProperty( "cueVideoById", videoId, startSeconds, suggestedQuality ); } public function loadVideoById(videoId:String, startSeconds:Number, suggestedQuality:String):void{ flash_proxy::callProperty( "loadVideoById", videoId, startSeconds, suggestedQuality ); } public function cueVideoByUrl(mediaContentUrl:String, startSeconds:Number, suggestedQuality:String):void{ flash_proxy::callProperty( "cueVideoByUrl", mediaContentUrl, startSeconds, suggestedQuality ); } public function loadVideoByUrl(mediaContentUrl:String, startSeconds:Number, suggestedQuality:String):void{ flash_proxy::callProperty( "loadVideoByUrl", mediaContentUrl, startSeconds, suggestedQuality ); } public function playVideo():void{ flash_proxy::callProperty( "playVideo" ); } public function pauseVideo():void{ flash_proxy::callProperty( "pauseVideo" ); } public function stopVideo():void{ flash_proxy::callProperty( "stopVideo" ); } public function seekTo(seconds:Number, allowSeekAhead:Boolean):void{ flash_proxy::callProperty( "seekTo", seconds, allowSeekAhead ); } public function mute():void{ flash_proxy::callProperty( "mute" ); } public function unMute():void{ flash_proxy::callProperty( "unMute" ); } public function isMuted():Boolean{ return flash_proxy::callProperty( "isMuted" ); } public function setVolume(volume:Number):void{ flash_proxy::callProperty( "setVolume", volume ); } public function getVolume():Number{ return flash_proxy::callProperty( "getVolume" ); } public function setSize(width:Number, height:Number):void{ flash_proxy::callProperty( "setSize", width, height ); } public function getVideoBytesLoaded():Number{ return flash_proxy::callProperty( "getVideoBytesLoaded" ); } public function getVideoBytesTotal():Number{ return flash_proxy::callProperty( "getVideoBytesTotal" ); } public function getVideoStartBytes():Number{ return flash_proxy::callProperty( "getVideoStartBytes" ); } public function getPlayerState():Number{ return flash_proxy::callProperty( "getPlayerState" ); } public function getCurrentTime():Number{ return flash_proxy::callProperty( "getCurrentTime" ); } public function getPlaybackQuality():String{ return flash_proxy::callProperty( "getPlaybackQuality" ); } public function setPlaybackQuality(suggestedQuality:String):void{ flash_proxy::callProperty( "setPlaybackQuality", suggestedQuality ); } public function getAvailableQualityLevels():Array{ return flash_proxy::callProperty( "getAvailableQualityLevels" ); } public function getDuration():Number{ return flash_proxy::callProperty( "getDuration" ); } public function getVideoUrl():String{ return flash_proxy::callProperty( "getVideoUrl" ); } public function getVideoEmbedCode():String{ return flash_proxy::callProperty( "getVideoEmbedCode" ); } public function addEventListener(event:String, listener:Function):void{ flash_proxy::callProperty( "addEventListener", event, listener ); } public function destroy():void{ flash_proxy::callProperty( "destroy" ); } } } |
最低限の操作系を用意。Play・Pause、Stop、シークバー。
今回はAPIの確認なんで作り込まず、Flexのコンポーネントで定義。
<?xml version="1.0" encoding="utf-8"?> <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300" creationComplete="init()"> <s:layout> <s:BasicLayout/> </s:layout> <fx:Script> <![CDATA[ private function init():void{ alpha = 0.5; seekBar.scaleY = 0.25; seekBar.decrementButton.visible = false; seekBar.incrementButton.visible = false; } ]]> </fx:Script> <fx:Declarations> <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 --> </fx:Declarations> <s:Button x="55" y="0" label="Pause" id="playPause" width="50" fontSize="10"/> <s:Button x="0" y="0" label="Stop" id="stop" width="50" fontSize="10"/> <s:HScrollBar x="94" y="8" width="320" id="seekBar"/> </s:Group> |
プレイヤーを格納する表示オブジェクト
package video{ import flash.display.*; import flash.events.*; import flash.net.*; import jp.progression.casts.*; import jp.progression.casts.mx.*; import jp.progression.commands.*; import jp.progression.commands.display.*; import jp.progression.commands.net.*; import mx.events.*; public class ChromelessPlayerContainer extends CastUIComponent{ public function ChromelessPlayerContainer(){ super(); _loader = new Loader(); _controller = new Controller(); _controller.y = 305; //トラックを操作した時のアニメーション、マウス位置に移動させる _controller.seekBar.setStyle( "smoothScrolling", false ); _controller.seekBar.setStyle( "repeatInterval", 0 ); _controller.seekBar.setStyle( "repeatDelay", 0 ); } private var _loader:Loader; //ローダー private var _player:YouTubePlayer; //プレイヤーオブジェクト private var _controller:Controller; //コントローラー protected override function atCastAdded():void{ addCommand( new LoadURL( new URLRequest( "http://gdata.youtube.com/feeds/api/videos" ), { onStart:function():void{ var vars:URLVariables = new URLVariables(); vars.vq = "teI8o6k67-k"; vars.format = 5; this.request.data =vars; } } ), new Func( function():void{ var _feed:XML = new XML( this.latestData ); //xmlが持つ名前空間を格納 var _media:Namespace = _feed.namespace( "media" ); //feedのyt名前空間 var _yt:Namespace = _feed.namespace( "yt" ); //feedのyt名前空間 _loader.contentLoaderInfo.addEventListener( Event.INIT, onInit ); _loader.load( new URLRequest( "http://www.youtube.com/apiplayer?version=3" ) ); function onInit( e:Event ):void{ e.target.removeEventListener( e.type, arguments.callee ); _player = new YouTubePlayer( _loader.content ); _player.addEventListener( YouTubePlayer.ON_STATE_CHANGE, onStateChange ); _player.addEventListener( "onReady", onReady ); } function onReady( e:Event ):void{ e.target.removeEventListener( e.type, arguments.callee ); _player.setSize( 400, 300 ); //プレイヤーに映像を表示する //表示するURLを取得 var q:QName = new QName( _media, "content" ); var media:XMLList = _feed.descendants( q ); //取得した要素のformatからswf形式を取得 for each( var node:XML in media ){ if( node.@_yt::format == 5 ){ break; } } //読み込み _player.loadVideoByUrl( node.@url, 0, "default" ); _loader.dispatchEvent( new Event( Event.COMPLETE ) ); } }, null, _loader, Event.COMPLETE ), new AddChild( this, _loader ), new AddChild( this, _controller ), new Func( function():void{ //停止ボタン _controller.stop.addEventListener( FlexEvent.BUTTON_DOWN, onMouseDownStop ); //再生、一時停止ボタン _controller.playPause.addEventListener( FlexEvent.BUTTON_DOWN, onMouseDownPlayPause ); //シーク操作 _controller.seekBar.addEventListener( FlexEvent.CHANGE_START, onChangeStart ); _controller.seekBar.addEventListener( FlexEvent.CHANGE_END, onChangeEnd ); }) ) } //停止ボタンを押したら private function onMouseDownStop( e:FlexEvent ):void{ //ビデオを停止 _player.stopVideo(); } //再生・一時停止ボタンを押したら private function onMouseDownPlayPause( e:FlexEvent ):void{ //ボタンにPlayが表示されていたら if( _controller.playPause.label == "Play" ){ //一時停止 _player.playVideo(); } //ボタンにPauseが表示されていたら else{ //再生 _player.pauseVideo(); } } //プレイヤーの状態が変わったら private function onStateChange( e:Event ):void{ var state:int = _player.getPlayerState(); //サムの進行を停止 _controller.removeEventListener( Event.ENTER_FRAME, onEnterFrame ); switch( state ){ case -1: //停止なら _controller.playPause.label = "Play"; _controller.seekBar.value = 0; break; case 0 : //最後まで再生したら break; case 1 : //再生なら _controller.playPause.label = "Pause"; //サムの進行開始 _controller.addEventListener( Event.ENTER_FRAME, onEnterFrame ); break; case 2 : //一時停止なら _controller.playPause.label = "Play"; break; case 3 : //バッファ中なら break; case 5 : //キューされたら //cueVideoById、cueVideoByUrlの時、発生 break; } } //ユーザーがシーク操作を開始したら private function onChangeStart( e:FlexEvent ):void{ _isSeek = true; } //ユーザーがシーク操作を完了したら private function onChangeEnd( e:FlexEvent ):void{ _isSeek = false; if( _player.getPlayerState() < 0 ){ _player.playVideo(); } _player.seekTo( ( _controller.seekBar.value / _controller.seekBar.maximum ) * _player.getDuration(), true ); } //コントローラ操作中はtrue private var _isSeek:Boolean = false; //サムを再生位置に進める private function onEnterFrame( e:Event ):void{ //コントローラ捜査中は終了 if( _isSeek ) return; //現在の再生時間 var time:Number = _player.getCurrentTime(); //ビデオの合計時間 var total:Number = _player.getDuration(); _controller.seekBar.value = ( time / total ) * 100; } } } |