FLV プレイヤーを書いてみた
_人人人人人人人人人人人人人人人人人人人人_ > あんたたち!サンプルよ!サンプルよ!!! <  ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄ _,,,, --──-- ,,,__ , '´ __ `ヽ、,ヘ .くヽ_r'_ヽ 、 ,、_) ヽ ,______r'´イ´ ['、イ_,-イ、ゝ,_, ,イ_,-,_ゝヽ、__〉, r、 r 、 ,! 、!-|ーi、λ_L!」/_-i、|〉',ヽイ / L > ヽ i_ノL.イ (ヒ_] ヒ_ン ).!_イ | | /つ ) ( と ト、 ヽ! |.i"" ,___, "" | ! | |/ "'''ーく ミ 〈 ⌒ \.| ! ',. ヽ _ン .,! ! .| | 〉 (⌒ヽ彡ノノ | |ヽ、 イノi .| .| ミ ミ ̄フ⌒つ (と | 彡 | | .| ` ー--─ ´/ /入、 | ミ / ノ
のっけからごめんなさい。FLV を動的にロードして再生するサンプル一式です。Flex 3 の SDK さえあればコンパイルできますが、ローカルで試す場合は適宜設定が必要です。『信頼されているローカル SWF ファイル』とかで検索して調べて下さい。
まずはファイルの構成から。
- mainApp.mxml // ビューに当たる部分
- AppConfig.as // 各種コンフィグ
- VideoPlayer.as // 各コンポーネントのコントローラに当たる部分
- mycomp(ディレクトリ)// カスタムコンポーネントを含むディレクトリ
- MyHSlider.as // HSldier の拡張
- Screen.as // FLV を再生するためのスクリーン
- ScreenEvent.as // カスタムイベントクラス
- StringUtil.as // String クラスの操作用
では、各ファイルのソースを載せていきます。長いから続きを使います。
mainApp.mxml
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:comp="mycomp.*" initialize="init()"> <mx:Script source="VideoPlayer.as"/> <mx:Canvas id="baseCanvas"> <comp:Screen id="s" x="0" y="0" width="320" height="240"/> <mx:Label id="timer" x="0" y="250"/> <mx:Button id="playButton" x="0" y="280" toggle="true" label="{AppConfig.pauseLabel}"/> <mx:Button id="roopButton" x="250" y="280" toggle="true" label="{AppConfig.offLabel}"/> <comp:MyHSlider id="seekBar" x="80" y="280" liveDragging="true" minimum="0" maximum="100" width="120"/> <mx:VSlider id="volumeBar" x="220" y="240" liveDragging="true" minimum="0" maximum="1" height="50" value="1"/> </mx:Canvas> </mx:Application>
ここはビューなので、配置等は適宜変更して下さい。
AppConfig.as
package { public class AppConfig { public static const defaultLoadPath:String = "test.flv"; // MyHSlider のスキン設定用 public static const slideBarImagePath:String = "img/slideBar.png"; public static const slideThumbImagePath:String = "img/slideBar.png"; // VideoPlayer の再生ボタン public static const playLabel:String = "Play"; public static const pauseLabel:String = "Pause"; // 同、ループボタン public static const onLabel:String = "Loop On"; public static const offLabel:String = "Loop Off"; } }
見ての通り、コンフィグ値を定数にしているだけです。パッケージも無名で十分。
VideoPlayer.as
import flash.events.*; import mx.containers.*; import mx.controls.*; import mx.core.*; import mycomp.*; private var path:String; private function init():void { // フレームごとに実行するイベント this.addEventListener(Event.ENTER_FRAME, enterFrameHandler); // HTML から FLV のパスを取得 if(Application.application.parameters.path) { path = Application.application.parameters.path; }else { path = AppConfig.defaultLoadPath; } // ビデオを標示するスクリーンの初期化 s.load(path); s.addEventListener(ScreenEvent.PLAY_COMPLETE, completeHandler); // 再生・一時停止の切り替えボタンの初期化 playButton.addEventListener(MouseEvent.CLICK, playStatusHandler); // ループ再生の切り替えボタンの初期化 roopButton.addEventListener(MouseEvent.CLICK, roopStatusHandler); // シークバーの初期化 seekBar.addEventListener(Event.CHANGE, seekBarHandler); seekBar.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); seekBar.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler); // ボリュームコントロールの初期化 volumeBar.addEventListener(Event.CHANGE, volumeHandler); } private function enterFrameHandler(e:Event):void { seekBar.drawLoaded(s.getLoadedBytes() / s.getTotalBytes()); setTimer(); if(playButton.selected == false) { seekBar.value = s.getOffset() / s.getPlayTime() * 100; } } private function playStatusHandler(e:MouseEvent):void { s.togglePause(); playButton.label = playButton.selected ? AppConfig.playLabel : AppConfig.pauseLabel; } // シークバーの位置にあわせてシークする private function seekBarHandler(e:Event):void { // 最大値になったらほんの少し前に戻しておく if(seekBar.value >= seekBar.maximum) { var val:Number = Math.floor(seekBar.maximum * 0.99); seekBar.value = val; s.seek(s.getPlayTime() * val); // ロードされていない部分になったらロードされた終端に戻しておく }else if(seekBar.value >= (seekBar.maximum * s.getLoadedBytes() / s.getTotalBytes())) { s.seek(seekBar.maximum * s.getLoadedBytes() / s.getTotalBytes()); }else { s.seek(s.getPlayTime() * seekBar.value / seekBar.maximum); } setTimer(); } private function mouseDownHandler(e:MouseEvent):void { s.pause(); } private function mouseUpHandler(e:MouseEvent):void { if(playButton.selected == false) { s.togglePause(); } } // ボリュームの調節 private function volumeHandler(e:Event):void { s.setVolume(volumeBar.value); } // 再生終了時 private function completeHandler(e:ScreenEvent):void { s.pause(); s.seek(0); seekBar.value = 0; setTimer(); // ループボタンの状況により、ループするかどうか設定 if(roopButton.selected == true) { s.play(); playButton.selected = false; playButton.label = AppConfig.pauseLabel; }else { playButton.selected = true; playButton.label = AppConfig.playLabel; } } private function roopStatusHandler(e:MouseEvent):void { roopButton.label = roopButton.selected ? AppConfig.onLabel : AppConfig.offLabel; } // タイマーの値をセットする private function setTimer():void { timer.text = StringUtil.toTime(s.getOffset()) + "/" + StringUtil.toTime(s.getPlayTime()); }
配置されたコントローラにイベントなどを設定していきます。一部カスタムコンポーネントのメソッドを呼び出したりしている部分がありますが、そこは個々のコンポーネントを確認して下さい。
MyHSlider.as
package mycomp { import mx.controls.*; public class MyHSlider extends HSlider { public function MyHSlider() { super(); } // どこまで再生が終わったか、見た目で分かるよう表示する public function drawLoaded(percentage:Number):void { if(percentage < 1) { this.graphics.clear(); this.graphics.beginFill(0xFF0000); this.graphics.drawRoundRect(7, 10, this.width * 0.9 * percentage, 3, 1); } } } }
HSlider を拡張して、どこまでロードが終わったかを表示するバーをつけてみました。Youtube とかニコニコでおなじみのあれです。
Screen.as
package mycomp { import flash.events.*; import flash.media.*; import flash.net.*; import flash.utils.*; import mx.core.*; public class Screen extends UIComponent { private var path:String; private var con:NetConnection; private var stream:NetStream; private var video:Video; private var playTime:Number; public function Screen(){ super(); } public function load(path:String):void { this.path = path; con = new NetConnection(); con.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); con.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); con.connect(null); } private function securityErrorHandler(e:SecurityErrorEvent):void { trace("securityErrorHandler: " + e); } private function netStatusHandler(e:NetStatusEvent):void { switch (e.info.code) { case "NetConnection.Connect.Success": connectStream(); break; case "NetStream.Play.StreamNotFound": trace("Unable to locate video: " + this.path); break; } } private function connectStream():void { stream = new NetStream(con); // メタデータの取得用 stream.client = this; // ストリームの状態をイベントで監視 stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); stream.addEventListener(NetStatusEvent.NET_STATUS, nsStatusHandler); // ビデオを定義し、ストリームをアタッチ video = new Video(); video.attachNetStream(stream); stream.play(path); addChild(video); } private function asyncErrorHandler(e:AsyncErrorEvent):void { // ignore AsyncErrorEvent events. } private function nsStatusHandler(e:NetStatusEvent):void { switch(e.info.code) { case "NetStream.Play.Stop": dispatchEvent(new ScreenEvent(ScreenEvent.PLAY_COMPLETE)); break; // もしエンドがとれない flv を使わざるを得ない場合 // 上の case 節をコメントアウトし // ここと checkTime メソッドのコメントを外す /* case "NetStream.Buffer.Empty": checkTime(); break; */ } } /* private function checkTime():void { if(playTime && stream.time >= Math.floor(playTime)) { dispatchEvent(new ScreenEvent(ScreenEvent.PLAY_COMPLETE)); } } */ // ビデオのメタデータを取得 public function onMetaData(param:Object):void { playTime = param.duration; } // 総再生時間を返す public function getPlayTime():Number { if(playTime) { return playTime; }else { return 0; } } // 現在の再生時間を返す public function getOffset():Number { if(stream != null) { return stream.time; }else { return 0; } } // ロード済みのバイト数を返す public function getLoadedBytes():Number { if(stream != null) { return stream.bytesLoaded; }else { return 0; } } // 総バイト数を返す public function getTotalBytes():Number { if(stream != null) { return stream.bytesTotal; }else { return 1; } } // 再生・一時停止の切り替え public function togglePause():void { stream.togglePause(); } // 再生 public function play():void { stream.play(path); } // 一時停止 public function pause():void { stream.pause(); } // 再生位置のシーク public function seek(offset:Number):void { stream.seek(offset); } // ボリュームの調整 public function setVolume(value:Number):void { var transform:SoundTransform = new SoundTransform(); transform.volume = value; stream.soundTransform = transform; } } }
これが一番の肝になるコンポーネントです。再生終了の判定がうまく取れない FLV もあるとかないとか聞いたので、そうした FLV があるようならコメントアウトした部分を読んで下さい。
ScreenEvent.as
package mycomp { import flash.events.*; public class ScreenEvent extends Event { public static const PLAY_COMPLETE:String = "play_complete"; public function ScreenEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); } public override function clone():Event { return new ScreenEvent(type, bubbles, cancelable); } } }
再生終了時のカスタムイベントです。カスタムイベントを使うときはどこでイベントを発生させて、どこで受け取るのかに注意して下さい。
StringUtil.as
package mycomp { public class StringUtil { // 桁そろえを行う public static function figureFormat(obj:Object, figure:int):String { var str:String = obj.toString(); if(str.length < figure) { for(var i:int = str.length; i < figure; i ++) { str = "0" + str; } } return str; } // 与えられた数字を秒数とみなし、時分秒の形式にフォーマットする public static function toTime(seconds:Number):String { var h:int = seconds / 3600; var m:int = (seconds - h * 60) / 60; var s:int = seconds % 60; return h + ":" + StringUtil.figureFormat(m, 2) + ":" + StringUtil.figureFormat(s, 2); } } }
とても簡易的ですが、時刻表示に使うメソッドをまとめたものです。
使い方
html から FLV のパスを渡す形式になっているので、以下を参考に path という変数を渡してみて下さい。
<html> <head> <title>FLV Player</title> </head> <body> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="myflash" width="100%" height="100%" codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"> <param name="movie" value="mainApp.swf" /> <param name="quality" value="high" /> <param name="bgcolor" value="#869ca7" /> <param name="allowScriptAccess" value="sameDomain" /> <param name="flashvars" value='path=test.flv'> <embed src="mainApp.swf?path=test.flv" quality="high" bgcolor="#869ca7" flashvars='path=test.flv' width="100%" height="100%" name="myflash" align="middle" play="true" loop="false" quality="high" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer"> </embed> </object> </body> </html>
なお、パスが渡されていない場合は AppConfig.as で設定したデフォルトのパス(test.flv)を読みにいきます。swf を直接叩いてテストするなら、同名のファイルを用意して下さい(ニコニコ動画あたりから落とすのが手っ取り早いです)。
大体以上で完了。ここ、頭悪いとか指摘がありましたらご教授頂けると幸いです。これが、サンプルだ!