New video stuffs.
- VideoPlayer is the interface to the video backend. - Adapted my old VideoDisplayer into a FlvVideoPlayer and the SimpleVideoDisplay frontend. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@680 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.flash.video {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import flash.events.AsyncErrorEvent;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.ErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.EventDispatcher;
|
||||
import flash.events.NetStatusEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.events.TimerEvent;
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import flash.media.Video;
|
||||
|
||||
import flash.net.NetConnection;
|
||||
import flash.net.NetStream;
|
||||
|
||||
import flash.utils.Timer;
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.ValueEvent;
|
||||
|
||||
public class FlvVideoPlayer extends EventDispatcher
|
||||
implements VideoPlayer
|
||||
{
|
||||
public function FlvVideoPlayer ()
|
||||
{
|
||||
_videoChecker = new Timer(100);
|
||||
_videoChecker.addEventListener(TimerEvent.TIMER, handleVideoCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start playing a video!
|
||||
*/
|
||||
public function load (url :String) :void
|
||||
{
|
||||
_netCon = new NetConnection();
|
||||
_netCon.addEventListener(NetStatusEvent.NET_STATUS, handleNetStatus);
|
||||
|
||||
// error handlers
|
||||
_netCon.addEventListener(AsyncErrorEvent.ASYNC_ERROR, handleAsyncError);
|
||||
_netCon.addEventListener(IOErrorEvent.IO_ERROR, handleIOError);
|
||||
_netCon.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
|
||||
|
||||
_netCon.connect(null);
|
||||
_netStream = new NetStream(_netCon);
|
||||
// pass in refs to some of our protected methods
|
||||
_netStream.client = {
|
||||
onMetaData: handleMetaData
|
||||
};
|
||||
_netStream.addEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
|
||||
|
||||
_vid.attachNetStream(_netStream);
|
||||
_videoChecker.start();
|
||||
|
||||
_netStream.play(url);
|
||||
_netStream.pause(); // TODO Does this work?
|
||||
updateState(VideoPlayerCodes.STATE_READY);
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function getDisplay () :DisplayObject
|
||||
{
|
||||
return _vid;
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function getState () :int
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function getSize () :Point
|
||||
{
|
||||
return _size.clone();
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function play () :void
|
||||
{
|
||||
if (_netStream != null) {
|
||||
_netStream.resume();
|
||||
updateState(VideoPlayerCodes.STATE_PLAYING);
|
||||
}
|
||||
// TODO: throw an error if _netStream == null??
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function pause () :void
|
||||
{
|
||||
if (_netStream != null) {
|
||||
_netStream.pause();
|
||||
updateState(VideoPlayerCodes.STATE_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function getDuration () :Number
|
||||
{
|
||||
return NaN; // TODO
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function getPosition () :Number
|
||||
{
|
||||
return NaN; // TODO
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function seek (position :Number) :void
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function getVolume () :Number
|
||||
{
|
||||
return 1; // TODO
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function setVolume (volume :Number) :void
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// from VideoPlayer
|
||||
public function unload () :void
|
||||
{
|
||||
_videoChecker.reset();
|
||||
_vid.attachNetStream(null);
|
||||
|
||||
if (_netStream != null) {
|
||||
_netStream.close();
|
||||
_netStream.removeEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
|
||||
_netStream = null;
|
||||
}
|
||||
if (_netCon != null) {
|
||||
_netCon.close();
|
||||
_netCon.removeEventListener(NetStatusEvent.NET_STATUS, handleNetStatus);
|
||||
_netCon.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, handleAsyncError);
|
||||
_netCon.removeEventListener(IOErrorEvent.IO_ERROR, handleIOError);
|
||||
_netCon.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
|
||||
_netCon = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we now know the dimensions of the video.
|
||||
*/
|
||||
protected function handleVideoCheck (event :TimerEvent) :void
|
||||
{
|
||||
if (_vid.videoWidth == 0 || _vid.videoHeight == 0) {
|
||||
return; // not known yet!
|
||||
}
|
||||
|
||||
// stop the checker timer
|
||||
_videoChecker.stop();
|
||||
_size = new Point(_vid.videoWidth, _vid.videoHeight);
|
||||
|
||||
// set up the width/height
|
||||
_vid.width = _size.x;
|
||||
_vid.height = _size.y;
|
||||
|
||||
log.info("=================> size known: " + _size);
|
||||
dispatchEvent(new ValueEvent(VideoPlayerCodes.SIZE, _size.clone()));
|
||||
}
|
||||
|
||||
protected function handleNetStatus (event :NetStatusEvent) :void
|
||||
{
|
||||
var info :Object = event.info;
|
||||
if ("error" == info.level) {
|
||||
log.warning("NetStatus error: " + info.code);
|
||||
dispatchEvent(new ValueEvent(VideoPlayerCodes.ERROR, info.code));
|
||||
return;
|
||||
}
|
||||
// else info.level == "status"
|
||||
switch (info.code) {
|
||||
case "NetConnection.Connect.Success":
|
||||
case "NetConnection.Connect.Closed":
|
||||
// these status events we ignore
|
||||
break;
|
||||
|
||||
default:
|
||||
log.info("NetStatus status: " + info.code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleStreamNetStatus (event :NetStatusEvent) :void
|
||||
{
|
||||
if (event.info.code == "NetStream.Play.Stop") {
|
||||
_netStream.seek(0);
|
||||
_netStream.pause();
|
||||
updateState(VideoPlayerCodes.STATE_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleAsyncError (event :AsyncErrorEvent) :void
|
||||
{
|
||||
redispatchError(event);
|
||||
}
|
||||
|
||||
protected function handleIOError (event :IOErrorEvent) :void
|
||||
{
|
||||
redispatchError(event);
|
||||
}
|
||||
|
||||
protected function handleSecurityError (event :SecurityErrorEvent) :void
|
||||
{
|
||||
redispatchError(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redispatch some error we received to our listeners.
|
||||
*/
|
||||
protected function redispatchError (event :ErrorEvent) :void
|
||||
{
|
||||
log.warning("got video error: " + event);
|
||||
dispatchEvent(new ValueEvent(VideoPlayerCodes.ERROR, event.text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when metadata (if any) is found in the video stream.
|
||||
*/
|
||||
protected function handleMetaData (info :Object) :void
|
||||
{
|
||||
log.info("Got video metadata:");
|
||||
for (var n :String in info) {
|
||||
log.info(" " + n + ": " + info[n]);
|
||||
}
|
||||
|
||||
// TODO: total duration is info.duration
|
||||
}
|
||||
|
||||
protected function updateState (newState :int) :void
|
||||
{
|
||||
_state = newState;
|
||||
dispatchEvent(new ValueEvent(VideoPlayerCodes.STATE, newState));
|
||||
}
|
||||
|
||||
protected const log :Log = Log.getLog(this);
|
||||
|
||||
protected var _vid :Video = new Video();
|
||||
|
||||
protected var _netCon :NetConnection;
|
||||
|
||||
protected var _netStream :NetStream;
|
||||
|
||||
protected var _state :int = VideoPlayerCodes.STATE_UNREADY;
|
||||
|
||||
/** Our size, null until known. */
|
||||
protected var _size :Point;
|
||||
|
||||
/** Checks the video every 100ms to see if the dimensions are now know.
|
||||
* Yes, this is how to do it. We could trigger on ENTER_FRAME, but then
|
||||
* we may not know the dimensions unless we're added on the display list,
|
||||
* and we want this to work in the general case. */
|
||||
protected var _videoChecker :Timer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// $Id#
|
||||
|
||||
package com.threerings.flash.video {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.Graphics;
|
||||
import flash.display.Shape;
|
||||
import flash.display.Sprite;
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.ValueEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @eventType com.threerings.flash.video.VideoPlayerCodes.SIZE
|
||||
*/
|
||||
[Event(name="size", type="com.threerings.util.ValueEvent")]
|
||||
|
||||
/**
|
||||
* A simple video display.
|
||||
*/
|
||||
public class SimpleVideoDisplay extends Sprite
|
||||
{
|
||||
public static const NATIVE_WIDTH :int = 320;
|
||||
|
||||
public static const NATIVE_HEIGHT :int = 240;
|
||||
|
||||
/**
|
||||
* Create a video displayer.
|
||||
*/
|
||||
public function SimpleVideoDisplay (player :VideoPlayer)
|
||||
{
|
||||
_player = player;
|
||||
_player.addEventListener(VideoPlayerCodes.STATE, handlePlayerState);
|
||||
_player.addEventListener(VideoPlayerCodes.SIZE, handlePlayerSize);
|
||||
_player.addEventListener(VideoPlayerCodes.ERROR, handlePlayerError);
|
||||
|
||||
addChild(_player.getDisplay());
|
||||
|
||||
_mask = new Shape();
|
||||
this.mask = _mask;
|
||||
addChild(_mask);
|
||||
|
||||
addEventListener(MouseEvent.ROLL_OVER, handleRollOver);
|
||||
addEventListener(MouseEvent.ROLL_OUT, handleRollOut);
|
||||
|
||||
_hud = new Sprite();
|
||||
_hud.addEventListener(MouseEvent.CLICK, handleClick);
|
||||
redrawHUD();
|
||||
|
||||
// create the mask...
|
||||
var g :Graphics = _mask.graphics;
|
||||
g.clear();
|
||||
g.beginFill(0xFFFFFF);
|
||||
g.drawRect(0, 0, NATIVE_WIDTH, NATIVE_HEIGHT);
|
||||
g.endFill();
|
||||
|
||||
g = this.graphics;
|
||||
g.beginFill(0xFF000000);
|
||||
g.drawRect(0, 0, NATIVE_WIDTH, NATIVE_HEIGHT);
|
||||
g.endFill();
|
||||
|
||||
// and update the HUD location (even if not currently showing)
|
||||
_hud.x = NATIVE_WIDTH/2;
|
||||
_hud.y = NATIVE_HEIGHT/2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing our video.
|
||||
*/
|
||||
public function unload () :void
|
||||
{
|
||||
_player.unload();
|
||||
}
|
||||
|
||||
protected function handleRollOver (event :MouseEvent) :void
|
||||
{
|
||||
showHUD(true);
|
||||
}
|
||||
|
||||
protected function handleRollOut (event :MouseEvent) :void
|
||||
{
|
||||
showHUD(false);
|
||||
}
|
||||
|
||||
protected function handleClick (event :MouseEvent) :void
|
||||
{
|
||||
// the buck stops here!
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
switch (_player.getState()) {
|
||||
case VideoPlayerCodes.STATE_READY:
|
||||
case VideoPlayerCodes.STATE_PAUSED:
|
||||
_player.play();
|
||||
break;
|
||||
|
||||
case VideoPlayerCodes.STATE_PLAYING:
|
||||
_player.pause();
|
||||
break;
|
||||
|
||||
default:
|
||||
log.info("Click while player in unhandled state: " + _player.getState());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function handlePlayerState (event :ValueEvent) :void
|
||||
{
|
||||
redrawHUD();
|
||||
}
|
||||
|
||||
protected function handlePlayerSize (event :ValueEvent) :void
|
||||
{
|
||||
const size :Point = Point(event.value);
|
||||
|
||||
const disp :DisplayObject = _player.getDisplay();
|
||||
// TODO: siggggghhhhh
|
||||
// disp.scaleX = NATIVE_WIDTH / size.x;
|
||||
// disp.scaleY = NATIVE_HEIGHT / size.y;
|
||||
disp.width = NATIVE_WIDTH;
|
||||
disp.height = NATIVE_HEIGHT;
|
||||
|
||||
// and, we redispatch
|
||||
dispatchEvent(event);
|
||||
}
|
||||
|
||||
protected function handlePlayerError (event :ValueEvent) :void
|
||||
{
|
||||
// TODO.. maybe just redispatch
|
||||
log.warning("player error: " + event.value);
|
||||
}
|
||||
|
||||
protected function showHUD (show :Boolean) :void
|
||||
{
|
||||
if (show == (_hud.parent == null)) {
|
||||
if (show) {
|
||||
addChild(_hud);
|
||||
} else {
|
||||
removeChild(_hud);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function redrawHUD () :void
|
||||
{
|
||||
const state :int = _player.getState();
|
||||
|
||||
const g :Graphics = _hud.graphics;
|
||||
g.clear();
|
||||
|
||||
if (state == VideoPlayerCodes.STATE_READY ||
|
||||
state == VideoPlayerCodes.STATE_PLAYING ||
|
||||
state == VideoPlayerCodes.STATE_PAUSED) {
|
||||
// draw a nice circle
|
||||
g.beginFill(0x333333);
|
||||
g.drawCircle(0, 0, 20);
|
||||
g.endFill();
|
||||
g.lineStyle(2, 0xFFFFFF);
|
||||
g.drawCircle(0, 0, 20);
|
||||
|
||||
if (state == VideoPlayerCodes.STATE_PLAYING) {
|
||||
g.lineStyle(2, 0x00FF00);
|
||||
g.moveTo(-4, -10);
|
||||
g.lineTo(-4, 10);
|
||||
g.moveTo(4, -10);
|
||||
g.lineTo(4, 10);
|
||||
|
||||
} else {
|
||||
g.lineStyle(0, 0, 0);
|
||||
g.beginFill(0x00FF00);
|
||||
g.moveTo(-4, -10);
|
||||
g.lineTo(4, 0);
|
||||
g.lineTo(-4, 10);
|
||||
g.lineTo(-4, -10);
|
||||
g.endFill();
|
||||
}
|
||||
|
||||
} else {
|
||||
// draw something loading-like
|
||||
g.beginFill(0x000033);
|
||||
g.drawCircle(5, 5, 10);
|
||||
g.drawCircle(-5, 5, 10);
|
||||
g.drawCircle(-5, -5, 10);
|
||||
g.drawCircle(5, -5, 10);
|
||||
g.endFill();
|
||||
}
|
||||
}
|
||||
|
||||
protected const log :Log = Log.getLog(this);
|
||||
|
||||
protected var _player :VideoPlayer;
|
||||
|
||||
protected var _hud :Sprite;
|
||||
|
||||
/** Our mask, also defines our boundaries for clicking. */
|
||||
protected var _mask :Shape;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.flash.video {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
import flash.events.IEventDispatcher;
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
import com.threerings.util.ValueEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @eventType com.threerings.flash.video.VideoPlayerCodes.STATE
|
||||
*/
|
||||
[Event(name="state", type="com.threerings.util.ValueEvent")]
|
||||
|
||||
/**
|
||||
*
|
||||
* @eventType com.threerings.flash.video.VideoPlayerCodes.DURATION
|
||||
*/
|
||||
[Event(name="duration", type="com.threerings.util.ValueEvent")]
|
||||
|
||||
/**
|
||||
*
|
||||
* @eventType com.threerings.flash.video.VideoPlayerCodes.POSITION
|
||||
*/
|
||||
[Event(name="position", type="com.threerings.util.ValueEvent")]
|
||||
|
||||
/**
|
||||
*
|
||||
* @eventType com.threerings.flash.video.VideoPlayerCodes.SIZE
|
||||
*/
|
||||
[Event(name="size", type="com.threerings.util.ValueEvent")]
|
||||
|
||||
/**
|
||||
*
|
||||
* @eventType com.threerings.flash.video.VideoPlayerCodes.ERROR
|
||||
*/
|
||||
[Event(name="error", type="com.threerings.util.ValueEvent")]
|
||||
|
||||
/**
|
||||
* Implemented by video playing backends.
|
||||
*/
|
||||
public interface VideoPlayer extends IEventDispatcher
|
||||
{
|
||||
/**
|
||||
* Get the actual visualization of the video.
|
||||
*/
|
||||
function getDisplay () :DisplayObject;
|
||||
|
||||
/**
|
||||
* @return a VideoPlayerCodes state constant.
|
||||
*/
|
||||
function getState () :int;
|
||||
|
||||
/**
|
||||
* Get the size of the video, or null if not yet known.
|
||||
*/
|
||||
function getSize () :Point;
|
||||
|
||||
function play () :void;
|
||||
|
||||
function pause () :void;
|
||||
|
||||
/**
|
||||
* Get the duration of the video, or NaN if not yet known.
|
||||
*/
|
||||
function getDuration () :Number;
|
||||
|
||||
/**
|
||||
* Get the position of the video, or NaN if not yet ready.
|
||||
*/
|
||||
function getPosition () :Number;
|
||||
|
||||
/**
|
||||
* Seek to the specified position.
|
||||
*/
|
||||
function seek (position :Number) :void;
|
||||
|
||||
/**
|
||||
* Set the volume, between 0-1.
|
||||
*/
|
||||
function setVolume (volume :Number) :void;
|
||||
|
||||
/**
|
||||
* Get the volume, from 0 to 1.
|
||||
*/
|
||||
function getVolume () :Number;
|
||||
|
||||
/**
|
||||
* Unload the video.
|
||||
*/
|
||||
function unload () :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.flash.video {
|
||||
|
||||
public class VideoPlayerCodes
|
||||
{
|
||||
public static const STATE :String = "state";
|
||||
|
||||
public static const DURATION :String = "duration";
|
||||
|
||||
public static const POSITION :String = "position";
|
||||
|
||||
public static const SIZE :String = "size";
|
||||
|
||||
public static const ERROR :String = "error";
|
||||
|
||||
/** Indicates we're still loading or initializing. */
|
||||
public static const STATE_UNREADY :int = 0;
|
||||
|
||||
/** Indicates we're ready to play. */
|
||||
public static const STATE_READY :int = 1;
|
||||
|
||||
public static const STATE_PLAYING :int = 2;
|
||||
|
||||
public static const STATE_PAUSED :int = 3;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user