2010 年 のアーカイブ

TLFTextField独習

2010 年 7月 7 日 水曜日 kosuke

ご存知の通り、Flash CS5からテキストフィールドは従来のTextFieldと、Text Layout Frameworkを実装したTLFTextFieldが扱えるようになりました。
Text Layout Frameworkは、Flash Player 10から実装されているFlash Text Engineを扱いやすいようにコンポーネント化したフレームワークなんだけど、Flash Text Engineはもとい、Text Layout Frameworkすらも、もうわけわからないってくらい、プロパティ、メソッドが多すぎて痺れます。

しかし、強力なテキスト管理機能を有した新しいテキストエンジンが今後の主流になっていくだろうし、テキストの扱いは地味ながら間違いなく多用するものなので習得せにゃならんだろうと思います。
という訳で、使える機会があれば少しづつ移行しているのだけれど、新しいものってことで実際にコケたところや試したことをエントリーしていこうかと。

とりあえず、先日転んだところで、TLFTextFieldのこと。
TLFTextFieldに関しては、正直今までのTextFieldと変わらないじゃん!わけないよ!くらいに思っていたら、小一時間悩み続けていたという話。

TextFieldでは、一部HTMLのタグが使えました。さらにスタイルシートによる、一部スタイルの指定ができました。
たとえばこんな感じ。aタグにスタイルを指定して、linkとlink:hoverにカラーを適用できます。Flashで外部更新可能なニュースを作ったりする時に使いますよね。

package{
 
 
	import flash.display.Sprite;
	import flash.text.*;
 
 
	public class TextFieldStyle extends Sprite{
 
		public function TextFieldStyle(){
			XML.ignoreComments 		= true;
			XML.ignoreProcessingInstructions= true;
			XML.ignoreWhitespace		= true;
			XML.prettyIndent		= 0;
			XML.prettyPrinting		= false;
			var doc:XML	= <div>これはTextFieldです。ここを押すと<a href="http://blog.nipx.jp" target="_blank">リンク</a>します。</div>;
 
			var style:StyleSheet	= new StyleSheet();
			style.setStyle( "a",{ color:"#4371A0" } );
			style.setStyle( "a:hover",{ color:"#6F8F0F" } );
 
			var field:TextField	= new TextField();
			field.x			= 250;
			field.y			= 10;
			field.width		= 0;
			field.height		= 0;
			field.autoSize		= TextFieldAutoSize.CENTER;
			field.wordWrap		= false;
			field.condenseWhite	= true;
			field.styleSheet	= style;
			field.htmlText		= doc.toString();
			addChild( field );
		}
 
 
	}
}

This movie requires Flash Player 10.0.0

TLFTextFieldでも同じようにHTMLタグが利用できます。

package{
 
 
	import fl.text.TLFTextField;
 
	import flash.display.Sprite;
	import flash.text.*;
 
 
	public class TLFTextFieldStyle extends Sprite{
 
 
		public function TLFTextFieldStyle(){
			XML.ignoreComments 		= true;
			XML.ignoreProcessingInstructions= true;
			XML.ignoreWhitespace		= true;
			XML.prettyIndent		= 0;
			XML.prettyPrinting		= false;
			var doc:XML	= <div>これはTLFTextFieldです。ここを押すと<a href="http://blog.nipx.jp" target="_blank">リンク</a>します。</div>;
 
			var style:StyleSheet	= new StyleSheet();
			style.setStyle( "a",{ color:"#4371A0" } );
			style.setStyle( "a:hover",{ color:"#6F8F0F" } );
 
 
			var field:TLFTextField	= new TLFTextField();
			field.x			= 250;
			field.y			= 10;
			field.width		= 0;
			field.height		= 0;
			field.autoSize		= TextFieldAutoSize.CENTER;
			field.wordWrap		= false;
			field.condenseWhite	= true;
			field.styleSheet	= style;
			field.htmlText		= doc.toXMLString();
			addChild( field );
		}
 
 
	}
}

This movie requires Flash Player 10.0.0

しかし、aタグのスタイル指定が適用されていません。そうTLFTextFieldは、TextFieldと共通の使い勝手の同じプロパティが多数あるけど、中身は別物。styleSheetプロパティについては、

ってことで、styleSheetのプロパティはあるけれど常に無意味。スタイルシートはサポートされない。
そもそも、HTMLタグはサポートされるものの、TLFTextFieldのhtmlTextでは、

aタグの疑似クラスからしてサポートされていないみたい。
ええ!?それじゃあ劣化してんじゃん?って思うのは浅はかだったというもの。そこはしっかりText Layout Frameworkでサポートされています。魔のText Layout Frameworkで。
TLFTextField.textFlowのプロパティにそれらしきものを見つけたところから迷走しだす。

一連のヘルプを読み、
Text Layout Framework の使用

野中先生の解説を読み、
ActionScript 3.0でFlash Professional CS5の Text Layout Frameworkを使う

TLFTextField.textFlowのTextFlowを直接定義したり入れ替えたりと散々まさぐって、それで今回の指定はできたんですけど、なんともまどろっこしい。TLFTextField使う意味ないじゃん!
後にそんなことをせずとも出来ることに気づいた。

TLFTextField.tlfMarkupプロパティ。あるじゃん!これに記述を入れれば出来ました。

package{
 
 
	import fl.text.TLFTextField;
 
	import flash.display.Sprite;
	import flash.text.*;
 
	import flashx.textLayout.conversion.*;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.formats.TextLayoutFormat;
 
 
	public class TLFTextFieldStyle1 extends Sprite{
 
 
		public function TLFTextFieldStyle1(){
			XML.ignoreComments 		= true;
			XML.ignoreProcessingInstructions= true;
			XML.ignoreWhitespace		= true;
			XML.prettyIndent		= 0;
			XML.prettyPrinting		= false;
			var doc:XML	= <div>これはTLFTextFieldです。ここを押すと<a href="http://blog.nipx.jp" target="_blank">リンク</a>します。</div>;
 
			var field:TLFTextField	= new TLFTextField();
			field.x			= stage.stageWidth/2;
			field.y			= 10;
			field.width		= 0;
			field.height		= 0;
			field.autoSize		= TextFieldAutoSize.CENTER;
			field.wordWrap		= false;
			field.multiline		= false;
			field.condenseWhite	= true;
 
			//変換
			var flow:TextFlow	= TextConverter.importToFlow( doc.toXMLString(), TextConverter.TEXT_FIELD_HTML_FORMAT );
			var markup:XML	= TextConverter.export( flow, TextConverter.TEXT_LAYOUT_FORMAT, ConversionType.XML_TYPE ) as XML;
			field.tlfMarkup	= markup;
 
			//リンクフォーマット
			var linkNormalFormat:TextLayoutFormat	= new TextLayoutFormat();
			linkNormalFormat.color					= 0x4371A0;
			var linkHoverFormat:TextLayoutFormat	= new TextLayoutFormat();
			linkHoverFormat.color					= 0x6F8F0F;
			field.textFlow.linkNormalFormat	= linkNormalFormat;
			field.textFlow.linkHoverFormat	= linkHoverFormat;
 
			//更新
			field.textFlow.invalidateAllFormats();
			field.textFlow.flowComposer.updateAllControllers();
 
			addChild( field );
		}
 
 
	}
}

This movie requires Flash Player 10.0.0

とりあえず今回要件を満たすには、テキストはTextLayout マークアップ形式で記述する必要があります。
この形式で記述するなら、たとえば、

var doc:XML	= <TextFlow xmlns='http://ns.adobe.com/textLayout/2008'><p><span>これはTLFTextFieldです。ここを押すと</span><a href="http://blog.nipx.jp" target="_blank">リンク</a><span>します。</span></p></TextFlow>;

のように記述しなければいけない。この例でも<span>の記述がなんだかめんどくさいと思うんだよね。
外部ファイルでメンテナンスできることを前程に考えているんで、知識のないひとに記述してもらうのはますます厳しい。

なので、元の、

var doc:XML	= <div>これはTLFTextFieldです。ここを押すと<a href="http://blog.nipx.jp" target="_blank">リンク</a>します。</div>;

を変換する方法で考えます。

記述をTextFlowに変換したりTextFlowをマークアップに変換したりする、TextConverterクラスがあるのですが、直接この記述を、

var doc:XML	= <div>これはTLFTextFieldです。ここを押すと<a href="http://blog.nipx.jp" target="_blank">リンク</a>します。</div>;
var flow:TextFlow	= TextConverter.importToFlow( doc, TextConverter.TEXT_LAYOUT_FORMAT);

のようにしても変換できません。

一旦、TextConverter.TEXT_FIELD_HTML_FORMATに変換してから、再度TextConverter.TEXT_LAYOUT_FORMATの記述に書き出します。

var flow:TextFlow	= TextConverter.importToFlow( doc.toXMLString(), TextConverter.TEXT_FIELD_HTML_FORMAT );
var markup:XML	= TextConverter.export( flow, TextConverter.TEXT_LAYOUT_FORMAT, ConversionType.XML_TYPE ) as XML;
field.tlfMarkup	= markup;

TLFTextFieldのtextFlowではリンクのフォーマットを指定して、最後に更新する。
invalidateAllFormats()が重要。これを実行しないと新しいフォーマットが適用されなかった。

var linkNormalFormat:TextLayoutFormat	= new TextLayoutFormat();
linkNormalFormat.color					= 0x4371A0;
var linkHoverFormat:TextLayoutFormat	= new TextLayoutFormat();
linkHoverFormat.color					= 0x6F8F0F;
field.textFlow.linkNormalFormat	= linkNormalFormat;
field.textFlow.linkHoverFormat	= linkHoverFormat;
 
field.textFlow.invalidateAllFormats();
field.textFlow.flowComposer.updateAllControllers();

HIROSHI IWASAKI

2010 年 7月 1 日 木曜日 kosuke

フォトグラファー 岩崎寛さんのポートフォリオサイトを作りました。
nssgraphica町田さんとのお仕事。僕はFlashの実装を担当させていただきました。
Flashで使う外部XMLファイルをXHTMLで記述することでHTML版のサイトを用意。
何かと話題なiPhoneやiPadでも作品が見れるように構成しました。
さまざまな大人の事情で表に出せない仕事が多いなか久々に公開できる案件。楽しくやらせていただきました。

HIROSHI IWASAKI
http://hiroshiiwasaki.com

ArtDirection&Design / Munehiro Machida. nssgraphica
Flash / KOSUKE,Nakamura. nipx
Photograph / Hiroshi Iwasaki. stash

Safari 5.0 で画面が溶ける

2010 年 6月 22 日 火曜日 kosuke

Safari 5.0 にしたら全面Flashのリサイズ時にオブジェクトが伸びるようになった。革新的過ぎる。他のサイトでも起きてるし、FireFoxでは今迄どおり動いているし。仕様か?バグか?新手の嫌がらせか?!

Safari バージョン 5.0 (6533.16)


forループ

2010 年 6月 14 日 月曜日 kosuke

小ネタが続きます。
forステートメントは、指定回数のループに用いることが通常ですが、構文的には、

for( 初期値; 終了する条件; 次の値 )

です。なので例えば以下みたいに使うことも出来る。

var master:Vector.<String>	= Vector.<String>([
	"0",
	"1",
	"2",
	"1",
	"21",
	"2",
	"2"
]);
var record:Vector.<String> = Vector.<String>([]);
 
function getValue( str:String ):String{
	var value:String;
	for each( var target:String in record ){
		if( target == str ){
			value = target;
			break;
		}
	}
	return value;
}
 
 
for( var i:uint=0; i<master.length; i++ ){
	var n:String	= master[i];
	for( n; getValue(n); n+="+" );
	record.push( n );
}
 
 
trace( "master:" + master );
trace( "record:" + record );

上記のソースでは、ある文字列の配列をループして、同じ名前があったら、文字列の最後に「+」を付与しています。

for文のポイントはここ。

for( n; getValue(n); n+="+" );

初期値nには、配列から取得した文字列になります。
終了条件は、getValueで得ます。getValueはnの文字列が既に登録されていたらその文字列を、そうでなければ空を返す。
次の値はnの最後に「+」を付与したもの。

このループを通過後、nにはまだ登録されていない文字列が入っています。
結果は以下になります。

master:0,1,2,1,21,2,2
record:0,1,2,1+,21,2+,2++

これをどんな時に使うかというと、たとえばユーザが変更できるxmlから値を取得して使う時、その値は同じものがあってはならない場合なんかに有効。xmlで外部編集を可能にした場合、意図しない値をユーザが入力してしまう可能性があります。転ばぬ先の杖的にこういった構文を入れておくとよいかもしれないです。

Shadowbox.js カウンタの表記

2010 年 6月 11 日 金曜日 kosuke

このブログでも使っている、Shadowbox.jsというLightBox系Ajaxライブラリについて。
Shadowboxでは、グループ化した時、カウンタが表示されます。このカウンタは「 1 of 10 」のような表記。だけどあまり、この「 of 」って表記をデザインで使うことなくないですか?
大体、1 / 10 みたいな。スラッシュを使うんじゃないかと。こっちの方がスマートに見えるし。

そこで、このofを変えたいと思ったのですが、この表記は公式サイトにあるAPIやOptionには記載が無かった。しょうがないので、中身をみてみたらありました。 S.langってオブジェクトの中です。

S.lang = 
{
	 code : "en", of : "of", loading : "loading", cancel : "Cancel", next : "Next", previous : "Previous", 
	 play : "Play", pause : "Pause", close : "Close", errors : 
	 {
	     single : 'You must install the <a href="{0}">{1}</a> browser plugin to view this content.', 
	     shared : 'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.', 
	     either : 'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'
	 }
};

このオブジェクトのofの値が表示されてるっぽい。SはShadowboxのショートカットなので、

Shadowbox.lang.of = "/";

のようにして、表記を変更できます。

Adobe CS5

2010 年 5月 29 日 土曜日 kosuke

昨日発売のCS5。発売前日のAdobe Creative Suite 5 Web Premium発売記念セミナーも見に行ってました。しばらく体験版試して…とか考えたけど結局、昨日注文して今日届きました。CS4を見送ったって人も今回は購入されたのでは?

インストール後に登録できるCS Live サービス。1年間無料で使えるのだけど、このBROWSERLABが結構良いかも。選んだOS、ブラウザの表示状態がまとめてみれる。スゴい。

AIR for Android Developer Prerelease を試してみた

2010 年 5月 25 日 火曜日 kosuke

話題のAndroid用AIRの開発者向けプレリリースがどんなものなのか知りたく登録してみた。

プログラムはこちら。
Adobe AIR for Android

このプログラムは誰でも登録出来るのだけど、プログラムで得た内容を外部に開示してはならないようなことが記されているので詳細は書かず話を進めます。

ひとまず、本当に動くの?ってことが試したくて、昨日作った待ち受けFlashをas3で書き直してAIR化。Androidアプリにしてみる。

こちらも初めて使いましたが、Android SDKをダウンロードしてAndroidのエミューレート環境を作りインストールします。

どうかっていうと普通に動きます。拍子抜けするくらい簡単に。

で、先の理由で記事的には書きようがないわけですが、言いたいことが一つ出て来た。何かと言うと、

本当にいいの?Apple。Flash 無しで?

CS5は、これのiPhone版が既に完成された状態で組み込まれているっていうのにもったいないなぁ。FlashでiPhoneアプリ作りたいって開発者は大勢いるのに。iPhone OS4は本当にあの規約のままリリースするんですかね?

YouTube API 3

2010 年 5月 17 日 月曜日 kosuke

GWにさわったYouTube APIはプレイヤーまわりだけでしたが、その後真打ち部分、YouTube Data APIを少し試してみました。

Data APIは、プレイヤーに渡す動画を選択したり、YouTubeが備えるコミュニティ機能を扱うAPIです。リファレンスはこちら。

デベロッパー ガイド: Data API プロトコル

Data APIではフィードを使ってやりとりをしていきます。リクエストの種類がいろいろあるので、必要な情報はおそらく得られると思いますが、思ったように使うのはやっぱり大変ですよね。

ひとまず、作ってみたサンプルがこちら
動画の検索結果のサムネイルをロモウォール風に表示して動画を選べます。

忘れないうちに、つまずいたところを記憶しておきます。

動画の検索については、リファレンスの「動画の取得と検索」の「動画の検索」にある、

http://gdata.youtube.com/feeds/api/videos

をリクエストします。
すると、「動画のフィードとエントリの説明」にあるような、検索結果のフィードが返ってくる。この検索結果の「entryタグ」がヒットした動画の情報になっています。

リクエストの際、取得する検索結果の位置や数を指定したりするパラメータがあるのだけれど、ここでまずころぶ。

取得する検索結果を数は、

max-results

というパラメータで指定できる。(最大50件まで)

検索結果をどこから取得するかは、

start-index

で指定できます。

検索にヒットした動画の数は、フィードの最初の方にある、

openSearch:totalResults

タグでわかります。

ここまでざっと見て、ページング(ページ分け)をするには、次のフィードを取得する検索パラメータを、totalResultに至るまでstart-indexにmax-resultsを足した位置とすればよいと思いました。でも、これはNG。
openSearch:totalResultsの説明
を良く読むと、なんとtotalResultsの値は「おおよその値」を示していて、正確な値を示しているとは限らないとある。え?なんともあいまいなと思うのだが、実際検索してみると、頻繁にtotalResultsの数が上下するわけです。
したがってページ分けにこのopenSearch:totalResultsは使えない。
ページ分けをするには、totalResultsのリファレンスの下に書かれている<link rel=”next”> と <link rel=”prev”> を使って行います。
これ、はまった。。。。

次にプレイヤーなんですが、イベントの取得に、

player.addEventListener(event:String, listener:Function):Void

を使いますが、これが消せないっぽい。

player.destroy():Void

なんてそれっぽいAPIもダメでした。Loaderをunloadしてもnullしても消えなくて、何度も読み込む場合、付与したイベントが問題になったりする。新しいLoaderを作れば以前のリスナーが動く機会は無くなるけど、これだとメモリにどんどんゴミが溜まっていっちゃう。どうもそもそも消えない?っぽいので、プレイヤーは一度ロードしたらアンロードせず、cueVideoByUrlやloadVideoByUrlなどで新しい動画を読み込んだほうがよいかもしれません。

YouTube API 2

2010 年 5月 6 日 木曜日 kosuke

前のエントリーの続き。クロムレスプレイヤーを試します。

クロムレスプレイヤーをロードする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よりむしろ使いやすく感じました。


YouTube Chromeless Player

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;
		}
 
 
	}
}

YouTube API

2010 年 5月 1 日 土曜日 kosuke

GWで嫁と子どもは帰省しちゃったし、仕事は片付いたしで、良いのか悪いのかあまりにまとまった時間があるので使ったことのないWeb APIを学習してみる。YouTube API。FlashからYouTube APIにアクセスしてFlash上で再生することにトライ。

リファレンスはこちらです。
YouTube ActionScript 3.0 Player API Reference

デフォルトの操作パネルをもつ「embedded player」と、ビデオエリアだけが表示される「chromeless player」とがある。独自の操作パネルを設定するなら「chromeless player」を使う。

それぞれのプレイヤーを呼び出すAPIは、

chromeless player

http://www.youtube.com/apiplayer?version=3

embedded player

http://www.youtube.com/v/VIDEO_ID?version=3

chromeless playerは、プレイヤーをロード後にムービーを指定するようで、embedded playerはロード時にムービーのVIDEO_IDを渡す。VIDEO_IDについて記載を見かけなかったのだけど、ブラウザからYouTubeを表示した時のURLのクエリv=〜の部分のようです。

ひとまずembedded playerを試してみた。

おわり…。

embedded playerをSWFに埋め込むだけなら、これだけっぽい。
ポイントになりそうなところは、ロードがINIT状態になったら、さらにコンテンツがonReadyイベントを返すまで待つこと。
このイベントをもってプレイヤーの初期化が完了しプレイヤーのAPIが利用できるようになる。
あとはリファレンスに載っているAPIを使ってプレイヤーを好きなように操作出来るはず。

拍子抜けするくらい簡単に使えるようだが、クロムレスプレイヤーとあわせてもう少しAPIを追ってみようと思う。