How to detect the presense of an SWF in the browser cache? - actionscript-3

I have an AS3 application that loads various SWFs at runtime. The loading animation that is being used has a fairly long in and out animation that I don't want to show if the target SWF is in the browser cache.
So at the moment each SWF is loaded in as required using Greensock's SWFLoader in a basic manner:
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
loader = new SWFLoader("mySWF.swf", {name:"sectionLoader",context:context,auditSize:true,onOpen:onLoadInit,onProgress:onLoadProgress, onComplete:onCompleteLoadHandler, onError:onLoadErrorHandler});
loader.load();
My goal is to do something before calling loader.load(); to determine if the load operation will require the request to go beyond the browser cache, but before I get into R&Ding something I thought I'd ask if anyone has already done something similar.
A few more thoughts I've had so far:
Just keeping track of what has been loaded in AS3 isn't good enough because if the user clears their cache they might be left loading a large SWF on a slow connection with no indicator.
Might a combination of LoaderItem.httpStatus and LoaderItem.auditSize() be worth investigating?
Is there a better loading framework for AS3 that I should be looking into instead of the Greensock classes?
Ideally I would prefer to also have some kind of version detection to span sessions that could be months apart, but one step at a time.

when you are doing any HTTP request, the responce comes up with HTTPStatus property. In AS3 you just need to chek if
HttpStatusEvent.status == 304
And for httpStatus in greensock library.
Basically 304 code means that no chages has been made on server side to the resource which user has requested. Which eventually leads to conclution that the resource is in the cache.
UPDATE
If this will not fit your needs try storing some variable for should you play the animation or not in Cookies or in Session variables.

Related

Why does URLStream sometimes not fire Event.COMPLETE?

I've got an application that is downloading several large binary files and saving them to disk. On some machines it works fine and on some other machines every once in a while a download will proceed to 99.9% complete and the URLStream object will not fire Event.COMPLETE
This is almost identical to the issue that appears here:
Why does URLStream complete event get dispatched when the file is not finished loading?
I've tried using the 'Cache Bust' method described in one of the answers but still no dice.
Any help would be appreciated.
Here is some sample code to help illustrate what I am trying to do:
var contentURL:String = "http://some-large-binary-file-in-amazon-s3.tar";
var stream:URLStream = new URLStream();
stream.addEventListener(Event.COMPLETE, function(e:Event):void{
//This should fire when the file is done downloading
//On some systems this fails to fire once in a while
//On other systems it never fails to fire
});
stream.addEventListener(ProgressEvent.PROGRESS, function(pe:ProgressEvent):void{
//Write the bytes available in the stream and save them to disk
//Note that a download will reach 100% complete in terms of total progress but the 'complete' event might still not fire.
});
var urlRequest:URLRequest = new URLRequest(contentURL);
//Here we might add some headers to the URL Request for resuming a file
//but they don't matter, the 'Event.COMPLETE' will fail to fire with our without
//these headers
addCustomHeaders( urlRequest );
stream.load( urlRequest );
Imo this is a code meant to fail where you purposely give up any control on whatever is going on and just assume that everything would work by itself and go well. I never had any problems whatsoever with the URLStream class but here's basically what I never do:
I never not register all the different error event available (you don't register any).
I never use anonymous listeners. Even though they are supposed to not be GC until the download is complete this is imo an unnecessary unsafe bet especially since it's not rare for the URLStream to idle a little while loading the last bits. I would not be surprised if removing those anonymous listeners would actually fix the problem.

How to play a seamless loop from an AudioSprite in AS3, without SAMPLE_DATA

I created a batch of sounds assembled with this tool:
AudioSprite
https://github.com/tonistiigi/audiosprite
The output is generally used for JS libraries, such as Howler, Zynga Jukebox, or SoundJS - but I wanted to see if it's possible to implement in AS3.
I started creating a Sound player that can load, parse and play the sounds based on the JSON and MP3 file this tool generates.
So far so good! ... except for loops.
Now, the big question is - is there a way to play a Sound-loop seamlessly given that all music & sounds coexist in the same MP3 file, and it has a start & end range to play and stop it?
Example of how the sounds are placed in the file:
mygame_sounds.mp3 = [BUZZ + LASER + BOING ... + TRACKLOOP]
I'm looking for a solution that does not involve using the SAMPLE_DATA Event (given it eats up a lot of CPU usage). If there's no way around it, please explain why.
So far I've had mild success using flash.utils.Timer objects triggered after a given AudioSprite's duration, but it's not consistent.
To stop / dispose of a non-looping sound, I rely on a Master Timer (running at very short intervals) and that seems to "cut" the sample appropriately. But I already tried using this Master Timer to play a looped-sound over and over - same latency issues.
Is there any method to predict / measure how much latency is to be expected by the time the sound completes one pass?
In SoundJS we could not find a way to allow smooth looping of audio sprites in AS3 and went with a timer. We found Web Audio was the only api that allowed smooth looping, and therefore recommended staying away from audiosprites for sounds that needed to loop smoothly if any other plugin might be used.
Hope that helps.
The reason of why you can't get smooth loops of a track retrieved from a larger audio file is that you cannot check sound position faster than once per SWF frame, which length depends on stage.frameRate and total processing time of your application and is generally varied. So, if your looping sounds lasts say 5.123 seconds (I don't care how many samples, just that its length does not make a full number of frames regardless of stage.frameRate), your sound will attempt to play for either 5.125 seconds (205 frames at 40 fps, IMO best bet for this particular sound), 5.133 seconds (154 frames at 30 fps) or some weird number of frames if the SWF would experience lag. The excess milliseconds cannot be totally controlled by any means due to AS3/Flash engine optimization. So, consider not using audio sprites and shift into audio packs (several audio files in an SWF, or one sound in an MP3).
Although I'm still working on the perfect solution, this is the best I could come up with:
Load the JSON file / ByteArray.
Parse the JSON file to obtain each sprites' ID, start and end times.
Load the MP3 file / ByteArray (requires loadCompressedDataFromByteArray()) into a master Sound object.
Once loaded, check if any sprites are marked as "loops".
Create separate Sound objects for the above loops, and extract the portion from the master Sound via loadPCMFromByteArray() with some "magic-numbers" (details below).
To play a one-shot sound, call the master Sound's play(sprite.start * 1000) (depending on the format, usually the JSON's start values are in seconds, needs to be in milliseconds).
To play a seamless-loop sound, call the individual Sound object's (created in step #5) play(0, 9999) method.
I won't go too deep in details on how to stop the sounds (SoundChannel.stop(), bam!), but I'll explain the "magic-numbers" mentioned above. See this snippet:
var goldenOffset:UInt = (64 << 5);
var goldenDuration:UInt = (64 << 2);
var sampleRate:UInt = 44100;
for (id in loops) {
var sprite:AudioSpriteItem = _mapSprites.get(id);
var loop:Sound = _mapLoops.get(id);
var sampleBytes = new ByteArray();
var samplesTotal:UInt = cast(sprite.duration * sampleRate + goldenDuration);
var samplesStart:UInt = cast(sprite.start * sampleRate + goldenOffset);
_sound.extract(sampleBytes, samplesTotal, samplesStart);
sampleBytes.position = 0;
loop.loadPCMFromByteArray(sampleBytes, samplesTotal, "float", true);
}
Quite honestly, these magic goldenOffset and goldenDuration values were just found via Trial-and-Error. I could get close to a seamless loop without them by just calculating the start and duration with the sampleRate (assuming 44100 by default), but each endings had a bit of a hiccup to it.
After several adjustments, those couple "64 left bit-shifted" values made the loops sound smoother.
I posted the Haxe project on github (compiled SWC also available in /bin folder) if you wish to try it / read through the code.
FLAudioSprite
Github page: https://github.com/bigp/FLAudioSprite
SWF Demo (Download): bit.ly/FLAudioSpriteSWFDemo

Saving flash game state to server after window.onbeforeunload when swf is cross-domain

My Facebook app is a flash game. I want the game swf to save its latest state to the server when the window unloads. Since I embed the swf with swfobject, I use its embed handler to add a onbeforeunload listener to window:
function embedHandler(event)
{
shell=event.ref;
window.onbeforeunload=function(event)
{
shell.message("save", null);
//delay the unloading a bit so flash has time to contact server
var now = new Date().getTime();
var later=now+50;
while (now < later)
{
now = new Date().getTime();
}
}
}
Here's the problem. This works every time when the swf is loaded directly from the app (a rails app). It never works when the swf is loaded from Amazon.
All the cross-domain issues are worked out between the swf and the app--the rails app accepts calls from Amazon swf, and the Amazon swf loads data from the rails app.
ExternalInterface also works for both outgoing and ingoing calls. But I suspect this is nonetheless a browser security issue, since the inward-going ExternalInterface call only fails when:
it is called from inside the window.onbeforeunload handler
the swf originates from Amazon.
What is the problem? How does one unobtrusively save game state when the game is from a CDN and the save is triggered by onbeforeunload in Javascript? Or is there a better way to accomplish this same thing?
Testing in Firefox.
ExternalInterface also works for both outgoing and ingoing calls. But
I suspect this is nonetheless a browser security issue, since the
inward-going ExternalInterface call only fails when:
it is called from inside the window.onbeforeunload handler
the swf originates from Amazon.
From the sounds of it you worked out all the security issues.
It is more likely a lack of understanding on your part on what is going on behind the scene when onbeforeunload is triggered.
This is a function that will not wait for your "game.swf" to finish the call back via ExternalInterface.This is why you added a stalling mechanism to delay that process. However, I will assume here that this works from the rails app because that is a local server and you are not subject to the lag monster.
Now you might be thinking well I put in a delay it should work. Well that delay is on 50 milliseconds. Try increasing to to 5000(5 seconds) and you should see it start to work on the cdn.
The saving of data should be controlled via the flash app and not triggered by an outside source.
In the game itself you should have milestones that should trigger a save event.
In closing I do want to add that is by far the worst method you could use to save information up to a server. onbeforeunload is unreliable and is subject to cross browser issues let alone putting a lag loop in the JavaScript is just a bad idea and in the end just annoy your users to the point that they won't return.

Is there reliable method of ensuring crossdomain policy files have been retrieved for all Facebook image servers?

I've recently started putting together a Facebook Connect AS3 app and retrieving objects and images through the Graph API.
Running anywhere but locally, I receive security errors of the form:
SecurityError: Error #2122: Security sandbox violation: Loader.content: xxxx cannot access http://photos-a.ak.fbcdn.net/xxxx.jpg
A policy file is required, but the checkPolicyFile flag was not set when this media was loaded.
If I add a line of the form:
Security.loadPolicyFile("ht_tp://photos-a.ak.fbcdn.net/crossdomain.xml");
-then I'm fine for that server, but it seems that there are any number of domains with the photos-[letter] format. I've added the one for each in the alphabet - which happily retrieves crossdomain files successfully - but it doesn't seem like a nice solution, and doesn't accommodate any new hosting setups Facebook may will implement in the future.
One thing I'd considered was retrieving the crossdomain policy file on a per image basis, capturing the domain from the image URL before making the image request. Unfortunately, at least via the Graph solution (and I haven't looked too closely at the others), their servers resolve the image url after the request is made, from something more generic like:
ht_tps://graph.facebook.com/[objectId]/picture?type=small&access_token=[accessToken]
Has anyone found a more dependable means of ensuring that images can be retrieved without security sandbox violations? Or do Facebook maintain a definitive list that developers need to keep an eye on?
Thanks!
Load the facebook crossdomains on the initial of your application as below;
Security.allowDomain("*");
Security.allowInsecureDomain("*");
Security.loadPolicyFile("http://graph.facebook.com/crossdomain.xml");
Security.loadPolicyFile("https://graph.facebook.com/crossdomain.xml");
Security.loadPolicyFile("http://profile.ak.fbcdn.net/crossdomain.xml");
Security.loadPolicyFile("https://profile.ak.fbcdn.net/crossdomain.xml");
Security.loadPolicyFile("http://profile.cc.fbcdn.net/crossdomain.xml");
Security.loadPolicyFile("https://profile.cc.fbcdn.net/crossdomain.xml");
Security.loadPolicyFile("http://fbcdn-profile-a.akamaihd.net/crossdomain.xml");
Security.loadPolicyFile("https://fbcdn-profile-a.akamaihd.net/crossdomain.xml");
Security.loadPolicyFile("http://fbcdn-sphotos-a.akamaihd.net/crossdomain.xml");
Security.loadPolicyFile("https://fbcdn-sphotos-a.akamaihd.net/crossdomain.xml");
and then whenever you want to load an image from facebook, set the checkPolicy flag to true using the Loader's LoaderContext as below;
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
context.checkPolicyFile = true;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadFacebookPhoto);
loader.load(new URLRequest(YOUR_FACEBOOK_PHOTO_URL),context);
private function onLoadFacebookPhoto(e:Event):void
{
addChild(Bitmap(LoaderInfo(e.target).content));
}
Ideally I would guess that you'd want Flash to get the policy file on its own, rather than triggering it with Security.loadPolicyFile. Have you tried simply setting the checkPolicyFile flag for your Loader's LoaderContext?
Alternately, I believe that when you use URLLoader instead of Loader, Flash will request a policy file automatically, so you could try that as well. The tricky thing is that if you use Loader, Flash will let you display what you've loaded even without a crossdomain policy, so it doesn't load one unless you tell it to. When you use URLLoader, the load itself is not allowed unless there's a policy file, so Flash gets it automatically.

How to check for the FLV file existence before playing that using FLVPlayback in Action Script 3?

I'm very new to the Action Scripting, I'm using the FLVPlayback class to play my FLV files.
If I'm trying to play a FLV file which is not existed yet then I am getting a "VideoError: 1000" with message of Unable to make connection to server or to find FLV on server.
I want to check for the FLV file existence using the file URL or path, before playing that FLV by FLVPlayback. Can anybody please suggest a way to do that.
Thanks
The only way to catch the error safely is to listen for the fl.video.VideoEvent.STATE_CHANGE event and act accordingly. Here's a little code snippet on how to do so:
import fl.video.FLVPlayback;
import fl.video.VideoEvent;
import fl.video.VideoState;
var videoPlayer:FLVPlayback;
videoPlayer.addEventListener( VideoEvent.STATE_CHANGE, onVideoStateChange );
/** Bad source **/
videoPlayer.source = "http://www.helpexamples.com/flash/video/caption_video_error.flv";
/** Good source **/
//videoPlayer.source = "http://www.helpexamples.com/flash/video/caption_video.flv";
function onVideoStateChange( evt:VideoEvent ):void
{
var videoPlayer:FLVPlayback = evt.target as FLVPlayback;
switch( evt.state )
{
case VideoState.CONNECTION_ERROR:
trace( 'Connection error' );
/**
* Once you hit this event, you should run some logic to do one or more of the following:
* 1. Show an error message to the user
* 2. Try to load another video
* 3. Hide the FLVPlayback component
*/
break;
default:
trace( 'Player is: ' + evt.state );
}
}
For a full list of possible VideoState constants, visit fl.video.VideoState.
I think you may be able to make use of the stateChange event. One of the possible event types is VideoState.CONNECTION_ERROR and another is VideoState.DISCONNECTED which may also work.
Try giving that a shot.
If those don't work, the only way I can think of would be to either do a HEAD or GET request for the flv before you attempt to load it. Only a successful response would trigger the video loading through the normal method. I don't remember whether Flash supports HEAD requests, but if it does that would certainly be the better option.
If Flash does not support HEAD requests then you may be better off having a simple, server-side script that could verify the existence of the flv before you actually request if. That way you can use a simple GET request without having to retrieve the whole file.
INLINE THINKING
I am just thinking, another possible solution using GET would be to cancel the load as soon as bytesLoaded > 1K (for example), or something like that. As long as you are checking for a size greater than the 404 response you are getting, you should be able to assume the flv is being loaded.