A reasonable alpha-level video displayer.
When you mouse-over it, the controls appear, which are currently only a pause/play button. When it reaches the end, it autorewinds and pauses. Things: - We may want all videos to start paused. However, since we're not using a streaming media server, there's no way to show the first frame without loading the video unless we screenshot it on the server and then include a secondary media ident, which we don't do. So: as long as the user has to load the FLV, we may as well play it and not make it just look like an image. - We'll probably want to adapt this into a standalone video displayer that can be used to view FLVs using our UI from inside a web page. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4610 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -21,21 +21,33 @@ public class VideoDisplayer extends Sprite
|
|||||||
{
|
{
|
||||||
/** A value event dispatched when the size of the video is known.
|
/** A value event dispatched when the size of the video is known.
|
||||||
* Value: [ width, height ]. */
|
* Value: [ width, height ]. */
|
||||||
public static const SIZE_KNOWN :String = "SIZE_KNOWN";
|
public static const SIZE_KNOWN :String = "videoDisplayerSizeKnown";
|
||||||
|
|
||||||
/** A value event sent when there's an error loading the video.
|
/** A value event sent when there's an error loading the video.
|
||||||
* Value: original error event. */
|
* Value: original error event. */
|
||||||
public static const VIDEO_ERROR :String = "VIDEO_ERROR";
|
public static const VIDEO_ERROR :String = "videoDisplayerError";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a video displayer.
|
||||||
|
*/
|
||||||
public function VideoDisplayer ()
|
public function VideoDisplayer ()
|
||||||
{
|
{
|
||||||
_vid = new Video();
|
_vid = new Video();
|
||||||
addChild(_vid);
|
addChild(_vid);
|
||||||
|
|
||||||
addEventListener(MouseEvent.CLICK, handleClick);
|
addEventListener(MouseEvent.ROLL_OVER, handleRollOver);
|
||||||
|
addEventListener(MouseEvent.ROLL_OUT, handleRollOut);
|
||||||
|
|
||||||
_videoChecker.addEventListener(TimerEvent.TIMER, handleVideoCheck);
|
_videoChecker.addEventListener(TimerEvent.TIMER, handleVideoCheck);
|
||||||
|
|
||||||
|
_pauser = new Sprite();
|
||||||
|
_pauser.addEventListener(MouseEvent.CLICK, handleClick);
|
||||||
|
redrawPauser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start playing a video!
|
||||||
|
*/
|
||||||
public function setup (url :String) :void
|
public function setup (url :String) :void
|
||||||
{
|
{
|
||||||
_netCon = new NetConnection();
|
_netCon = new NetConnection();
|
||||||
@@ -47,15 +59,22 @@ public class VideoDisplayer extends Sprite
|
|||||||
_netCon.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
|
_netCon.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
|
||||||
|
|
||||||
_netCon.connect(null);
|
_netCon.connect(null);
|
||||||
trace("Not waiting, just using!");
|
|
||||||
_netStream = new NetStream(_netCon);
|
_netStream = new NetStream(_netCon);
|
||||||
_netStream.client = this;
|
// pass in refs to some of our protected methods
|
||||||
//_netStream.client = new Callbacker(this);
|
_netStream.client = {
|
||||||
|
onMetaData: metaDataReceived
|
||||||
|
};
|
||||||
|
_netStream.addEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
|
||||||
|
|
||||||
_vid.attachNetStream(_netStream);
|
_vid.attachNetStream(_netStream);
|
||||||
_videoChecker.start();
|
_videoChecker.start();
|
||||||
_netStream.play(url);
|
_netStream.play(url);
|
||||||
|
_paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop playing our video.
|
||||||
|
*/
|
||||||
public function shutdown () :void
|
public function shutdown () :void
|
||||||
{
|
{
|
||||||
_videoChecker.reset();
|
_videoChecker.reset();
|
||||||
@@ -63,6 +82,7 @@ public class VideoDisplayer extends Sprite
|
|||||||
|
|
||||||
if (_netStream != null) {
|
if (_netStream != null) {
|
||||||
_netStream.close();
|
_netStream.close();
|
||||||
|
_netStream.removeEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
|
||||||
_netStream = null;
|
_netStream = null;
|
||||||
}
|
}
|
||||||
if (_netCon != null) {
|
if (_netCon != null) {
|
||||||
@@ -90,15 +110,71 @@ public class VideoDisplayer extends Sprite
|
|||||||
// set up the width/height
|
// set up the width/height
|
||||||
_vid.width = _vid.videoWidth;
|
_vid.width = _vid.videoWidth;
|
||||||
_vid.height = _vid.videoHeight;
|
_vid.height = _vid.videoHeight;
|
||||||
|
redrawPauser();
|
||||||
|
|
||||||
// tell any interested parties
|
// tell any interested parties
|
||||||
dispatchEvent(new ValueEvent(SIZE_KNOWN, [ _vid.videoWidth, _vid.videoHeight ]));
|
dispatchEvent(new ValueEvent(SIZE_KNOWN, [ _vid.videoWidth, _vid.videoHeight ]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function handleRollOver (event :MouseEvent) :void
|
||||||
|
{
|
||||||
|
addChild(_pauser);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleRollOut (event :MouseEvent) :void
|
||||||
|
{
|
||||||
|
removeChild(_pauser);
|
||||||
|
}
|
||||||
|
|
||||||
protected function handleClick (event :MouseEvent) :void
|
protected function handleClick (event :MouseEvent) :void
|
||||||
{
|
{
|
||||||
trace("Click!");
|
if (_paused) {
|
||||||
_netStream.togglePause();
|
_netStream.resume();
|
||||||
|
_paused = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_netStream.pause();
|
||||||
|
_paused = true;
|
||||||
|
}
|
||||||
|
redrawPauser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the pauser sprite, update it's location.
|
||||||
|
* This will become something artistic, etc.
|
||||||
|
*/
|
||||||
|
protected function redrawPauser () :void
|
||||||
|
{
|
||||||
|
with (_pauser.graphics) {
|
||||||
|
clear();
|
||||||
|
// draw a nice circle
|
||||||
|
beginFill(0x333333);
|
||||||
|
drawCircle(0, 0, 20);
|
||||||
|
endFill();
|
||||||
|
lineStyle(2, 0);
|
||||||
|
drawCircle(0, 0, 20);
|
||||||
|
|
||||||
|
if (_paused) {
|
||||||
|
lineStyle(0, 0, 0);
|
||||||
|
beginFill(0x00FF00);
|
||||||
|
moveTo(-4, -10);
|
||||||
|
lineTo(4, 0);
|
||||||
|
lineTo(-4, 10);
|
||||||
|
lineTo(-4, -10);
|
||||||
|
endFill();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lineStyle(2, 0x00FF00);
|
||||||
|
moveTo(-4, -10);
|
||||||
|
lineTo(-4, 10);
|
||||||
|
moveTo(4, -10);
|
||||||
|
lineTo(4, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and update the location
|
||||||
|
_pauser.x = _vid.width/2;
|
||||||
|
_pauser.y = _vid.height/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleNetStatus (event :NetStatusEvent) :void
|
protected function handleNetStatus (event :NetStatusEvent) :void
|
||||||
@@ -112,7 +188,8 @@ public class VideoDisplayer extends Sprite
|
|||||||
// else info.level == "status"
|
// else info.level == "status"
|
||||||
switch (info.code) {
|
switch (info.code) {
|
||||||
case "NetConnection.Connect.Success":
|
case "NetConnection.Connect.Success":
|
||||||
trace("Connected!");
|
case "NetConnection.Connect.Closed":
|
||||||
|
// these status events we ignore
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -121,6 +198,16 @@ public class VideoDisplayer extends Sprite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function handleStreamNetStatus (event :NetStatusEvent) :void
|
||||||
|
{
|
||||||
|
if (event.info.code == "NetStream.Play.Stop") {
|
||||||
|
_netStream.seek(0);
|
||||||
|
_netStream.pause();
|
||||||
|
_paused = true;
|
||||||
|
redrawPauser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function handleAsyncError (event :AsyncErrorEvent) :void
|
protected function handleAsyncError (event :AsyncErrorEvent) :void
|
||||||
{
|
{
|
||||||
trace("AsyncError: " + event);
|
trace("AsyncError: " + event);
|
||||||
@@ -139,31 +226,35 @@ public class VideoDisplayer extends Sprite
|
|||||||
redispatchError(event);
|
redispatchError(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redispatch some error we received to our listeners.
|
||||||
|
*/
|
||||||
protected function redispatchError (event :Event) :void
|
protected function redispatchError (event :Event) :void
|
||||||
{
|
{
|
||||||
dispatchEvent(new ValueEvent(VIDEO_ERROR, event));
|
dispatchEvent(new ValueEvent(VIDEO_ERROR, event));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function callbackCalled (name :String, args :Array) :*
|
/**
|
||||||
|
* Called when metadata (if any) is found in the video stream.
|
||||||
|
*/
|
||||||
|
protected function metaDataReceived (obj :Object) :void
|
||||||
{
|
{
|
||||||
trace("Callback: " + name + ", args: " + args);
|
trace("Got video metadata:");
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onMetaData (obj :Object) :void
|
|
||||||
{
|
|
||||||
trace("Got metadata..");
|
|
||||||
for (var n :String in obj) {
|
for (var n :String in obj) {
|
||||||
trace("name " + n + " -> " + obj[n]);
|
trace(" " + n + ": " + obj[n]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected var _vid :Video;
|
protected var _vid :Video;
|
||||||
|
|
||||||
|
protected var _paused :Boolean = false;
|
||||||
|
|
||||||
protected var _netCon :NetConnection;
|
protected var _netCon :NetConnection;
|
||||||
|
|
||||||
protected var _netStream :NetStream;
|
protected var _netStream :NetStream;
|
||||||
|
|
||||||
|
protected var _pauser :Sprite;
|
||||||
|
|
||||||
/** Checks the video every 100ms to see if the dimensions are now know.
|
/** 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
|
* 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,
|
* we may not know the dimensions unless we're added on the display list,
|
||||||
@@ -171,55 +262,3 @@ public class VideoDisplayer extends Sprite
|
|||||||
protected var _videoChecker :Timer = new Timer(100);
|
protected var _videoChecker :Timer = new Timer(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
import flash.utils.Proxy;
|
|
||||||
|
|
||||||
import flash.utils.flash_proxy;
|
|
||||||
|
|
||||||
import com.threerings.util.VideoDisplayer;
|
|
||||||
|
|
||||||
use namespace flash_proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
internal class Callbacker extends Proxy
|
|
||||||
{
|
|
||||||
public function Callbacker (displayer :VideoDisplayer)
|
|
||||||
{
|
|
||||||
_displayer = displayer;
|
|
||||||
|
|
||||||
_props
|
|
||||||
}
|
|
||||||
|
|
||||||
override flash_proxy function hasProperty (name :*) :Boolean
|
|
||||||
{
|
|
||||||
return (_props[name] !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
override flash_proxy function getProperty (name :*) :*
|
|
||||||
{
|
|
||||||
trace("Damn thing asked about '" + name + "'.");
|
|
||||||
return _props[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
override flash_proxy function setProperty (name :*, value :*) :void
|
|
||||||
{
|
|
||||||
trace("Damn thing tried to set '" + name + "' to '" + value + "'.");
|
|
||||||
_props[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
override flash_proxy function deleteProperty (name :*) :Boolean
|
|
||||||
{
|
|
||||||
trace("Damn thing tried to delete '" + name + "'.");
|
|
||||||
return (delete _props[name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
override flash_proxy function callProperty (name :*, ... args) :*
|
|
||||||
{
|
|
||||||
return _displayer.callbackCalled(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected var _props :Object = {};
|
|
||||||
|
|
||||||
protected var _displayer :VideoDisplayer;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user