Checkpoint: seeking, tweaking... no peeking!

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@684 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Ray Greenwell
2008-11-04 03:52:34 +00:00
parent cda3d6f94e
commit 1e33a90f14
2 changed files with 180 additions and 42 deletions
@@ -16,6 +16,7 @@ import flash.events.TimerEvent;
import flash.geom.Point;
import flash.media.SoundTransform;
import flash.media.Video;
import flash.net.NetConnection;
@@ -31,8 +32,11 @@ public class FlvVideoPlayer extends EventDispatcher
{
public function FlvVideoPlayer ()
{
_videoChecker = new Timer(100);
_videoChecker.addEventListener(TimerEvent.TIMER, handleVideoCheck);
_sizeChecker = new Timer(100);
_sizeChecker.addEventListener(TimerEvent.TIMER, handleSizeCheck);
_positionChecker = new Timer(250);
_positionChecker.addEventListener(TimerEvent.TIMER, handlePositionCheck);
}
/**
@@ -54,10 +58,11 @@ public class FlvVideoPlayer extends EventDispatcher
_netStream.client = {
onMetaData: handleMetaData
};
_netStream.soundTransform = new SoundTransform(_volume);
_netStream.addEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
_vid.attachNetStream(_netStream);
_videoChecker.start();
_sizeChecker.start();
_netStream.play(url);
_netStream.pause(); // TODO Does this work?
@@ -104,37 +109,45 @@ public class FlvVideoPlayer extends EventDispatcher
// from VideoPlayer
public function getDuration () :Number
{
return NaN; // TODO
return _duration;
}
// from VideoPlayer
public function getPosition () :Number
{
return NaN; // TODO
if (_netStream != null) {
return _netStream.time;
}
return NaN;
}
// from VideoPlayer
public function seek (position :Number) :void
{
// TODO
if (_netStream != null) {
_netStream.seek(position);
}
}
// from VideoPlayer
public function getVolume () :Number
{
return 1; // TODO
return _volume;
}
// from VideoPlayer
public function setVolume (volume :Number) :void
{
// TODO
_volume = Math.max(0, Math.min(1, volume));
if (_netStream != null) {
_netStream.soundTransform = new SoundTransform(_volume);
}
}
// from VideoPlayer
public function unload () :void
{
_videoChecker.reset();
_sizeChecker.reset();
_vid.attachNetStream(null);
if (_netStream != null) {
@@ -155,24 +168,29 @@ public class FlvVideoPlayer extends EventDispatcher
/**
* Check to see if we now know the dimensions of the video.
*/
protected function handleVideoCheck (event :TimerEvent) :void
protected function handleSizeCheck (event :TimerEvent) :void
{
if (_vid.videoWidth == 0 || _vid.videoHeight == 0) {
return; // not known yet!
}
// stop the checker timer
_videoChecker.stop();
_sizeChecker.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);
log.debug("====> size known: " + _size);
dispatchEvent(new ValueEvent(VideoPlayerCodes.SIZE, _size.clone()));
}
protected function handlePositionCheck (event :TimerEvent) :void
{
dispatchEvent(new ValueEvent(VideoPlayerCodes.POSITION, getPosition()));
}
protected function handleNetStatus (event :NetStatusEvent) :void
{
var info :Object = event.info;
@@ -196,10 +214,19 @@ public class FlvVideoPlayer extends EventDispatcher
protected function handleStreamNetStatus (event :NetStatusEvent) :void
{
if (event.info.code == "NetStream.Play.Stop") {
log.debug("NetStatus", "level", event.info.level, "code", event.info.code);
switch (event.info.code) {
case "NetStream.Play.Stop":
// rewind to the beginning
_netStream.seek(0);
_netStream.pause();
updateState(VideoPlayerCodes.STATE_PAUSED);
break;
case "NetStream.Seek.Notify":
handlePositionCheck(null);
break;
}
}
@@ -238,12 +265,31 @@ public class FlvVideoPlayer extends EventDispatcher
}
// TODO: total duration is info.duration
if ("duration" in info) {
_duration = Number(info.duration);
if (!isNaN(_duration)) {
dispatchEvent(new ValueEvent(VideoPlayerCodes.DURATION, _duration));
}
}
}
protected function updateState (newState :int) :void
{
const oldState :int = _state;
_state = newState;
dispatchEvent(new ValueEvent(VideoPlayerCodes.STATE, newState));
if (_state == VideoPlayerCodes.STATE_PLAYING) {
_positionChecker.start();
} else {
_positionChecker.reset();
if (oldState == VideoPlayerCodes.STATE_PLAYING) {
handlePositionCheck(null);
}
}
}
protected const log :Log = Log.getLog(this);
@@ -259,10 +305,16 @@ public class FlvVideoPlayer extends EventDispatcher
/** Our size, null until known. */
protected var _size :Point;
protected var _duration :Number = NaN;
protected var _volume :Number = 1;
/** 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;
protected var _sizeChecker :Timer;
protected var _positionChecker :Timer;
}
}
@@ -38,6 +38,8 @@ public class SimpleVideoDisplay extends Sprite
_player = player;
_player.addEventListener(VideoPlayerCodes.STATE, handlePlayerState);
_player.addEventListener(VideoPlayerCodes.SIZE, handlePlayerSize);
_player.addEventListener(VideoPlayerCodes.DURATION, handlePlayerDuration);
_player.addEventListener(VideoPlayerCodes.POSITION, handlePlayerPosition);
_player.addEventListener(VideoPlayerCodes.ERROR, handlePlayerError);
addChild(_player.getDisplay());
@@ -68,6 +70,36 @@ public class SimpleVideoDisplay extends Sprite
// and update the HUD location (even if not currently showing)
_hud.x = NATIVE_WIDTH/2;
_hud.y = NATIVE_HEIGHT/2;
_track = new Sprite();
_track.x = PAD - _hud.x;
_track.y = NATIVE_HEIGHT - PAD - TRACK_HEIGHT - _hud.y;
g = _track.graphics;
g.beginFill(0x000000, .7);
g.lineStyle(1, 0xFFFFFF);
g.drawRect(0, 0, TRACK_WIDTH, TRACK_HEIGHT);
g.endFill();
//g.drawRect(0, 0, TRACK_WIDTH - 2, TRACK_HEIGHT - 2);
// _track is not added to _hud until we know the duration
const trackMask :Shape = new Shape();
g = trackMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, TRACK_WIDTH, TRACK_HEIGHT);
_track.addChild(trackMask);
_track.mask = trackMask;
_knob = new Sprite();
_knob.y = TRACK_HEIGHT / 2;
g = _knob.graphics;
g.lineStyle(1, 0xFFFFFF);
g.beginFill(0x000099);
g.drawCircle(0, 0, TRACK_HEIGHT/2 - 1);
// _knob is not added to _track until we know the position
_track.addEventListener(MouseEvent.CLICK, handleTrackClick);
_knob.addEventListener(MouseEvent.MOUSE_DOWN, handleKnobDown);
_knob.addEventListener(MouseEvent.MOUSE_UP, handleKnobUp);
}
/**
@@ -109,6 +141,36 @@ public class SimpleVideoDisplay extends Sprite
}
}
protected function handleTrackClick (event :MouseEvent) :void
{
event.stopImmediatePropagation();
// see if we can seek to a position
const dur :Number = _player.getDuration();
if (isNaN(dur)) {
log.debug("Duration is NaN, track click dropped.");
return;
}
var perc :Number = event.localX / TRACK_WIDTH;
perc = Math.max(0, Math.min(1, perc));
log.debug("Seek", "x", event.localX, "perc", perc, "pos", (perc * dur));
_player.seek(perc * dur);
}
protected function handleKnobDown (event :MouseEvent) :void
{
event.stopImmediatePropagation();
// TODO
}
protected function handleKnobUp (event :MouseEvent) :void
{
event.stopImmediatePropagation();
// TODO
}
protected function handlePlayerState (event :ValueEvent) :void
{
redrawHUD();
@@ -129,6 +191,20 @@ public class SimpleVideoDisplay extends Sprite
dispatchEvent(event);
}
protected function handlePlayerDuration (event :ValueEvent) :void
{
_hud.addChild(_track);
}
protected function handlePlayerPosition (event :ValueEvent) :void
{
const pos :Number = Number(event.value);
_knob.x = (pos / _player.getDuration()) * TRACK_WIDTH;
if (_knob.parent == null) {
_track.addChild(_knob);
}
}
protected function handlePlayerError (event :ValueEvent) :void
{
// TODO.. maybe just redispatch
@@ -153,41 +229,42 @@ public class SimpleVideoDisplay extends Sprite
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 {
if ((state != VideoPlayerCodes.STATE_READY) &&
(state != VideoPlayerCodes.STATE_PLAYING) &&
(state != VideoPlayerCodes.STATE_PAUSED)) {
// draw something loading-like
// TODO animated swf
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();
return;
}
// draw a nice circle
g.beginFill(0x000000, .7);
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 { // READY or PAUSED
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();
}
}
@@ -196,8 +273,17 @@ public class SimpleVideoDisplay extends Sprite
protected var _player :VideoPlayer;
protected var _hud :Sprite;
protected var _track :Sprite;
/** The seek selector knob, positioned on the hud. */
protected var _knob :Sprite;
/** Our mask, also defines our boundaries for clicking. */
protected var _mask :Shape;
protected static const PAD :int = 10;
protected static const TRACK_HEIGHT :int = 20;
protected static const TRACK_WIDTH :int = NATIVE_WIDTH - (PAD * 2);
}
}