Flash kiosk touch app - if nothing happens go to homepage - actionscript-3

I'm making a simple touchscreen app with flash. When users doesn't use it for 1 or 2 minutes I would like that the movie will automatically go back to the homepage link. How to reach that?
Is there any actionscript that if a mouse doesn't move for 2 minutes the movie will go to a link or it will rewind?
Thank you!
SOLVED:
AS2.0
// main timeline code
timeout = 5000;
this.onMouseMove = function(){
clearInterval(ID);
ID = setInterval(action, timeout);
trace("interaction");
};
function action(){
clearInterval(ID);
trace("no interaction for 5 seconds");
// go to end frame
};

Here is static class I wrote for a kiosk application I made many years ago. You just pass in a reference to the main stage, a function to reset the program, and a dialog box function (you could easily take out that part of the code if you don't want the dialog).
TimeoutManager.init(stage, myResetFunction, myDialogBoxFunction);
You could enable or disable it anytime with: TimeoutManager.kioskTimeoutEnabled = false;
Let me know if you have any questions about it, it's fairly well commented.
package
{
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.utils.clearTimeout;
import flash.utils.setTimeout;
import flash.utils.Timer;
/**
* Manages the Kiosk session timeout functionality
* #LDMS
*/
public class TimeoutManager
{
//KioskTimeout Vars
private static var kioskTimeoutEnabled_ :Boolean = false;
private static var kioskTimeoutTimer :Timer = new Timer(3000, 1);
private static var kioskTimeout :uint;
private static var timeoutCountdownTimer:Timer = new Timer(1000);
private static var stage :Stage;
public static var defaultTime :int = 120000; //2 minutes - amount of inactivity before the "are you still there" message comes up.
public static var dialogTime :int = 30000; //30 seconds - amount of time to wait for the user to click the "are you still there" message before reseting the applicaiton
public static var resetFunction :Function; //the function to call to reset the application
public static var dialogFunction:Function; //the function to call to show a dialog box - passes tittle & text as two parameters to the method.
public static function init(mainStage:Stage, resetFunction_:Function, dialogFunction_:Function):void {
stage = mainStage;
resetFunction = resetFunction_;
dialogFunction = dialogFunction_;
kioskTimeoutTimer.addEventListener(TimerEvent.TIMER, timeoutKiosk, false, 0, true);
kioskTimeoutEnabled = true;
timeoutCountdownTimer.addEventListener(TimerEvent.TIMER, countdownTick, false, 0, true);
}
/**
* Enable or Disable the Kiosk reset timeout feature
*/
public static function set kioskTimeoutEnabled(val:Boolean):void
{
kioskTimeoutEnabled_ = val;
if (val) {
setKioskTimeout();
try { stage.addEventListener(MouseEvent.MOUSE_DOWN, kioskTimeoutReset, false, 0, true); } catch (e:Error) { trace(e.toString()); };
} else {
cancelKioskTimeout();
try { stage.removeEventListener(MouseEvent.MOUSE_DOWN, kioskTimeoutReset, false); } catch (e:Error) { trace(e.toString()); };
};
}
public static function get kioskTimeoutEnabled():Boolean { return kioskTimeoutEnabled_; };
/**
* Resets the timeout period for the kiosk reset timeout feature - called on a global mouse_down event.
* #param e - unused mouse_down event
*/
public static function kioskTimeoutReset(e:Event = null):void {
cancelKioskTimeout();
setKioskTimeout();
}
/**
* used to cancel the kiosk timeout period temporarily
*/
public static function cancelKioskTimeout():void {
kioskTimeoutTimer.reset();
}
/**
* Set or reset the time (in milliseconds) before the kiosk times out.
* #param overrideTime - if not 0 (default), sets a temporary new time (in milliseconds) before the kiosk times out.
*/
public static function setKioskTimeout(overrideTime:Number = 0):void {
if(!kioskTimeoutEnabled) return;
try { cancelKioskTimeout(); } catch (e:Error) { };
var time:Number = (overrideTime > 0) ? overrideTime : defaultTime;
kioskTimeoutTimer.delay = time;
kioskTimeoutTimer.start();
}
/**
* after the set inactivity period passes, this function is called to let the user know the kiosk is about to reset giving them a chance to keep their session alive
*/
private static function timeoutKiosk(e:Event = null):void {
dialogFunction("Are you still there?", "Reset in " + String(Preferences.kioskTimeout_dialogTime / 1000) + " seconds");
timeoutCountdownTimer.start();
kioskTimeout = setTimeout(goReset, dialogTime);
stage.addEventListener(MouseEvent.MOUSE_DOWN, clearKioskTimeout2,false,0,true);
}
protected static function countdownTick(e:TimerEvent):void {
dialogFunction("Are you still there?", "Reset in " + String((Preferences.kioskTimeout_dialogTime / 1000) - timeoutCountdownTimer.currentCount) + " seconds");
}
/**
* Cancels the timeout prompt timeout
* #param e - unused mouse event
*/
private static function clearKioskTimeout2(e:Event = null):void {
try {
stage.removeEventListener(MouseEvent.MOUSE_DOWN, clearKioskTimeout2,false);
}catch (e:Error) { trace(e.toString()); };
clearTimeout(kioskTimeout);
timeoutCountdownTimer.reset();
}
private static function goReset(e:Event = null):void {
timeoutCountdownTimer.reset();
resetFunction();
}
}
}

Related

AS3 Windows style drag and drop

I'm making a windows style app with different windows which contain different elements. I know how to code a drag and drop function to change the position of the windows on the stage but I'd like to use a single code for all windows without repeating infinite functions. My code is:
public function fl_WindowDrag(event: MouseEvent): void {
instance.startDrag();
}
public function fl_WindowDrop(event: MouseEvent): void {
instance.stopDrag();
}
I'd like the istance name was retrived automatically from the selected window, is it possible?
I hope you understand my need
Any help is well accepted
Thanks in advance
package {
import flash.display.*;
import flash.text.*;
import flash.events.*;
import flash.ui.Keyboard;
import flash.ui.Mouse;
import flash.display.MovieClip;
public class MainTimeline extends MovieClip {
//Variabili
public var VFullscreen: int = 1;
//Import var
public var VTerminal: Terminal = new Terminal();
public var nTerminal:String;
public function MainTimeline(): void {
stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
//Import
//Terminal
VTerminal.x = 288;
VTerminal.y = 384;
stage.addChild(VTerminal);
//Event Listeners
//addEventListener(MouseEvent.CLICK, fl_BringToFront);
VTerminal.addEventListener(MouseEvent.MOUSE_DOWN, fl_WindowDrag);
VTerminal.addEventListener(MouseEvent.MOUSE_UP, fl_WindowDrop);
}
//public functions
//Gestione Fullscreen
public function fl_Fullscreen(event: MouseEvent): void {
switch (VFullscreen) {
case 0:
stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
VFullscreen = 1;
break;
case 1:
stage.displayState = StageDisplayState.NORMAL;
VFullscreen = 0;
break;
}
}
public function fl_FSCheck(event: Event): void {
if (stage.displayState == StageDisplayState.NORMAL) {
VFullscreen = 0;
}
if (stage.displayState == StageDisplayState.FULL_SCREEN_INTERACTIVE) {
VFullscreen = 1;
}
}
//Primo Piano Finestre
public function fl_BringToFront(event: MouseEvent): void {
this.addChild(event.currentTarget as DisplayObject);
}
public function fl_WindowDrag(event: MouseEvent): void {
event.currentTarget.startDrag();
nTerminal = event.currentTarget.name.toString();
trace(nTerminal);
}
public function fl_WindowDrop(event: MouseEvent): void {
event.currentTarget.stopDrag();
}
//Chiusura
public function fl_Close(event: MouseEvent): void {
stage.nativeWindow.close();
}
//Apertura/Chiusura Terminal
public function fl_Terminal(event: MouseEvent): void {
if (contains(VTerminal)) {
removeChild(VTerminal);
} else {
VTerminal.x = 288;
VTerminal.y = 320;
addChild(VTerminal);
}
}
}
}
There are many ways you can do this.
You can prepare base class for all windows wich will inherit this behavior and if you plan to design over a dozen of windows this is definitelly thing you should consider. Another way is to create sperarate class designed to only do this and register all windows you want to it.
But since you asking if this is even possible I would recommend you to try some third party libraries.
GreenShock has a good tool for this but i didn't use their libralies a while and I don't know what are their licecing programs.
In case you don't like it, you can use my code i made some time ago.
It's very easy to use but it's not fully implemented(can't rotate and such) and documentation is very weak but if you were interested i can help you with this.
All you need to do is pass objects you want to move along with some bassic properties:
TransformTool.addClient(target:InteractiveObject, operations:uint = 3, edgesMask:uint = 15, sizeBounds:Array = null, dragBounds:Array = null, grabRange:Number = 5):TransformData
terget - that would be your window.
operations - bit-flags representing type of transformation (scale, drag, rotate)
edgesMask - bit-flags defining which edges should be involved in scaling operation (left, right, top, bottom) all these flags values as well as operations flags can be found in TransformData class.
sizeBounds - array contian minimum and maximum size of scaled object accordingly for with and height.
dragBounds - position boundries for draged object. Basically those are arguments for flash Rectangle class.
grabRange - distance from mouse to edge in which you can grab and edge (or edges in case of corner). You can grab an edge also with mouse outside the object and scale two separate objects edges at once.
So assuming w and w2 are your windows, usage looks like this:
TransformTool.addClient(w, TransforData.SCALE|TransformData.DRAG, TransformData.BOTTOM_EDGE|TransformData.RIGHT_EDGE, null, null, 10);
TransformTool.addClient(w2, 3, 15, [20, 10, 350, 350], [100, 100, 600, 500], 10);
That is only this line required to use it.
You can also add listener if you would like to change cursor.
TransformTool.eventDispatcher.addEventListener(TransformToolEvent.EDGES_HIT, transformTestHit);
private function transformTestHit(e:TransformToolEvent):void
{
trace(TransformData(e.data).hitEdges);
}
Below is all the code involved. You can use it as you wish however be Aware that TransformTool is static class and uses only one stage instance.
I you want to develop app in Air and use native windows you would need to modify this code because each navtive window instance has its own unique stage.
TransformTool class:
package utils
{
import flash.display.DisplayObject;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* ...
* #author Audionysos
*/
public class TransformTool
{
private static var clients:Vector.<TransformData> = new Vector.<TransformData>();
private static var transforming:Vector.<TransformData> = new Vector.<TransformData>();
private static var _stage:Stage;
public static var checkEdgesOnMMove:Boolean = true;
private static var _eventDispatcher:EventDispatcher = new EventDispatcher();
public static function addClient(target:InteractiveObject, operations:uint = 3, edgesMask:uint = 15, sizeBounds:Array = null, dragBounds:Array = null, grabRange:Number = 5):TransformData {
var sBR:Rectangle;
var dBR:Rectangle;
if (sizeBounds) sBR = new Rectangle(sizeBounds[0], sizeBounds[1], sizeBounds[2], sizeBounds[3]);
if (dragBounds) dBR = new Rectangle(dragBounds[0], dragBounds[1], dragBounds[2], dragBounds[3]);
var td:TransformData = new TransformData(target, operations, edgesMask, sBR, dBR, grabRange);
if (operations & TransformData.SCALE) td.allowDrag = true;
clients.push(td);
if (stage) return td;
if (!target.stage) target.addEventListener(Event.ADDED_TO_STAGE, onStage);
else { stage = target.stage; addStageListeners(); }
return td;
}
/**
* Return TransformData object associated with given target.
* #param target object associated with searched TransformData.
* #return TransformData object associated with given target.
*/
static public function getTransformData(target:InteractiveObject):TransformData {
for (var i:int = 0; i < clients.length; i++){
if (clients[i].targetObject == target) return clients[i];
}return null;
}
/**
* Mouse position relative to specifed object.
* #param target InteractiveObject or TransformData object.
* #return Mouse position relative to specifed object.
*/
static public function getTargetMouse(target:*):Point
{
var t:InteractiveObject = target as InteractiveObject;
if (!t && target as TransformData) t = TransformData(target).targetObject;
else throw new Error ("property object must be of type InteractiveObject or TransformData");
return new Point(t.parent.mouseX, t.parent.mouseY);
}
/**
* Adds MOUSE_DOWN and MOUSE_UP listener for current stage;
*/
static private function addStageListeners():void
{
//trace("TT adding stage listeners");
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onMUp);
}
static private function onStage(e:Event):void
{
//trace("TT client on stage");
InteractiveObject(e.target).removeEventListener(Event.ADDED_TO_STAGE, onStage);
stage = InteractiveObject(e.target).stage;
addStageListeners();
}
static private function onMUp(e:MouseEvent):void
{
for (var i:int = 0; i < transforming.length; i++){
transforming[i].isTransforming = false;
}
transforming = new Vector.<TransformData>();
}
static private function onMDown(e:MouseEvent):void
{
//trace("TT MousDown");
findAffectedObjects();
}
static private function findAffectedObjects():void
{
for (var i:int = 0; i < clients.length; i++) {
clients[i].hitEdges = findEdges(clients[i]);
if (!clients[i].hitEdges) continue;
//trace("TT got R", clients[i].hitEdges);
transforming.push(clients[i]);
clients[i].isTransforming = true;
clients[i].updateMouseVector();
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMMove);
}
}
static private function onMMove(e:MouseEvent):void
{
//trace("Transforming", transforming.length);
if (TransformTool.checkEdgesOnMMove) dispatchEdges();
//trace("TT Moving");
for (var i:int = 0; i < transforming.length; i++) {
if (transforming[i].operations & TransformData.ROTATE) rotateTarget();
//if (!checkEdgesOnMMove)
transforming[i].updateMouseVector();
scaleTarget(transforming[i]);
fixSize(transforming[i]);
fixPlacement(transforming[i]);
}
}
/**
* Performs fingEdges() operation on each client TransformData object and dispatches EDGES_HIT event if result is different form last hitEdges state;
*/
static private function dispatchEdges():void
{
for (var i:int = 0; i < clients.length; i++) {
if (clients[i].isTransforming) continue;
var r:uint = findEdges(clients[i]);
if (r != clients[i].hitEdges) {
clients[i].hitEdges = r;
_eventDispatcher.dispatchEvent(new TransformToolEvent(TransformToolEvent.EDGES_HIT, clients[i]));
}
}
}
static private function rotateTarget():void
{
}
/**
* If a part of an object is outside defined dragBounds rectangle area it will move this object to closest allowed position.
* #param td
*/
static public function fixPlacement(td:TransformData):void
{
if (!td.dragBounds) return;
td.targetObject.x = Math.max(td.targetObject.x, td.dragBounds.x);
td.targetObject.x = Math.min(td.dragBounds.right - td.targetObject.width, td.targetObject.x);
td.targetObject.y = Math.max(td.targetObject.y, td.dragBounds.y);
td.targetObject.y = Math.min(td.dragBounds.bottom-td.targetObject.height, td.targetObject.y);
}
/**
* Changes the object to fit min/max size defined in sizeBounds object of the transformation data.
* #param td
*/
static public function fixSize(td:TransformData):void
{
if (!td.sizeBounds) return;
td.targetObject.width = Math.min(td.targetObject.width, td.sizeBounds.width);
td.targetObject.width = Math.max(td.targetObject.width, td.sizeBounds.x);
td.targetObject.height = Math.min(td.targetObject.height, td.sizeBounds.height);
td.targetObject.height = Math.max(td.targetObject.height, td.sizeBounds.y);
}
/**
* Scales the object accordingly to grabed edges and move of the mouse.
* #param td
*/
static public function scaleTarget(td:TransformData):void
{
//trace("TT mouse vector", td.mouseVector);
var rD:Point = td.mouseVector//new Point(td.mouseVector.x * td.targetObject.parent.scaleX, td.mouseVector.y * td.targetObject.parent.scaleY); //relativeDisplacement
if (td.hitEdges & TransformData.LEFT_EDGE) { td.targetObject.width -= rD.x; td.targetObject.x += rD.x; }
if (td.hitEdges & TransformData.RIGHT_EDGE) { td.targetObject.width += rD.x; }
if (td.hitEdges & TransformData.TOP_EDGE) { td.targetObject.height -= rD.y; td.targetObject.y += rD.y; }
if (td.hitEdges & TransformData.BOTTOM_EDGE) { td.targetObject.height += rD.y; }
}
/**
* Check if mouse position is in grab range to any of specified object edge.
* #param target examined object
* #param grabRange minimal distance from mouse position to edge of the object.
* #return resul of the inspection.
*/
static public function findEdges(td:TransformData):uint
{
if (!isMouseNearTarget(td)) return 0;
var t:InteractiveObject = td.targetObject;
var gR:Number = td.grabRange;
var r:uint;
if (Math.abs(t.x - t.parent.mouseX) < gR && t.parent.mouseX) r |= TransformData.LEFT_EDGE;
if (Math.abs(t.x + t.width- t.parent.mouseX) < gR) r |= TransformData.RIGHT_EDGE;
if (Math.abs(t.y - t.parent.mouseY) < gR) r |= TransformData.TOP_EDGE;
if (Math.abs(t.y + t.height - t.parent.mouseY) < gR) r |= TransformData.BOTTOM_EDGE;
return r;
}
/**
* Check if mouse relative position is cantained within target rectangle + grabRange;
* #param td object to examine.
* #return true if mouse is near object (edges can be grabbed);
*/
static public function isMouseNearTarget(td:TransformData):Boolean
{
td.updateMouseVector();
var exRect:Rectangle = td.targetObject.getRect(td.targetObject.parent).clone();
exRect.inflate(td.grabRange, td.grabRange);
return exRect.containsPoint(td.mouseStart);
}
/**
* Dispatches events associated with transformed client objects.
* TransformToolEvent contains reference to interested TransformData object.
* #eventType TransformToolEvent.EDGE_HIT dispatched when mouse cursor is close enought client object edges to let it to be scaled.
* You can for example use it's hitEdges property to change cursor icon accordingly.
*/
static public function get eventDispatcher():EventDispatcher
{
return _eventDispatcher;
}
/**
* Stage property on which mouse events will be proceded.
* This will be set automaticly from client object (it it was null before).
*/
static public function get stage():Stage
{
return _stage;
}
static public function set stage(value:Stage):void
{
if (_stage) {
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMMove);
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMUp);
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMDown);
}
_stage = value;
addStageListeners();
if(checkEdgesOnMMove) value.addEventListener(MouseEvent.MOUSE_MOVE, onMMove);
}
}
}
TransformData class:
package utils
{
import flash.display.InteractiveObject;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* ...
* #author Audionysos
*/
public class TransformData
{
public static const SCALE:uint = 1;
public static const DRAG:uint = 2;
public static const ROTATE:uint = 4;
public static const TOP_EDGE:uint = 1;
public static const RIGHT_EDGE:uint = 2;
public static const BOTTOM_EDGE:uint = 4;
public static const LEFT_EDGE:uint = 8;
public var targetObject:InteractiveObject;
public var grabRange:Number;
public var sizeBounds:Rectangle;
public var dragBounds:Rectangle;
public var mouseStart:Point;
public var mouseVector:Point;
public var hitEdges:uint;
public var edgesMask:uint;
public var operations:uint;
public var isTransforming:Boolean;
private var _allowDrag:Boolean;
private var isDraging:Boolean;
public function TransformData(target:InteractiveObject, operations:uint = 3, edgesMask:uint = 15, sizeBounds:Rectangle = null, dragBounds:Rectangle = null, grabRange:Number = 5)
{
targetObject = target;
this.sizeBounds = sizeBounds;
this.dragBounds = dragBounds;
this.grabRange = grabRange;
this.edgesMask = edgesMask;
this.operations = operations;
}
public function updateMouseVector () {
var mP:Point = new Point(targetObject.parent.mouseX, targetObject.parent.mouseY);
if (!mouseStart) mouseStart = mP;
mouseVector = mP.subtract(mouseStart);
mouseStart = mP;
}
public function get allowDrag():Boolean
{
return _allowDrag;
}
public function set allowDrag(value:Boolean):void
{
if (_allowDrag && !value) {
targetObject.stage.removeEventListener(MouseEvent.MOUSE_UP, onMUp);
targetObject.removeEventListener(MouseEvent.MOUSE_DOWN, onMDown);
if (isDraging) targetObject.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMMove);
}
_allowDrag = value;
if (value) targetObject.addEventListener(MouseEvent.MOUSE_DOWN, onMDown);
}
private function onMDown(e:MouseEvent):void
{
isTransforming = true;
mouseStart = TransformTool.getTargetMouse(this);
targetObject.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMMove);
targetObject.stage.addEventListener(MouseEvent.MOUSE_UP, onMUp);
}
private function onMUp(e:MouseEvent):void
{
isTransforming = false;
targetObject.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMMove);
}
private function onMMove(e:MouseEvent):void
{
updateMouseVector();
targetObject.x += mouseVector.x;
targetObject.y += mouseVector.y;
TransformTool.fixPlacement(this);
}
}
}
TransformToolEvent:
package utils
{
import flash.events.Event;
/**
* ...
* #author Audionysos
*/
public class TransformToolEvent extends Event
{
public static const EDGES_HIT:String = "edgesHit";
private var _data:TransformData;
public function TransformToolEvent(type:String, data:TransformData, bubbles:Boolean=false, cancelable:Boolean=false) {
super(type, bubbles, cancelable);
_data = data;
}
public override function clone():Event {
return new TransformToolEvent(type, _data, bubbles, cancelable);
}
public override function toString():String {
return formatToString("TransformToolEvent", "type", "bubbles", "cancelable", "eventPhase", "data");
}
public function get data():TransformData {
return _data;
}
}
}
You can use the target or currentTarget propterty of the mouse-event to reference the instance that triggers the event, like this (i have not tested this code):
public function fl_WindowDrag(event: MouseEvent): void {
event.currentTarget.startDrag();
}
this way you can add the event handler to multiple instances. You can read more about the events properties here

synchronous image loading action script 3 flash

I am having a problem with my flash/as3 application. I've created most of it and at the moment I'm struggling to get my external resources work.
My application consists of a Controller class, that takes control of an application flow. At the beginning it initializes AppInitializer class, that loads / generates the whole app content (it is a simple point'n'click game).
In AppInitializer I create an array of items available in a game. Item's constructor takes path as a parameter (String) to the resource (image). Then, inside the constructor I call a static method of my AssetsLoader class which looks like that:
public static function loadImage(path:String):BitmapData
{
completed = false;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event){completed = true;
trace("Loading completed");
e.target.removeEventListener(Event.COMPLETE, check);});
if (path == "")
loader.load(new URLRequest("images/default.png"));
else
loader.load(new URLRequest("images/" + path));
//while (!completed);
var image:Bitmap = new Bitmap((loader.content as BitmapData));
return image.bitmapData;
}
Where completed is a static variable of AssetsLoader.
First problem is: I create many Item objects in a row, so the method loadImage should not be static I guess (same with completed variable), since that may cause problems when loading.
Second problem is: At the moment I'm unable to return the bitmapData (or bitmap, it does not really matter), because the return statement will always return null - because the resource is loaded asynchronously and is not loaded at the time application reaches return statement. If I uncomment the wile loop, the complete event is never called.
I would like to ask more experienced ActionScript developers to point out any solutions that would require minimal changes to the rest of my app and would solve those problems. (I think first problem can be eliminated by using some sort of queueing method for the loader, but I'm stuck with the second problem for few days so far and came up with no possible solution).
I could also consider changes in my logic, so I could preload all image resources into "somewhere" and after that just make copies of these images for my purposes. If that's easier to do.
So as I suggested in the comments, a minimal change resolution could simply be to pass a function to call as part of the parameters for loadImage(). This is known as a callback function, and it would look something like this:
First create the callback function:
public function addImage( bitmapData:BitmapData ):void {
//do something with the bitmapData returned
}
Next adjust your loadImage() local function to use the callback with the bitmap data when the event has completed:
public static function loadImage(path:String, callback:Function):BitmapData {
completed = false;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event){completed = true;
trace("Loading completed");
var image:Bitmap = new Bitmap((loader.content as BitmapData));
callback( image ); //call the callback with the bitmap
e.target.removeEventListener(Event.COMPLETE, check);});
if (path == "")
loader.load(new URLRequest("images/default.png"));
else
loader.load(new URLRequest("images/" + path));
}
Then just you make the call to loadImage() like so:
loadImage( myPathToImage, addImage );
That is a simply resolution and does exactly what you need it to.
Super, you commented that insane line of code with while ;)
Here for you, simple QueueLoader (It loads items one by one, when you add item to the queue, you can store id of item in queue), that will help you with your task:
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
public class StackOverflow extends Sprite {
public function StackOverflow() {
addEventListener(Event.ADDED_TO_STAGE, onAdded);
}
private function onAdded(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, onAdded);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
setup();
}
private function setup():void {
//Store somewhere reference on QueueLoader to reuse it, and use queue
var loader:QueueLoader = new QueueLoader();
loader.addEventListener(QueueLoaderEvent.COMPLETE, onCompleteItem);
//Add as many images to load as you want, and store Id's of items that
//will be loaded in future, if you want...
loader.addItem("someUrl1");
loader.addItem("someUrl2");
var importantId:int = loader.addItem("someUrl3");
loader.addItem("someUrl4");
loader.addItem("someUrl6");
}
private function onCompleteItem(e:QueueLoaderEvent):void {
trace("Item loaded");
}
}
}
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.net.URLRequest;
internal class QueueLoader extends EventDispatcher {
private var _list:Array;
private var _cursor:int;
private var _loading:Boolean;
public function QueueLoader(target:IEventDispatcher = null) {
super(target);
_list = [];
}
public function addItem(url:String):int {
var item:Object = {url: url, id: ++_cursor};
_list.push(item);
loadNext();
return item.id;
}
override public function dispatchEvent(evt:Event):Boolean {
if (hasEventListener(evt.type) || evt.bubbles) {
return super.dispatchEvent(evt);
}
return true;
}
protected function loadNext():void {
if (_list.length > 0 && !_loading) {
var loader:Loader = new Loader();
var data:Object = _list[0];
var request:URLRequest = new URLRequest(data.url);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete, false, 0, true);
loader.load(request);
_loading = true;
}
}
private function onComplete(e:Event):void {
var data:Object = _list.shift();
data.content = e.currentTarget.content;
dispatchEvent(new QueueLoaderEvent(QueueLoaderEvent.COMPLETE, data.id, data));
_loading = false;
loadNext();
}
}
internal class QueueLoaderEvent extends Event {
public static const COMPLETE:String = "queueLoaderEventComplete";
private var _id:int;
private var _data:Object;
public function QueueLoaderEvent(type:String, $id:int, $data:Object, bubbles:Boolean = false, cancelable:Boolean = false) {
_id = $id;
_data = $data;
super(type, bubbles, cancelable);
}
override public function clone():Event {
return new QueueLoaderEvent(type, id, data, bubbles, cancelable);
}
public function get id():int {
return _id;
}
public function get data():Object {
return _data;
}
}
Your loadImage mehtod will look at the end:
public static function loadImage(path:String):int
{
return queueLoader.addItem(path);
}

AIR (As3) - Download large file trough iPad application

First I want to specify that I can't post source code because the project is huge.
I'm trying to download a large file (500+ MB) on iPad device.
Initially I tried with URLLoader, but than I realized that the iPad devices has a very limited resources regarding memory. Than I thought that the URLStream will download the file in chunks and with FileStream I can save this chunks on the device (like this AS3: URLStream saving files to desktop?), but I was wrong, the device crashes when I try to download a big file because the RAM of the device is not enough (more precisely this becomes too big: System.privateMemory)
Does anyone have any idea how to download a file in chunks and is it possible without using "socket connection"?
Thanks in advance.
EDIT:
Here is the code that I use (the commented lines are the version in which the FileStream las closed only after the file is downloaded.
package components.streamDownloader
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.OutputProgressEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.system.System;
import flash.utils.ByteArray;
/**
*
*/
public class StreamDownloader extends EventDispatcher
{
[Event(name="DownloadComplete", type="com.tatstyappz.net.DownloadEvent")]
[Event(name="Error", type="com.tatstyappz.net.DownloadEvent")]
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
public function StreamDownloader()
{
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
private var file:File;
//private var fileStream:FileStream;
private var urlRequest:URLRequest;
private var urlStream:URLStream;
private var waitingForDataToWrite:Boolean = false;
//--------------------------------------------------------------------------
//
// API
//
//--------------------------------------------------------------------------
public function download(urlRequest:URLRequest, file:File):void {
init();
this.urlRequest = urlRequest;
this.file = file;
//fileStream.open(file, FileMode.WRITE);
urlStream.load(urlRequest);
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
//----------------------------------
// urlStream events
//----------------------------------
protected function urlStream_openHandler(event:Event):void
{
waitingForDataToWrite = false;
dispatchEvent(event.clone());
}
protected function urlStream_progressHandler(event:ProgressEvent):void
{
trace("MEMORY:", System.totalMemoryNumber / 1024 / 1024, "MEMORY P:", System.privateMemory / 1024 / 1024, "FREE MEMORY:", System.freeMemory / 1024 / 1024, "PROGRESS:", event.bytesLoaded / event.bytesTotal );
if(waitingForDataToWrite){
writeToDisk();
}
}
protected function urlStream_completeHandler(event:Event):void
{
if(urlStream.bytesAvailable > 0)
{
writeToDisk();
}
//fileStream.close();
destory();
dispatchEvent(event.clone());
// dispatch additional DownloadEvent
dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.DOWNLOAD_COMPLETE, urlRequest, file));
}
protected function urlStream_securityErrorHandler(event:SecurityErrorEvent):void
{
dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.ERROR, urlRequest, file, event.errorID.toString()));
destory();
}
protected function urlStream_ioErrorHandler(event:IOErrorEvent):void
{
dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.ERROR, urlRequest, file, event.errorID.toString()));
destory();
}
//----------------------------------
// fileStream events
//----------------------------------
protected function fileStream_outputProgressHandler(event:OutputProgressEvent):void
{
waitingForDataToWrite = true;
}
protected function fileStream_ioErrorHandler(event:IOErrorEvent):void
{
dispatchEvent(new StreamDownloadEvent(StreamDownloadEvent.ERROR, urlRequest, file, event.errorID.toString()));
destory();
}
//--------------------------------------------------------------------------
//
// Utils
//
//--------------------------------------------------------------------------
private function init():void
{
urlStream = new URLStream();
//fileStream = new FileStream();
urlStream.addEventListener(Event.OPEN, urlStream_openHandler);
urlStream.addEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler);
urlStream.addEventListener(Event.COMPLETE, urlStream_completeHandler);
urlStream.addEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, urlStream_securityErrorHandler);
//fileStream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, fileStream_outputProgressHandler)
//fileStream.addEventListener(IOErrorEvent.IO_ERROR, fileStream_ioErrorHandler);
}
private function destory():void
{
urlStream.removeEventListener(Event.OPEN, urlStream_openHandler);
urlStream.removeEventListener(ProgressEvent.PROGRESS, urlStream_progressHandler);
urlStream.removeEventListener(Event.COMPLETE, urlStream_completeHandler);
urlStream.removeEventListener(IOErrorEvent.IO_ERROR, urlStream_ioErrorHandler);
urlStream.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, urlStream_securityErrorHandler);
//fileStream.removeEventListener(OutputProgressEvent.OUTPUT_PROGRESS, fileStream_outputProgressHandler)
//fileStream.removeEventListener(IOErrorEvent.IO_ERROR, fileStream_ioErrorHandler);
urlStream = null;
//fileStream = null;
}
private function writeToDisk():void {
/*var fileData:ByteArray = new ByteArray();
urlStream.readBytes(fileData, 0, urlStream.bytesAvailable);
fileStream.writeBytes(fileData,0,fileData.length);
waitingForDataToWrite = false;*/
var bytes:ByteArray = new ByteArray();
urlStream.readBytes( bytes );
var fs:FileStream = new FileStream();
fs.open( file, FileMode.APPEND );
fs.writeBytes( bytes );
fs.close();
}
}
}
As I said in my comment to csomakk, I have successfully downloaded 300+ MB files through AIR for desktop, iOS, and Android using the URLStream chunking method.
Pseudo code:
var stream:URLStream = new URLStream();
stream.addEventListener( PROGRESS, progressHandler );
stream.addEventListener( COMPLETE, completeHandler );
stream.load( url );
private function progressHandler( e:ProgressEvent ):void {
this.writeDataToDisk();
}
private function completeHandler( e:Event ):void {
this.writeDataToDisk();
}
private function writeDataToDisk():void {
var bytes:ByteArray = new ByteArray();
this.stream.readBytes( bytes );
var fs:FileStream = new FileStream();
fs.open( file, FileMode.APPEND );
fs.writeBytes( bytes );
fs.close();
}
That basic logic works and works just fine up to 300MB (and likely further. Though I should have tested that, now that I think about it). That was written fairly quickly so there may be some errors and I definitely shorthanded a few things, but you get the idea.
If this does not work, we need a few things from you:
Post any errors
trace out file.size / 1024 / 1024 + "MB" after the fs.close() and see how far it gets before crashing
trace out System.memory / 1024 / 1024 + "MB" after thefs.close()` so we can monitor the memory usage
For 2 and 3, we should only need the last trace statements before the crash occurs.
Alternatively, you should know that you won't be able to do anything with that 500MB file in the application. Flash simply will not load it because of its size. The only reason I managed to get away with my 300MB video files is that we were streaming them from disk, not storing the entire things into memory.
Since I am not allowed to comment under Josh's answer for some reason, I am adding my version as a separate answer. But it is heavily based on his suggestion.
The code is also available with GitHub at: https://github.com/shishenkov/ActionscriptClasses/blob/master/us/flashmx/net/LargeFileLoader.as
The prerequisite - URLStream is an awesome class but it has a glitch in it that causes a memory leak/buildup to prevent large files from loading properly. The class I'm sharing here has been tested and was able to download a sequence of 1.5GB files into an iPad Air 2 (64GB) without an issue. I assume that larger files will be OK too, since it actually overcomes the RAM storage limitation (before the glitch fix it was crashing at around 200MB).
The glitch - the raw data bytearray where you copy over the loaded bytes never gets disposed of by GC (as noted here: http://blogs.splunk.com/2007/11/16/flashas3-urlstream-memory-leak/) so, the workaround is to implement a class that uses the Josh's technique and makes sure that the bytes are disposed off after the write.
... here's the code (NOTE: this is still pre-production):
package us.flashmx.net
{
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.HTTPStatusEvent;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.system.System;
import flash.utils.ByteArray;
/**
* ...
* #author Nick Shishenkov <n#vc.am>
*/
public class LargeFileLoader extends EventDispatcher
{
private var _url:String = "";
private var _filePath:String = "";
private var _fileStream:FileStream = new FileStream;
private var _urlStream:URLStream = new URLStream;
private var _localFile:File;
private var _bytesLoaded:Number;
public function LargeFileLoader()
{
super(null);
//
_urlStream.addEventListener(Event.OPEN, _onOpen, false, int.MIN_VALUE, true);
_urlStream.addEventListener(ProgressEvent.PROGRESS, _onProgress, false, int.MIN_VALUE, true);
_urlStream.addEventListener(Event.COMPLETE, _onComplete, false, int.MIN_VALUE, true);
_urlStream.addEventListener(IOErrorEvent.IO_ERROR, _onError, false, int.MIN_VALUE, true);
_urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _onSecurityError, false, int.MIN_VALUE, true);
_urlStream.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, _onHTTPStatus, false, int.MIN_VALUE, true);
_urlStream.addEventListener(HTTPStatusEvent.HTTP_STATUS, _onHTTPStatus, false, int.MIN_VALUE, true);
}
private function _onHTTPStatus(e:HTTPStatusEvent):void
{
dispatchEvent(e.clone());
}
public function load(remoteURL:String, localPath:String, overwrite:Boolean = true):void
{
_url = remoteURL;
_filePath = localPath;
//
_localFile = new File(_filePath);
_bytesLoaded = 0;
//
if (overwrite && _localFile.exists)
{
_localFile.deleteFile();
}
//
_urlStream.load(new URLRequest(url));
_fileStream.open(_localFile, FileMode.APPEND);
}
private function _onOpen(e:Event):void
{
dispatchEvent(e.clone());
}
private function _onSecurityError(e:SecurityErrorEvent):void
{
dispatchEvent(e.clone());
}
private function _onError(e:IOErrorEvent):void
{
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.text));
}
private function _onProgress(e:ProgressEvent):void
{
//
trace(" -> _onProgress: " + _urlStream.length + " | " + e.bytesLoaded + " / " + e.bytesTotal);
//
_writeStreamBytes();
//
dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, e.bytesLoaded, e.bytesTotal));
}
private function _onComplete(e:Event):void
{
_writeStreamBytes();
//
dispatchEvent(new Event(Event.COMPLETE));
}
private function _writeStreamBytes():void
{
var bytes:ByteArray = new ByteArray();
_urlStream.readBytes( bytes );
_fileStream.writeBytes( bytes );
//
_bytesLoaded += bytes.length;
//clear buffer (if the array stays non-null it will lead to a memmory leak
bytes = null;
}
public function get url():String
{
return _url;
}
public function get filePath():String
{
return _filePath;
}
public function get bytesLoaded():Number
{
//_localFile.size;
return _bytesLoaded;
}
public function dispose():void
{
try{ _fileStream.close(); }catch (err:Error){};
//
try{ _urlStream.close(); }catch (err:Error){};
//
_urlStream.removeEventListener(Event.OPEN, _onOpen);
_urlStream.removeEventListener(ProgressEvent.PROGRESS, _onProgress);
_urlStream.removeEventListener(Event.COMPLETE, _onComplete);
_urlStream.removeEventListener(IOErrorEvent.IO_ERROR, _onError);
_urlStream.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, _onSecurityError);
_urlStream.removeEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, _onHTTPStatus);
_urlStream.removeEventListener(HTTPStatusEvent.HTTP_STATUS, _onHTTPStatus);
//
_urlStream = null;
_fileStream = null;
//
System.gc();
}
}
}
I ran several device tests with Scout CC and the memory stays down at all times (no buildups whatsoever). I will be testing some older iOS devices later on this week. For the record: I am using Adobe AIR 24.0.0.180
here's a sample use:
package us.flashmx.net
{
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.ProgressEvent;
/**
* ...
* #author ...
*/
public class LargeFileLoader_DEMO extends DisplayObject
{
private var _largeFilesLoader:LargeFileLoader;
public function LargeFileLoader_DEMO()
{
super();
//
init_largeFilesLoader("http://A.Large.File.URL/", "/The/Absolute/Local/Path");
}
public function dispose_largeFilesLoader():void
{
//
if (_largeFilesLoader != null)
{
//clear listeners
_largeFilesLoader.removeEventListener(ProgressEvent.PROGRESS, _onFileLoaderProgress);
_largeFilesLoader.removeEventListener(Event.COMPLETE, _onFileLoaderComplete);
//dispose
_largeFilesLoader.dispose();
//free mem
_largeFilesLoader = null;
}
}
private function init_largeFilesLoader(fURL:String, fPath:String):void
{
//
_largeFilesLoader = new LargeFileLoader;
//
_largeFilesLoader.addEventListener(ProgressEvent.PROGRESS, _onFileLoaderProgress, false, int.MIN_VALUE, true);
_largeFilesLoader.addEventListener(Event.COMPLETE, _onFileLoaderComplete, false, int.MIN_VALUE, true);
//
_largeFilesLoader.load(fURL, fPath);
}
private function _onFileLoaderComplete(e:Event):void
{
trace("All done!");
dispose_largeFilesLoader();
}
private function _onFileLoaderProgress(e:ProgressEvent):void
{
_largeFilesLoader.bytesLoaded;
}
}
}
... I hope that helps !
cheers
-Nick

ActionScript 3 custom "call-later" almost working, expert wanted!

I have this class ('Scheduler.as'):
package
{
import flash.events.TimerEvent;
import flash.utils.Timer;
public class Scheduler
{
private var m_tmr:Timer = null;
private var m_the_this:* = null;
private var m_function:Function = null;
private var m_args:Array = null;
public function Scheduler(the_this:*, f:Function, interval:int, args:Array = null)
{
this.m_the_this = the_this;
this.m_function = f;
this.m_args = args;
if (this.m_args.length == 0)
this.m_args = null;
this.m_tmr = new Timer(interval, 1);
this.m_tmr.addEventListener(TimerEvent.TIMER, on_timer);
this.m_tmr.start();
}
private function on_timer(e:TimerEvent):void
{
if (this.m_args == null)
this.m_function.call(this.m_the_this);
else
this.m_function.call(this.m_the_this, this.m_args);
}
public static function schedule_call(the_this:*, f:Function, interval:int, ...args):Scheduler
{
return new Scheduler(the_this, f, interval, args);
}
}
}
And here's an AS3 FlashDevelop app that uses it ('Main.as'):
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
Scheduler.schedule_call(this, this.test_func_NO_PARAMS, 0);
Scheduler.schedule_call(this, this.test_func_ONE_PARAM, 0, 123);
Scheduler.schedule_call(this, this.test_func_TWO_PARAMS, 0, "HELLO", "WORLD");
}
private function test_func_NO_PARAMS():void
{
trace("No params was called successfully!");
}
private function test_func_ONE_PARAM(some_number:int):void
{
trace("One param was called successfully! 'some_number' = " + some_number);
}
private function test_func_TWO_PARAMS(stringA:String, stringB:String):void
{
trace("Two params was called successfully! 'stringA' = " + stringA + ", 'stringB' = " + stringB);
}
}
}
So as you see in your test-run the first two calls work fine, the one that calls a function that takes no parameters and the one that takes one parameter.
The problem is when I need to pass more than one parameter!
Solving the issue:
Well, I know it'd be solved if I could simply retain the ...args as is, and pass it on to the this.m_function.call call.
Another way, maybe is to have some sort of a foreach loop which would feed the designated ...args when the time comes, yet again, how would I refer/pass it?
There must be a nice trick here to make it work, you're welcome to sweat with me on this one!
Try changing this line:
this.m_function.call(this.m_the_this, this.m_args);
to this:
this.m_function.apply(this.m_the_this, this.m_args);
apply passes the parameters to the applied function as a list instead of a single array (the same effect as if you wrote the_function(arg[0],arg[1],arg[N])).
Edit:
Maybe I'm not understanding your problem, but this simplified sample of your code works fine (I'm not using a Timer and I'm not building any instance but I think the main thing here is how apply works; taking an array of parameters and passing them as a list of variable parameters to the invoked function):
private function arg0():void {
trace("arg0");
}
private function arg1(a:*):void {
trace("arg1");
}
private function arg2(a:*,b:*):void {
trace("arg2");
}
private function arg3(a:*,b:*,c:*):void {
trace("arg3");
}
private function test():void {
schedule_call(this,arg0,10);
schedule_call(this,arg1,10,1);
schedule_call(this,arg2,10,1,2);
schedule_call(this,arg3,10,1,2,3);
}
public function schedule_call(the_this:*, f:Function, interval:int, ...args):void
{
var m_args:Array = args;
f.apply(the_this, m_args);
}

sound stream stops loading

I have a little sound stream script here, but sometimes if you press play to the next track before the current has done loading the next track doesn't comeplete loading
package player {
import flash.events.Event;
import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
import player.Loader_bar;
public class Stream extends Sprite {
private var _Sound = null;
private var _Channel = null;
private var isLoading = false;
private var _Loader_bar = null;
public var loader_color = null;
public var loader_width = 0;
public var loader_height = 0;
private var i = 0;
public function Stream(){
this._Loader_bar = new Loader_bar();
addChild(this._Loader_bar);
}
public function cnstr(){
this._Loader_bar.color = this.loader_color;
this._Loader_bar.w = this.loader_width;
this._Loader_bar.h = this.loader_height;
this._Loader_bar.cnstr();
}
public function play(url){
this.stop();
this.close();
this._Sound = new Sound();
this.isLoading = true;
this.addEventListener(Event.ENTER_FRAME, listener_bytesLoaded);
this._Sound.addEventListener(Event.COMPLETE, listener_loadedComplete);
this._Sound.load(new URLRequest(url));
this._Channel = this._Sound.play();
}
public function stop(){
if(this._Channel){
this._Channel.stop();
}
}
private function close(){
if(this.isLoading){
this._Sound.close();
}
}
private function listener_loadedComplete(event){
this.close();
this.isLoading = false;
this.removeEventListener(Event.ENTER_FRAME, listener_bytesLoaded);
}
private function listener_bytesLoaded(event){
var float = this._Sound.bytesLoaded / this._Sound.bytesTotal;
this._Loader_bar.progress(float);
var data = {
i : this.i,
float : float,
loaded : this._Sound.bytesLoaded,
total : this._Sound.bytesTotal
};
ExternalInterface.call('swf2js', 'tst_progress', data);
this.i++;
}
}
}
close():void
Closes the stream, causing any download of data to cease.
try this:
create a boolean like hasLoaded, set it to false. When the sound successfully loads set it to true.
Then when you play a sound you can test for hasLoaded in your play() function. If you have called play() before your previous sound has loaded, hasLoaded will be false, in which case you call this._Sound.close() before creating and loading the new sound. The reason for testing instead of just calling close() is so if you have paused the stream you don't have to reload it to play it again.
ADDITION:
Concerning the load not reporting properly, you have your progress logic set up incorrectly. Try this:
1) import flash.events.ProgressEvent
2) For the listener, replace this.addEventListener(Event.ENTER_FRAME, listener_bytesLoaded); in your play() method with this._Sound.addEventListener(ProgressEvent.PROGRESS, listener_bytesLoaded);
3) Change your listener_bytesLoaded() method to the following:
private function listener_bytesLoaded(event:ProgressEvent)
{
var percent = event.bytesLoaded / event.bytesTotal;
this._Loader_bar.progress(percent);
var data = {
i : this.i,
float : percent,
loaded : event.bytesLoaded,
total : event.bytesTotal
};
ExternalInterface.call('swf2js', 'tst_progress', data);
this.i++;
}
4) Change this.removeEventListener(Event.ENTER_FRAME, listener_bytesLoaded);
in your listener_loadedComplete() method to this._Sound.removeEventListener(ProgressEvent.PROGRESS, listener_bytesLoaded); and then move it out of that method and place it inside the conditional in the close() method.
NOTE - I didn't actually compile this but I think its good to go. Hope that helps. :)