ddd796431d
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4362 542714f4-19e9-0310-aa3c-eee0fc999fb1
479 lines
13 KiB
ActionScript
479 lines
13 KiB
ActionScript
package com.threerings.util {
|
|
|
|
import flash.display.DisplayObject;
|
|
import flash.display.DisplayObjectContainer;
|
|
import flash.display.Loader;
|
|
import flash.display.LoaderInfo;
|
|
import flash.display.Shape;
|
|
|
|
import flash.errors.IOError;
|
|
|
|
import flash.events.Event;
|
|
import flash.events.EventDispatcher;
|
|
import flash.events.IEventDispatcher;
|
|
import flash.events.IOErrorEvent;
|
|
import flash.events.MouseEvent;
|
|
import flash.events.NetStatusEvent;
|
|
import flash.events.ProgressEvent;
|
|
import flash.events.SecurityErrorEvent;
|
|
import flash.events.StatusEvent;
|
|
import flash.events.TextEvent;
|
|
|
|
import flash.geom.Point;
|
|
|
|
import flash.media.Video;
|
|
|
|
import flash.system.ApplicationDomain;
|
|
import flash.system.LoaderContext;
|
|
import flash.system.SecurityDomain;
|
|
|
|
import flash.net.URLRequest;
|
|
|
|
import mx.core.ScrollPolicy;
|
|
import mx.core.UIComponent;
|
|
|
|
import mx.containers.Box;
|
|
import mx.controls.VideoDisplay;
|
|
|
|
import mx.events.VideoEvent;
|
|
|
|
import com.threerings.util.StringUtil;
|
|
|
|
import com.threerings.media.image.ImageUtil;
|
|
|
|
/**
|
|
* A wrapper class for all media that will be placed on the screen.
|
|
* Subject to change.
|
|
*/
|
|
public class MediaContainer extends Box
|
|
{
|
|
/** A log instance that can be shared by sprites. */
|
|
protected static const log :Log = Log.getLog(MediaContainer);
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function MediaContainer (url :String = null)
|
|
{
|
|
if (url != null) {
|
|
setMedia(url);
|
|
}
|
|
|
|
mouseEnabled = false;
|
|
mouseChildren = true;
|
|
verticalScrollPolicy = ScrollPolicy.OFF;
|
|
horizontalScrollPolicy = ScrollPolicy.OFF;
|
|
}
|
|
|
|
/**
|
|
* Configure the media to display.
|
|
*/
|
|
public function setMedia (url :String) :void
|
|
{
|
|
// shutdown any previous media
|
|
if (_media != null) {
|
|
shutdown(false);
|
|
}
|
|
|
|
// set up the new media
|
|
if (StringUtil.endsWith(url.toLowerCase(), ".flv")) {
|
|
setupVideo(url);
|
|
|
|
} else {
|
|
setupSwfOrImage(url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configure this sprite to show a video.
|
|
*/
|
|
protected function setupVideo (url :String) :void
|
|
{
|
|
var vid :VideoDisplay = new VideoDisplay();
|
|
vid.autoPlay = false;
|
|
_media = vid;
|
|
addChild(vid);
|
|
vid.addEventListener(ProgressEvent.PROGRESS, loadVideoProgress);
|
|
vid.addEventListener(VideoEvent.READY, loadVideoReady);
|
|
vid.addEventListener(VideoEvent.REWIND, videoDidRewind);
|
|
|
|
// start it loading
|
|
vid.source = url;
|
|
vid.load();
|
|
}
|
|
|
|
/**
|
|
* Configure this sprite to show an image or flash movie.
|
|
*/
|
|
protected function setupSwfOrImage (url :String) :void
|
|
{
|
|
// create our loader and set up some event listeners
|
|
var loader :Loader = new Loader();
|
|
_media = loader;
|
|
var info :LoaderInfo = loader.contentLoaderInfo;
|
|
info.addEventListener(Event.COMPLETE, loadingComplete);
|
|
info.addEventListener(IOErrorEvent.IO_ERROR, loadError);
|
|
info.addEventListener(ProgressEvent.PROGRESS, loadProgress);
|
|
|
|
// create a mask to prevent the media from drawing out of bounds
|
|
if (maxContentWidth < int.MAX_VALUE &&
|
|
maxContentHeight < int.MAX_VALUE) {
|
|
configureMask(maxContentWidth, maxContentHeight);
|
|
}
|
|
|
|
// start it loading, add it as a child
|
|
loader.load(new URLRequest(url), getContext(url));
|
|
rawChildren.addChild(loader);
|
|
|
|
try {
|
|
updateContentDimensions(info.width, info.height);
|
|
} catch (err :Error) {
|
|
// an error is thrown trying to access these props before they're
|
|
// ready
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display a 'broken image' to indicate there were troubles with
|
|
* loading the media.
|
|
*/
|
|
protected function setupBrokenImage (w :int = -1, h :int = -1) :void
|
|
{
|
|
if (w == -1) {
|
|
w = 100;
|
|
}
|
|
if (h == -1) {
|
|
h = 100;
|
|
}
|
|
_media = ImageUtil.createErrorImage(w, h);
|
|
rawChildren.addChild(_media);
|
|
}
|
|
|
|
/**
|
|
* Get the application domain being used by this media, or null if
|
|
* none or not applicable.
|
|
*/
|
|
public function getApplicationDomain () :ApplicationDomain
|
|
{
|
|
return (_media is Loader)
|
|
? (_media as Loader).contentLoaderInfo.applicationDomain
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Unload the media we're displaying, clean up any resources.
|
|
*
|
|
* @param completely if true, we're going away and should stop
|
|
* everything. Otherwise, we're just loading up new media.
|
|
*/
|
|
public function shutdown (completely :Boolean = true) :void
|
|
{
|
|
try {
|
|
// remove the mask
|
|
if (_media != null && _media.mask != null) {
|
|
rawChildren.removeChild(_media.mask);
|
|
_media.mask = null;
|
|
}
|
|
|
|
if (_media is Loader) {
|
|
var loader :Loader = (_media as Loader);
|
|
// remove any listeners
|
|
removeListeners(loader.contentLoaderInfo);
|
|
|
|
// dispose of media
|
|
try {
|
|
loader.close();
|
|
} catch (ioe :IOError) {
|
|
// ignore
|
|
}
|
|
loader.unload();
|
|
|
|
rawChildren.removeChild(loader);
|
|
|
|
} else if (_media is VideoDisplay) {
|
|
var vid :VideoDisplay = (_media as VideoDisplay);
|
|
// remove any listeners
|
|
vid.removeEventListener(ProgressEvent.PROGRESS,
|
|
loadVideoProgress);
|
|
vid.removeEventListener(VideoEvent.READY, loadVideoReady);
|
|
vid.removeEventListener(VideoEvent.REWIND, videoDidRewind);
|
|
|
|
// dispose of media
|
|
vid.pause();
|
|
try {
|
|
vid.close();
|
|
} catch (ioe :IOError) {
|
|
// ignore
|
|
}
|
|
vid.stop();
|
|
|
|
// remove from hierarchy
|
|
removeChild(vid);
|
|
|
|
} else if (_media != null) {
|
|
if (_media is UIComponent) {
|
|
removeChild(_media);
|
|
} else {
|
|
rawChildren.removeChild(_media);
|
|
}
|
|
}
|
|
} catch (ioe :IOError) {
|
|
log.warning("Error shutting down media: " + ioe);
|
|
log.logStackTrace(ioe);
|
|
}
|
|
|
|
// clean everything up
|
|
_w = 0;
|
|
_h = 0;
|
|
_media = null;
|
|
width = NaN;
|
|
height = NaN;
|
|
}
|
|
|
|
override public function parentChanged (p :DisplayObjectContainer) :void
|
|
{
|
|
super.parentChanged(p);
|
|
if (p == null && _media != null) {
|
|
shutdown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the width of the content, bounded by the maximum.
|
|
*/
|
|
public function get contentWidth () :int
|
|
{
|
|
return Math.min(Math.abs(_w * getMediaScaleX()), maxContentWidth);
|
|
}
|
|
|
|
/**
|
|
* Get the height of the content, bounded by the maximum.
|
|
*/
|
|
public function get contentHeight () :int
|
|
{
|
|
return Math.min(Math.abs(_h * getMediaScaleY()), maxContentHeight);
|
|
}
|
|
|
|
/**
|
|
* Get the maximum allowable width for our content.
|
|
*/
|
|
public function get maxContentWidth () :int
|
|
{
|
|
return int.MAX_VALUE;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum allowable height for our content.
|
|
*/
|
|
public function get maxContentHeight () :int
|
|
{
|
|
return int.MAX_VALUE;
|
|
}
|
|
|
|
/**
|
|
* Get the X scaling factor to use on the actual media.
|
|
*/
|
|
public function getMediaScaleX () :Number
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Get the Y scaling factor to use on the actual media.
|
|
*/
|
|
public function getMediaScaleY () :Number
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Return the LoaderContext that should be used to load the media
|
|
* at the specified url.
|
|
*/
|
|
protected function getContext (url :String) :LoaderContext
|
|
{
|
|
/* Super unrestrictive */
|
|
// return null;
|
|
|
|
/* a little unrestrictive */
|
|
// return new LoaderContext(false,
|
|
// ApplicationDomain.currentDomain,
|
|
// null);
|
|
|
|
/* more restrictive */
|
|
return new LoaderContext(false,
|
|
new ApplicationDomain(ApplicationDomain.currentDomain),
|
|
null);
|
|
|
|
/*
|
|
var loadCtx :LoaderContext = (_loadCtx.get(url) as LoaderContext);
|
|
if (loadCtx == null) {
|
|
trace("Creating new loadctx for " + url);
|
|
loadCtx = new LoaderContext(
|
|
false,
|
|
//new ApplicationDomain(ApplicationDomain.currentDomain),
|
|
ApplicationDomain.currentDomain,
|
|
null
|
|
);
|
|
_loadCtx.put(url, loadCtx);
|
|
}
|
|
return loadCtx;
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* Remove our listeners from the LoaderInfo object.
|
|
*/
|
|
protected function removeListeners (info :LoaderInfo) :void
|
|
{
|
|
info.removeEventListener(Event.COMPLETE, loadingComplete);
|
|
info.removeEventListener(IOErrorEvent.IO_ERROR, loadError);
|
|
info.removeEventListener(ProgressEvent.PROGRESS, loadProgress);
|
|
}
|
|
|
|
/**
|
|
* A callback to receive IO_ERROR events.
|
|
*/
|
|
protected function loadError (event :IOErrorEvent) :void
|
|
{
|
|
var info :LoaderInfo = (event.target as LoaderInfo);
|
|
removeListeners(info);
|
|
|
|
var loader :Loader = (_media as Loader);
|
|
rawChildren.removeChild(loader);
|
|
if (loader.mask != null) {
|
|
rawChildren.removeChild(loader.mask);
|
|
}
|
|
|
|
setupBrokenImage(-1, -1);
|
|
}
|
|
|
|
/**
|
|
* A callback to receive PROGRESS events.
|
|
*/
|
|
protected function loadProgress (event :ProgressEvent) :void
|
|
{
|
|
updateLoadingProgress(event.bytesLoaded, event.bytesTotal);
|
|
var info :LoaderInfo = (event.target as LoaderInfo);
|
|
try {
|
|
updateContentDimensions(info.width, info.height);
|
|
} catch (err :Error) {
|
|
// an error is thrown trying to access these props before they're
|
|
// ready
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A callback to receive PROGRESS events on the video.
|
|
*/
|
|
protected function loadVideoProgress (event :ProgressEvent) :void
|
|
{
|
|
var vid :VideoDisplay = (event.currentTarget as VideoDisplay);
|
|
updateContentDimensions(vid.videoWidth, vid.videoHeight);
|
|
|
|
updateLoadingProgress(vid.bytesLoaded, vid.bytesTotal);
|
|
}
|
|
|
|
/**
|
|
* A callback to receive READY events for video.
|
|
*/
|
|
protected function loadVideoReady (event :VideoEvent) :void
|
|
{
|
|
var vid :VideoDisplay = (event.currentTarget as VideoDisplay);
|
|
updateContentDimensions(vid.videoWidth, vid.videoHeight);
|
|
updateLoadingProgress(1, 1);
|
|
|
|
vid.play();
|
|
|
|
// remove the two listeners
|
|
vid.removeEventListener(ProgressEvent.PROGRESS, loadVideoProgress);
|
|
vid.removeEventListener(VideoEvent.READY, loadVideoReady);
|
|
}
|
|
|
|
/**
|
|
* Callback function to receive COMPLETE events for swfs or images.
|
|
*/
|
|
protected function loadingComplete (event :Event) :void
|
|
{
|
|
var info :LoaderInfo = (event.target as LoaderInfo);
|
|
removeListeners(info);
|
|
|
|
updateContentDimensions(info.width, info.height);
|
|
updateLoadingProgress(1, 1);
|
|
}
|
|
|
|
/**
|
|
* Called when the video auto-rewinds.
|
|
*/
|
|
protected function videoDidRewind (event :VideoEvent) :void
|
|
{
|
|
(_media as VideoDisplay).play();
|
|
}
|
|
|
|
/**
|
|
* Configure the mask for this object.
|
|
*/
|
|
protected function configureMask (ww :int, hh :int) :void
|
|
{
|
|
var mask :Shape;
|
|
if (_media.mask != null) {
|
|
mask = (_media.mask as Shape);
|
|
|
|
} else {
|
|
mask = new Shape();
|
|
// the mask must be added to the display list (which is wacky)
|
|
rawChildren.addChild(mask);
|
|
_media.mask = mask;
|
|
}
|
|
|
|
mask.graphics.clear();
|
|
mask.graphics.beginFill(0xFFFFFF);
|
|
mask.graphics.drawRect(0, 0, ww, hh);
|
|
mask.graphics.endFill();
|
|
}
|
|
|
|
/**
|
|
* Called during loading as we figure out how big the content we're
|
|
* loading is.
|
|
*/
|
|
protected function updateContentDimensions (ww :int, hh :int) :void
|
|
{
|
|
width = ww;
|
|
height = hh;
|
|
|
|
// update our saved size, and possibly notify our container
|
|
if (_w != ww || _h != hh) {
|
|
_w = ww;
|
|
_h = hh;
|
|
contentDimensionsUpdated();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when we know the true size of the content.
|
|
*/
|
|
protected function contentDimensionsUpdated () :void
|
|
{
|
|
// nada, by default
|
|
}
|
|
|
|
/**
|
|
* Update the graphics to indicate how much is loaded.
|
|
*/
|
|
protected function updateLoadingProgress (
|
|
soFar :Number, total :Number) :void
|
|
{
|
|
// nada, by default
|
|
}
|
|
|
|
/** The unscaled width of our content. */
|
|
protected var _w :int;
|
|
|
|
/** The unscaled height of our content. */
|
|
protected var _h :int;
|
|
|
|
/** Either a Loader or a VideoDisplay. */
|
|
protected var _media :DisplayObject;
|
|
}
|
|
}
|