‘Flash’ カテゴリーのアーカイブ

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を追ってみようと思う。

ApplicationDomain

2010 年 4月 18 日 日曜日 kosuke

最近のFlashはサイトは1つのプロジェクトを分業して複数人で開発することが多くなってきた。大規模なサイトを構築・運営するには一人の開発者が全てまかなえるわけもなく当然な話でもある。

そうなると問題になりそうなことがクラス名の衝突。実際、AS2のフルフラッシュサイトでは問題になったことも度々あった。
クラスベースで開発することが多いAS3では、親と子側のSWFで開発者が違うと同名のクラスがあって思うように動かないなんてことがますます増えそうだ。

そこで同名クラスの問題を回避するApplicationDomainの使い方を記録しておく。
ApplicationDomainの指定で同名クラスであっても親と子のクラスを分離できる。

2つのSWFがありどちらもドキュメントクラスがIndexだとします。
ただし、このIndexは別の開発者が作った別のクラス。そこで、

1) Index1.swf = Aさんが作ったswfで、Aさんが作ったIndexドキュメントクラスを持つ。
2) Index2.swf = Bさんが作ったswfで、Bさんが作ったIndexドキュメントクラスを持つ。
3) Index1.swfは、子としてIndex2.swfを読み込む。

とします。

で、Aさんが作ったindex1.swfのIndexクラスは、

package{
	import flash.display.*;
	public class Index extends Sprite{
		public function Index(){
			trace( "Aさん" );
		}
	}
}

「Aさん」と出力する。

かたや、Bさんが作ったindex2.swfのIndexクラスは、

package{
	import flash.display.*;
	public class Index extends Sprite{
		public function Index(){
			trace( "Bさん" );
		}
	}
}

「Bさん」と出力する。この二つもちろん単体では問題なく動きます。

しかし、Aさんの作ったIndexでBさんのindex2.swfを読み込むと

package{
	import flash.display.*;
	public class Index extends Sprite{
		public function Index(){
			trace( "Aさん" );
			var loader:Loader	= new Loader();
			loader.load( new URLRequest( "Index2.swf" ) );
		}
	}
}

期待に反して「Aさん」が出力され続ける。

読み込まれたBさんのIndexは、AさんのIndexクラスと同名の為、親側のAさんのIndexクラスと区別がつかず、AさんのIndexクラスを実行し続けてしまいます。なんにも取り決めをせず、別々に作ったら起きそうなエラーでしょ?
こんな時の為にApplicationDomainがあります。

AさんのIndexで、LoaderContextでアプリケーションドメインを分離して読み込むと、

package{
	import flash.display.*;
	public class Index extends Sprite{
		public function Index(){
			trace( "Aさん" );
			var context:LoaderContext	= new LoaderContext( false, new ApplicationDomain( null ) );
			var loader:Loader	= new Loader();
			loader.load( new URLRequest( "Index2.swf" ), context );
		}
	}
}

今度は正しく「Aさん」「Bさん」と出力されました。

ApplicationDomainはクラスを分離しているだけで、Index2.swfからIndex1.swfのプロパティやメソッドにアクセス出来なくなるわけではありません。こちらのアクセスはまた別の話。参照すれば親のメソッドを実行できます。

AさんのIndexでindex2.swfを読み込み表示する。

package{
	import flash.display.*;
	public class Index extends Sprite{
		public function Index(){
			trace( "Aさん" );
			var context:LoaderContext	= new LoaderContext( false, new ApplicationDomain( null ) );
			var loader:Loader	= new Loader();
			loader.load( new URLRequest( "Index2.swf" ), context );
			loader.contentLoaderInfo.addEventListener( Event.COMPLETE, 
				function onComplete(e:Event):void{
					addChild( loader );
				}
			);
		}
 
		public function onTest():void{
			trace( "Aさんのテスト" );
		}
	}
}

BさんのIndexで、AさんのIndexのメソッドを実行する。

package{
	import flash.display.*;
	public class Index extends Sprite{
		public function Index(){
			trace( "Bさん" );
			addEventListener( Event.ADDED_TO_STAGE,
				function( e:Event ):void{
					var parentIndex:Object = parent.parent as Object;
					if( parentIndex ) parentIndex.onTest();
				}
			);
		}
	}
}

逆に名前問題は解決していて子から親のクラスを使いたいってこともあると思います。
複数人での開発プロジェクトでは、セキュリティや受け渡しをしっかり話あっておかないと、合体させた時に思わぬトラブルに陥りそうですね。
Flashのセキュリティ関連は、バージョンが上がる度にややこしくなっているので、よくよく調べておこう。

参考:
ApplicationDomain クラスの使用
LoaderInfo
Flash Player セキュリティ

定数の構成

2010 年 4月 16 日 金曜日 kosuke

CS4から加えられたこの機能を今頃になって知る。実用的な機能なので記録しておこう。

ActionScript パブリッシュ設定

定数の構成を使うと、コンパイル時の条件指定が可能になる。
つまり定数の真偽値に応じてソースをSWFに含めるかどうか指定できるわけだ。
開発環境と本番環境とで異なる値を使う時や、デバック中はtrace出力したいけど公開するファイルには含みたくないなどの場合に使える。

ソース内に記述する変数で分岐させるのと違って、書き出したSWFにソース自体が含まれないので逆コンパイルの心配もないだろう。

定数の構成の設定パネルは、パブリッシュ設定 → ActionScript30.の詳細設定 → 定数の構成で表示する。

AS3のFlashファイルを作った場合、最初からCONFIG::FLASH_AUTHORINGという定数が宣言されている。これを使って例えば開発環境と本番環境で異なるURLが必要な場合、

package{
 
	import flash.display.Sprite;
 
	public class Teisu extends Sprite{
 
		public function Teisu(){
			super();
 
			var url:String = "http://nipx.jp/";
 
			CONFIG::FLASH_AUTHORING
			{
				url = "http://test.nipx.jp/";
			}
 
			trace( url );
		}
 
	}
 
}

のように記述すると、定数の真偽値によってURLを変更できる。
CONFIG::FLASH_AUTHORINGがtrueの場合、{ } 内のソースがコンパイルされて、urlはhttp://test.nipx.jp/になる。

多角形のパスの向き

2010 年 4月 9 日 金曜日 kosuke

多角形を構成する座標がある時、その多角形を左回りに描いているか右回りに描いているかの判定方法を記録。やらんとしていることは3Dの陰面消去と同様ですが、たとえば数字の「8」のアウトラインデータには、外側輪郭のパスが一つと、内側の抜きのパスが2あって、パスデータが輪郭のものか抜きのものか判断したかった。イラレでいうところの複合パスを作った時のパスの方向の部分です。

この判断方法ですが検索して、すごくわかりやすいページを見つけましたので紹介させていただきます。

参考
閉図形の座標の配列が右回りか左回りか調べる方法 – 教えて!goo
多角形の面積,重心(図心),断面N次モーメントの公式と,向き (頂点列の回転方向) の判別方法

わー、こういう公式があるんですね。試してみて期待通りの結果が得られました。

//右回りの座標
var _pts1:Vector.<Point>	= Vector.<Point>( [
	new Point( -50, -50 ),
	new Point(  50, -50 ),
	new Point(  50,  50 ),
	new Point( -50,  50 )
] );
 
//左周りの座標
_pts2:Vector.<Point>	= Vector.<Point>( [
	new Point( -50, -50 ),
	new Point( -50,  50 ),
	new Point(  50,  50 ),
	new Point(  50, -50 )
] );
 
//符号付面積を返す
function getArea( pts:Vector.<Point> ):Number{
	var S:Number	= 0;
	for( var i:uint=0; i<pts.length; i++ ){
		var a:Point = pts[i];
		var b:Point = ( i<pts.length-1 ) ? pts[ i+1 ] : pts[0];
		S += a.x * b.y - a.y * b.x;
	}
	return S / 2;
}
 
//右回りの座標より取得
getArea( _pts1 );
 
//左回りの座標より取得
getArea( _pts2 );

プロットして確認したファイル:多角形のパスの向き

This movie requires Flash Player 10.0.0

この公式、こちら作品でパスデータから塗りに抜きを作るのに使いました。
OutlineClock

pixelDissolveとDisplacementMapFilterのエフェクト

2010 年 4月 8 日 木曜日 kosuke

2BLOGの投稿は結構久しぶりになってしまいました。pixelDissolveを使ったエフェクト。
Flashやってて今更って話ですけど、はじめてwonderflを使った…。便利ですね。

CastPreloaderのイベント処理

2010 年 3月 10 日 水曜日 kosuke

Progression 4.0.1 Public Bate 1.3 を使っていて気づいたこと。CastPreloaderではProgressionインスタンスが生成されていないので、CastPreloader内でCastSpriteなど、Progressionの表示オブジェクトをAddChildコマンドで加えても、CastSpriteのatCastAddedなどイベント処理の実行がされないようだ。

たとえば以下のように、CastPreloaderのatCastLoadStart処理中にCastSpriteを加え,そのcastAddedイベント中にTraceコマンドで出力を試みても「 sample onCastAdded 」のtraceは出力されない。

override protected function atCastLoadStart():void{
	var sample:CastSprite	= new CastSprite();
	sample.onCastAdded = function():void{
		addCommand(
			new Trace( "sample onCastAdded" )
		)
	}
 
	addCommand(
		new AddChild( this.foreground, sample )
	);
}

castAddedイベントが正しく処理されるようにするには、PreloaderでもProgressionのインスタンスを生成しておくと期待通りになる。

override protected function atCastLoadStart():void{
	Progression.initialize( new WebConfig() );
	var manager:Progression	= new Progression( "preloader", this.stage );
 
	var sample:CastSprite	= new CastSprite();
	sample.onCastAdded = function():void{
		addCommand(
			new Trace( "sample onCastAdded" )
		)
	}
 
	addCommand(
		new AddChild( this.foreground, sample )
	);
}

ここでは、atCastLoadStart内でProgressionクラスの初期化とインスタンスを作っているけど、コンストラクタで作った方が自然かな。

ちなみに、PreloaderにProgressionクラスを加えると10K以上ファイルサイズが増えます。不要ならProgressionインスタンスをPreloaderで使う必要はないでしょう。
Preloaderの性質を考えるとそのファイルサイズは小さいほどよいと思いますので、CastPreloaderがデフォルトでProgressionインスタンスを生成しないのは僕は望ましいと思います。おそらくそのあたりがこの仕様の理由なんじゃないかと思ってみたり、みなかったり。

Basic認証を越える方法

2010 年 2月 27 日 土曜日 kosuke

前回エントリーの際に、Basic認証越えを試した時のメモ。

Basic認証を越える方法は、FLASH-JP.COM – フォーラム にスレッドが有名だと思います。

検索するとこれをAS3に置き換えた方法が沢山あったのですが、試してみて気づいたところがあります。それは送信メソッドがPOSTじゃないと認証されなかったことです。

例えば、以下だとリクエストヘッダで認証できませんでした。Base64エンコーダーはFlex SDKにインストールされているものを使っています。

var encoder:Base64Encoder	= new Base64Encoder();
encoder.encodeUTFBytes( "ユーザー名" : "パスワード" );
 
var header:URLRequestHeader	= new URLRequestHeader("Authorization", "Basic " + encoder.toString() );
var req:URLRequest = new URLRequest( "リクエストURL" );
req.requestHeaders.push( header );
 
navigateToURL( req );

こう書き直したら認証されるようになりました。

var encoder:Base64Encoder	= new Base64Encoder();
encoder.encodeUTFBytes( "ユーザー名" : "パスワード" );
 
var header:URLRequestHeader	= new URLRequestHeader("Authorization", "Basic " + encoder.toString() );
var req:URLRequest = new URLRequest( "リクエストURL" );
req.requestHeaders.push( header );
req.method= URLRequestMethod.POST;
req.data	= {};
navigateToURL( req );

変わっていることは、送信メソッドにPOSTを指定したことと、dataに空オブジェクトを加えたこと。リファレンスによれば、methodを指定してもdataが無いと自動的にGETで処理されるとあり、でダミーの値を入れておく必要があるようだ。ちなみにdataにオブジェクトを入れて、methodをGETにした場合も認証されませんでした。

FileReference

2010 年 2月 25 日 木曜日 kosuke

jpgやpng、mp3などをダウンロードさせたい時、navigateToURLで直接URLを指定するとブラウザのウインドウに表示さる。ブラウザのウインドウに表示せずダウンロードさせるにはFileReferenceが有効だ。

参考:FileReference – ActionScript 3.0 言語およびコンポーネントリファレンス

例:画像ファイルをダウンロードする

This movie requires Flash Player 10.0.0

private function _onMouseDown(e:MouseEvent):void{
	var req:URLRequest	= new URLRequest( "http://blog.nipx.jp/wp-content/uploads/2009/09/ceocle.jpg" );
	var file:FileReference	= new FileReference();
	file.download( req );
}

先日、これを使ってつまづいたところをメモしておく。よく読めばリファレンスに記載があったのだが…。

・認証がある場合、ブラウザからでないとダウンロードできない。
認証がある場合、Flashでパブリッシュした後の画面やスタンドアローンプレイヤーではダウンロードが失敗する。ブラウザで表示している場合、認証の入力画面が表示され認証可能。
仕事の場合、開発用のサーバーには大抵Basic認証がかかっていますがいちいちブラウザで確認するのは手間なので、パブリッシュの画面からダウンロードできないか試してみたのだけどダメっぽい。Basic認証を抜けるにはリクエストヘッダを送る方法が知られているけど、FileReferenceでは追加したリクエストヘッダ自体が無視されるようだ。


リファレンス download()メソッドより引用

サーバーでユーザー認証が必要な場合、ブラウザ内で実行される、つまり、ブラウザプラグインまたは ActiveX コントロールを使用する SWF ファイルでのみ、認証用のユーザー名とパスワードをユーザーが入力できるダイアログボックスを表示できます。〜

〜 URLRequest オブジェクトの requestHeaders プロパティは無視されるため、カスタム HTTP リクエストヘッダはアップロードまたはダウンロードでサポートされません。


FileReferenceを使う場合、ダウンロード状況を表示する演出なんか入れることありそうなんで、開発上ちょっと面倒だなぁと思う。ところでこのFileReference、Player 10以上ではFlashで生成したデータを保存させることが出来る。AirならFileで出来ると知っていたがFlash PlayerでもFileReferenceで保存できるようになっていたんですね。saveのdata引数にはByteArrayが渡せるのでどんなファイル形式でも保存出来るようだ。

例:テキストファイルに保存する

private function _onMouseDown(e:MouseEvent):void{
	var file:FileReference	= new FileReference();
	file.save( "FileReferenceでテキストデータを保存。", "sample.txt" );
}