Is it possible in an air application to start a download, pause it and after that resume it?
I want to download very big files (1-3Gb) and I need to be sure if the connection is interrupted, then the next time the user tries to download the file it's start from the last position.
Any ideas and source code samples would be appreciated.
Yes, you would want to use the URLStream class (URLLoader doesn't support partial downloads) and the HTTP Range header. Note that there are some onerous security restrictions on the Range header, but it should be fine in an AIR application. Here's some untested code that should give you the general idea.
private var _us:URLStream;
private var _buf:ByteArray;
private var _offs:uint;
private var _paused:Boolean;
private var _intervalId:uint;
...
private function init():void {
_buf = new ByteArray();
_offs = 0;
var ur:URLRequest = new URLRequest( ... uri ... );
_us = new URLStream();
_paused = false;
_intervalId = setInterval(500, partialLoad);
}
...
private function partialLoad():void {
var len:uint = _us.bytesAvailable;
_us.readBytes(_buf, _offs, len);
_offs += len;
if (_paused) {
_us.close();
clearInterval(_intervalId);
}
}
...
private function pause():void {
_paused = true;
}
...
private function resume():void {
var ur:URLRequest = new URLRequest(... uri ...);
ur.requestHeaders = [new URLRequestHeader("Range", "bytes=" + _offs + "-")];
_us.load(ur);
_paused = false;
_intervalId = setInterval(500, partialLoad);
}
if you are targeting mobile devices, maybe you should take a look at this native extension: http://myappsnippet.com/download-manager-air-native-extension/ it supports simultaneous resumable downloads with multi-section chunks to download files as fast as possible.
Related
I'm coding an app for Android and iOS devices using Flex. I've an SQLite db already created and I want access to his data. I've seen some code looking with google but I'm not able to run nothing.
I would appreciate if you can explain me where I've to put the file.db into my project and if you share me some simple code that allow to connect to file.db and select some rows.
Sorry for my bad english and thanks in advance.
The code bellow will return an object with the data from the database.
package bd
{
public class SQLPadConnection extends SQLStatement
{
protected var _DBFilePatch:String = "DB.sqlite";
protected var _conn:SQLConnection = new SQLConnection();
public function SQLPadConnection()
{
var folder:File = File.userDirectory.resolvePath('com.app.directoryName');
if (!folder.exists) {
folder.createDirectory();
}
_conn.open(folder.resolvePath(_DBFilePatch));
this.sqlConnection = _conn;
super();
}
public function getSQLData(SQL:String = ""):Object
{
if (StringUtil.trim(SQL) != "")
{
this.begin();
this.text = SQL;
this.execute();
this.commit();
return this.getResult();
}
else
return null;
}
}
}
To use that, just do like bellow:
var res:Object = new Object();
var SQL:SQLPadConnection = new SQLPadConnection();
res = SQL.getSQLData("SELECT * FROM TABLE");
res = SQL.getSQLData("INSERT INTO/ UPDATE/ DELETE...");
Hope this help someone.
I'm going to assume you actually meant AIR, this runtime is what allows you to connect to SQLite. In which case there is gobs of information just in the reference alone...
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/data/SQLConnection.html
I'm trying to drop in sqlite support for saving the score and some flags. I need to open the db if it exists, then init game values based on db. If the db does not exist, I need to create/init it. The code below compiles but crashes for unknown reason.
package mygame;
<snip imports>
import sys.db.Types;
class ScoreDB extends sys.db.Object {
public var id : SId;
public var dbscore1 : SInt;
public var dbsound : SInt;
public var dbscore2 : SInt;
}
class mygame extends Sprite {
<snip var defines>
public function new () {
// start sqlite code
sys.db.Manager.initialize();
// db does exist
// then read values
// currentScore = score1.dbscore1;
// doSound = score1.dbsound;
// doScore = score1.dbscore2;
// db does not exist:
var cnx = sys.db.Sqlite.open("mybase.db");
sys.db.Manager.cnx = cnx;
sys.db.TableCreate.create(ScoreDB.manager);
var score1 = new ScoreDB();
score1.id = 0;
score1.dbscore1 = 0;
score1.dbsound = 0;
score1.dbscore2 = 0;
score1.insert();
currentScore = 0;
doSound = 0;
doScore = 0;
cnx.close();
// end sqlite code
super ();
initialize ();
construct ();
newGame ();
}
I actually just solved the same issue.
The problem is that the app is essentially a .zip file and cannot be edited. You need to create (and later access) the DB in the app storage directory.
To access the directory use following code:
private static var localStoragePath:String = openfl.utils.SystemPath.applicationStorageDirectory;
There is a known bug that the IDE's don't show the SystemPath class, but don't mind it, it will compile without problem.
Later, with your common tools you can read and write the directory, create new folders etc.
Here's a quick test, to make sure it works and doesn't crash:
// creating a test folder
sys.FileSystem.createDirectory(openfl.utils.SystemPath.applicationStorageDirectory + "/testFolder");
var form:TextFormat = new TextFormat(null, 22, 0xFFFFFF);
// getting contents of the storage dir
var arr = sys.FileSystem.readDirectory(openfl.utils.SystemPath.applicationStorageDirectory);
var tf:TextField = new TextField();
tf.defaultTextFormat = form;
tf.width = Lib.current.stage.stageWidth;
tf.height = Lib.current.stage.stageHeight;
tf.multiline = true;
tf.wordWrap = true;
tf.text = arr.toString();
addChild(tf);
as you'll see, the newly created folder is there. You can delete the line that creates the folder and you'll see it's safe.
Oh, an don't forget to add Android permissions in the XML file:
<android permission="android.permission.WRITE_EXTERNAL_STORAGE"/>
As far as I know there is no sqlite definitions in openfl. And the ones you use are just normal cpp target definition. I suppose the problem is they don't work on android. Even more: I'm quite sure the api definitions are ok, but it tries to load dll with a wrong name, which probably kills your app without even letting out an error. Try to look into implementation(it is short and easy to understand) and change the dll name.
I have several sounds, how can I play them one by one. When I just use play(), the sounds play simultaneously. I also tried to handle SOUND_COMPLETE event, but it doesn't work.
sampleMP3A.play().addEventListener(
Event.SOUND_COMPLETE,
function(event:ResultEvent, o:Object):void {
sampleMP3B.play();
});
sampleMP3A plays well, but sampleMP3B doesn't play.
private var sounds:Array // it contain your sounds.
private var ind:Number = 0;
private function playSounds():void
{
var req:URLRequest = new URLRequest(sounds[ind]);
var s:Sound = new Sound(req);
var soundChannel:SoundChannel = new SoundChannel;
soundChannel = s.play();
soundChannel.addEventListener(Event.SOUND_COMPLETE,onComplete);
}
private function onComplete(event:Event):void
{
ind++;
playSounds();
}
I have a set of sound clips to be played one after another in a sequence with couple of time interval inbetween.
In my case, its a question - followed by the set of four options.
When I write the below code, all the audop files start together at same time. How can I had time delay inbetween, so that the second clip plays only after the first one is over, and the third one starts playing, only when the second option is over.
I am with Flex AIR AS 3. See code below. Thanks in advance.
private function playCoundClips(): void
{
//set audio clips
var questionClipSource : String = "assets/quiz_voiceovers/" + questionCode + "Q.mp3";
var optionAClipSource : String = "assets/quiz_voiceovers/" + questionCode + "a.mp3";
var optionBClipSource : String = "assets/quiz_voiceovers/" + questionCode + "b.mp3";
var optionCClipSource : String = "assets/quiz_voiceovers/" + questionCode + "c.mp3";
var optionDClipSource : String = "assets/quiz_voiceovers/" + questionCode + "d.mp3";
playThisClip(questionClipSource);
playThisClip(optionAClipSource);
playThisClip(optionBClipSource);
playThisClip(optionCClipSource);
playThisClip(optionDClipSource);
}
private function playThisClip(clipPath : String) : void
{
try
{
clipPlayingNow = true;
var soundReq:URLRequest = new URLRequest(clipPath);
var sound:Sound = new Sound();
var soundControl:SoundChannel = new SoundChannel();
sound.load(soundReq);
soundControl = sound.play(0, 0);
}
catch(err: Error)
{
Alert.show(err.getStackTrace());
}
}
Thanks
Sumit
The problem is you are spawning multiple asynchronous calls. Implement a complete call back function on Sound and then call your playThisClip function inside the callback function. (You can sleep for predefined time before calling)
Time delay, is a very bad idea (in 99% of the case).
Have a look at the SOUND_COMPLETE event (see the doc)
This event is triggered when the sound stops playing.
So, it's now very easy to play sounds in sequence.
A simple example (not tested but idea is here) :
//declare somewhere a list of sounds to play
var sounds:Array=["sound_a.mp3","sound_a.mp3"];//sounds paths
//this function will play all sounds in the sounds parameter
function playSounds(sounds:Array):void{
if(!sounds || sounds.length==0){
//no more sound to play
//you could dispatch an event here
return;
}
var sound:Sound=new Sound();
sound.load(new URLRequest(sounds.pop()));
var soundChannel:SoundChannel = sound.play();
soundChannel.addEVentListener(Event.SOUND_COMPLETE,function():void{
soundChannel.removeEventListener(Event.SOUND_COMPLETE,arguments.callee);
playSounds(sounds);
});
}
This helped me
http://livedocs.adobe.com/flex/3/html/help.html?content=Working_with_Sound_09.html
One need to write code for:
sound.addEventListener(Event.ENTER_FRAME, onEnterFrame);
soundControl.addEventListener(Event.SOUND_COMPLETE, onPlaybackComplete);
private function onEnterFrame(event:Event):void
{
var estimatedLength:int =
Math.ceil(sound.length / (sound.bytesLoaded / sound.bytesTotal));
var playbackPercent:uint =
Math.round(100 * (soundControl.position / estimatedLength));
}
private function onPlaybackComplete(event:Event):void
{
Alert.show("Hello!");
}
I'm trying to use the native flex serialization/deserialization process to save/load state of a workspace in flex. In order to save or load a file I have to use my servlet, which just bounces back the bytes from the input stream to the output stream. Here's a basic outline of my Flex code:
Serialized Object Container:
public class MyWorkspace {
public var id : String;
public var url : String;
public var objectCollection : ArrayCollection; // Contains MySubObjects
}
Serialized sub object:
public class MySubObject
{
public var name:String;
public var csv:String;
}
Visual Element Constructor:
public function VisualSandbox(){
registerClassAlias("MyWorkspaceAlias", MyWorkspace);
registerClassAlias("MySubObjectAlias", MySubObject);
}
Visual Element Event Handlers:
public function onSaveButtonClick(event : MouseEvent) : void
{
var ws : MyWorkspace = new MyWorkspace();
ws.id = "ID";
ws.url = "URL";
ws.objectCollection = new ArrayCollection(veObjCollectionAC.source.slice());
var ba : ByteArray = new ByteArray();
ba.writeObject(ws);
ba.position = 0;
var fr : FileReference = new FileReference();
// There's no need for me to put the rest so
...
fr.download(urlRequest, "workspace.ws");
}
public function onLoadButtonClick(event : MouseEvent) : void
{
veFileReference = new FileReference();
veFileReference.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, loadCompleteHandler);
// This part works as well so I'm skipping it.
}
public function loadCompleteHandler(event : DataEvent) : void
{
// Pretend I loaded "workspace.ws" by bouncing it off of my servlet byte for byte.
var ba : ByteArray = new ByteArray();
ba.writeObject(event.data);
ba.position = 0;
var obj : * = ba.readObject();
trace(obj is MyWorkspace); // Prints false
}
So my problem here is not with the saving of the workspace. That works great. My problem is with the loading of a workspace. The event.data that I write to the byte array is not reconstituted into a MyWorkspace object even though it is exactly what I wrote to the file.
There has to be a way of making a MyWorkspace object from the data, but I can't figure it out. Has anyone tried to do this before?
Thanks!
I've figured out what the problem here is. It specifically has to do with the line:
ba.writeObject(event.data);
The data field of the event, being a DataEvent, is specifically of type String. ByteArray.writeObject() puts a type code at the beginning of the byte array. This took me many hours of looking at Hexplorer to figure out why, exactly, the byte array had 2-3 extra characters at its beginning.
My current best solution for this is to change that line as follows:
for (var i : int = 0; i < event.data.length; ++i)
{
ba.writeByte(event.data.charCodeAt(i));
}
This ensures that the byte array is exactly the same as what was saved to my local drive and bounced off my servlet.
After doing this, trace(obj is MyWorkspace) prints true.
Hope this helps someone else in the future!
Have you tried casting the value?
var obj : MyWorkspace = ba.readObject() as MyWorkspace;