AIR (As3) - Download large file trough iPad application - actionscript-3

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

Related

Object Loading Issue

I am developing an application that consists of several different objects working together. Everything seems to work great, however, when I play this on a web server I notice the graphics and assets that are being loaded in from an external source are taking some time to load I'm not really sure what is going on I was hoping someone might provide some insight. Example, sometimes the graphic assets will not load at all, but audio seems to be loading in ok. Here is an example of my image loading class:
GFXReg.as
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.text.TextField;
public class GFXReg extends Sprite {
private var mc: MovieClip = new MovieClip();
private var loader: Loader = new Loader();
private var setW: Number;
public var setH: Number;
public var theH: Number;
private var bool: Boolean = false;
public function GFXReg(setSize: Boolean, W: Number, H: Number) {
bool = setSize;
if (bool) {
setW = W;
setH = H;
}
}
private function loadData(e: Event) {
if(loader.contentLoaderInfo.bytesLoaded == loader.contentLoaderInfo.bytesTotal){
addChild(loader);
}else{
var text:TextField = new TextField();
text.text = "Loading..."
addChild(text);
}
if (bool) {
loader.width = setW;
loader.height = setH;
}
}
public function theData(file: String): void {
loader.load(new URLRequest(file));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadData);
}
public function getBytes(str:String):int{
var num:int = 0;
switch(str){
case "loaded":
num = loader.contentLoaderInfo.bytesLoaded;
break;
case "total":
num = loader.contentLoaderInfo.bytesTotal;
break;
}
return num;
}
public function removeData():void{
loader.unloadAndStop(false);
trace("unload?");
}
private function unloadData(e:Event):void{
}
}
}
Main.as Sections(This loads in all the appropriate sections when prompted):
public function Main() {
xmlLoader = new XMLReg("config.xml", processXML);
}
private function processXML(e: Event): void {
theXML = new XML(e.target.data);
dir = theXML.MASTER.#DIRECTORY;
//BACKGROUND
bgGFX.theData(dir + theXML.MAINBG.#IMG);
//INITIALIZE
add(bgGFX,intro);
//INTRO WAS INSTANTIATED IN THE BEGINNING OF THIS CLASS
intro.start();
/*start method adds objects to stage, to test as a solution*/
addChildAt(header, numChildren);
//...
Also in my Main.as class I have a private method transition which loads in the appropriate sections after the previous sections have been completed these assets usually take a few seconds to load in I've noticed when working on a web server.
private function transition(e: Event): void {
//If intro is on the stage instantiate a new QuestionAnswer Object
if (intro.stage) {
header.enableMenu(true);
qa = new QuestAnswer(feedBool,nameCounter,nameVar);
qa.addEventListener("unload", transition);
qa.addEventListener("addFeed", setFeedFalse);
qa.addEventListener("addFeedTrue", setFeedTrue);
qa.addEventListener("enableHelp",enableHelp);
qa.addEventListener("getName",getName);
addChildAt(qa, numChildren-1);
remove(intro);
} else if (qa.stage) {
//If QuestionAnswer is on the Stage instantiate a new Results Object
header.enableMenu(true);
header.enableHelp(false);
results = new Results(qa.resultBundle());
results.addEventListener("unload", transition);
results.addEventListener("addFeed", setFeedFalse);
results.addEventListener("addFeedTrue", setFeedTrue);
results.addEventListener("setName",setName);
addChildAt(results, numChildren-1);
remove(qa);
} else if (results.stage) {
//If Results is on the stage loop back and instantiate a new QuestionAnswer Object
header.enableMenu(true);
header.enableHelp(false);
intro.enableAsset(true);
qa = new QuestAnswer(feedBool,nameCounter,nameVar);
qa.addEventListener("unload", transition);
qa.addEventListener("addFeed", setFeedFalse);
qa.addEventListener("addFeedTrue", setFeedTrue);
addChildAt(qa, numChildren-1);
trackAsset = true;
remove(results);
}
}

play flv using netStream appendBytes

I know that there are many ways to play an FLV file but considering my project requirements, I need to play the flv using URLStream and NetStream
here's the complete sample code that I'm doing my tests on:
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.NetStatusEvent;
import flash.events.ProgressEvent;
import flash.utils.ByteArray;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.NetStreamAppendBytesAction;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.media.Video;
import flash.net.URLStream;
/**
* ...
* #author Hadi Tavakoli
*/
public class Main extends Sprite
{
private var netConnection:NetConnection;
private var netStream:NetStream;
private var ul:URLStream;
private var video:Video;
private var bytes:ByteArray = new ByteArray();
private var _isSeek:Boolean = false;
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
video = new Video();
addChild(video);
netConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnectionStatusHandler);
netConnection.connect(null);
}
private function netConnectionStatusHandler(ev:NetStatusEvent):void
{
switch(ev.info.code)
{
case 'NetConnection.Connect.Success':
ul = new URLStream();
ul.addEventListener(ProgressEvent.PROGRESS, onProgress);
ul.load(new URLRequest('01.flv'));
break;
}
}
private function onProgress(e:ProgressEvent):void
{
ul.readBytes(bytes, bytes.length);
if (!netStream)
{
netStream = new NetStream(netConnection);
netStream.client = { };
video.attachNetStream(netStream);
netStream.play(null);
trace("BEGIN")
netStream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
}
else
{
if (!_isSeek)
{
trace("SEEK")
netStream.appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
_isSeek = true;
}
}
if (bytes.length == e.bytesTotal)
{
trace("END")
netStream.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
}
netStream.appendBytes(bytes);
trace("-")
}
}
}
I'm not sure if I am using "appendBytes" method correctly? the video is shown but only a very few first frames will play and then the video stops!
in my eyes it seems all ok! do you have any advice on where my problem is?
I don't think you need the if (!_isSeek) block. It looks like you are pushing the bytes as you receive them in a sequential order and so there's never a seek. It looks like it will push the first set of bytes and then append a seek action and append the rest of the bytes. Try just removing that block and see if it works.
Otherwise I think it's ok.
in "ul.readBytes(bytes, bytes.length);" line, there is a bug i guess. It's never worked for me also. It always return full length (from 0 to the available bytes). So It have a huge memory leak. But if you are using flash player 11.4 or later, you can change it like this.
ul.position = bytes.length;
ul.readBytes(bytes);

Record h.264 from webcam as f4v from flash

Using Flash Media Server, I have an pre-made application that records from webcam and microphone and publishes it to the FMS server as an FLV file.
This works fine, but I now have a need to stream this video that's been recorded to an iOS device. I've got FMS working and it can stream the hls-record/sample.f4v.m3u8 file to an iOS / quicktime device.
I've read some tutorials online and I've changed the publish(filename, "record") to publish("mp4:" + filename + ".f4v", record)
It records stores the file on the FMS server (i see it there, with the filename i gave it and as a .f4v file), it has content. When i goto play it though (via quicktime or safari) i see the length of the video (4 seconds as a test) but no video. The tracking bar doesn't move and the wait icon, waits.
FMS version 4.5 and Flash 11.3 is installed on my Mac. (CentOS 5.8 server)
I must be missing something.
package com
{
import fl.controls.ProgressBar;
import fl.controls.ProgressBarMode;
import flash.display.MovieClip;
import flash.events.Event;
import com.NetConnector
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.media.Camera;
import flash.media.Microphone;
import flash.media.Video;
import flash.net.navigateToURL;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.text.TextField;
import flash.utils.setTimeout;
import flash.utils.Timer;
import flash.media.H264Level;
import flash.media.H264Profile;
import flash.media.H264VideoStreamSettings;
/**
* ...
* #author Alexander (flash2you) < >
*/
public class Recorder extends MovieClip
{
private var dataHolder:DataHolder = DataHolder.getInstance()
public var layer:MovieClip
public var activityLevel_pb:ProgressBar
public var aguja:MovieClip
public var aguja2:MovieClip
public var publishButton:MovieClip
public var timer_txt:TextField
public var recordStatus:MovieClip
public var recordBtn:MovieClip
private var netStream:NetStream
private var microphone:Microphone = Microphone.getMicrophone()
private var camera:Camera = Camera.getCamera()
public var video:Video
private var timer:Timer = new Timer(100)
private var clockTimer:Timer = new Timer(1000)
public var published:Boolean = false
private var isRecording:Boolean = false
private var minutero = 0;
private var crono = 0;
private var records = 0;
public var settings_mc:MovieClip
public static var recorder:Recorder
public var settings_icon:MovieClip
private var limitTimer:Timer
public function Recorder()
{
Recorder.recorder = this;
timer.addEventListener(TimerEvent.TIMER, on$timer)
clockTimer.addEventListener(TimerEvent.TIMER, on$clockTimer)
//visible = false
recordBtn.buttonMode = true
recordBtn.addEventListener(MouseEvent.CLICK , recordBtn$click)
recordBtn.addEventListener(MouseEvent.MOUSE_OVER, recordBtn$over)
recordBtn.addEventListener(MouseEvent.MOUSE_OUT, recordBtn$out)
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage)
limitTimer = new Timer(dataHolder.timelimit * 1000);
limitTimer.addEventListener(TimerEvent.TIMER, onLimitTimerHandler)
}
private function onLimitTimerHandler(e:TimerEvent):void
{
stopPublish()
}
/*
* when we comes to second frame
* */
private function onAddedToStage(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
init()
}
/*
* function for set up camera from settings module
* */
public function setCamera(_camera:Camera) {
camera = _camera
addCameraSettings()
video.attachCamera(camera)
if (netStream){
netStream.attachCamera(camera)
}
}
public function setMicrophone(mic:Microphone) {
microphone = mic;
if (netStream){
netStream.attachAudio(microphone)
}
addMicSettings()
}
private function addMicSettings() {
microphone.setUseEchoSuppression(true);
microphone.setSilenceLevel(1)
}
private function addCameraSettings():void
{
camera.setQuality(90000, 90);
camera.setMode(320, 240, 30, true);
camera.setKeyFrameInterval(15);
//camera.setMode(dataHolder.cameraWidth, dataHolder.cameraHeight, dataHolder.cameraFPS)
//camera.setQuality(0, dataHolder.cameraQuality)
}
public function init() {
startConnect()
}
/*
* main function for connection
* */
private function startConnect() {
visible = true
timer_txt.htmlText = "<b>00:00</b>";
initCamera()
initMicropone()
var nc:NetConnection = new NetConnection()
nc.connect(null)
netStream = new NetStream(nc)
netStream.attachAudio(microphone)
video.attachCamera(camera)
layer.visible = false
publishButton.gotoAndStop(1);
activityLevel_pb.mode = ProgressBarMode.MANUAL;
recordStatus.gotoAndStop("noRecord")
timer.start()
connection.addEventListener(NetConnector.CONNECTED, connectionComplete)
connection.startConnection()
}
public function get connection():NetConnector {
return dataHolder.connection
}
private function on$timer(e:TimerEvent) {
activityLevel_pb.setProgress(microphone.activityLevel, 100)
}
/*
* when connection to your stream server done
* */
private function connectionComplete(e:Event = null) {
netStream = new NetStream(connection)
netStream.attachAudio(microphone)
netStream.attachCamera(camera)
}
/*
* add 0 if less then 10secs
* */
private function addLeading(nbr) {
if (nbr<10) {
return ("0"+Math.floor(nbr));
} else {
return (Math.floor(nbr).toString());
}
}
/*
* update visible clock, rotate arrows
* */
private function updateTimer() {
timer_txt.htmlText = "<b>"+addLeading(crono/60)+":"+addLeading(crono%60)+"</b>";
aguja.rotation = aguja.rotation+6;
if (addLeading(crono/60)>minutero) {
aguja2.rotation = aguja2.rotation+6;
++minutero;
}
// end if
++crono;
}
private function on$clockTimer(e:TimerEvent):void
{
updateTimer()
}
private function startClockTimer() {
clockTimer.start()
}
/*
* update graphics and start recording
* */
private function recordBtn$click(e:MouseEvent):void
{
if (!isRecording) {
startRecording()
recordStatus.gotoAndStop("record")
recordBtn.visible = false
}
}
private function recordBtn$over(e:MouseEvent):void
{
if (!isRecording) {
this.gotoAndPlay(65);
}
}
private function recordBtn$out(e:MouseEvent):void
{
if (!isRecording) {
this.gotoAndPlay(61);
}
}
private function startRecording() {
if (connection.connected){
var h264Settings:H264VideoStreamSettings = new H264VideoStreamSettings();
h264Settings.setProfileLevel(H264Profile.BASELINE, H264Level.LEVEL_3_1);
netStream.videoStreamSettings = h264Settings;
netStream.publish("mp4:" + dataHolder.filename + ".f4v", "record");
var metaData:Object = new Object();
metaData.codec = netStream.videoStreamSettings.codec;
metaData.profile = h264Settings.profile;
metaData.level = h264Settings.level;
metaData.fps = camera.fps;
metaData.bandwith = camera.bandwidth;
metaData.height = camera.height;
metaData.width = camera.width;
metaData.keyFrameInterval = camera.keyFrameInterval;
metaData.copyright = "company";
netStream.send("#setDataFrame", "onMetaData", metaData);
}
isRecording = true
startClockTimer()
publishButton.gotoAndPlay(2)
publishButton.buttonMode = true
publishButton.addEventListener(MouseEvent.CLICK, publishButton$click);
limitTimer.start()
}
/*
* redirect to finishURL that was passed via flashvars
* */
private function publishButton$click(e:MouseEvent):void
{
stopPublish()
var request:URLRequest = new URLRequest(dataHolder.finishURL)
navigateToURL(request, "_self")
}
private function stopPublish():void
{
netStream.close();
connection.close();
limitTimer.stop();
clockTimer.stop();
isRecording = false
recordStatus.gotoAndStop("recordEnd")
updateTimer();
}
/*
* init microphone
* */
private function initMicropone():void
{
microphone = Microphone.getMicrophone()
addMicSettings()
}
/*
* init camera
* */
private function initCamera():void
{
camera = Camera.getCamera()
addCameraSettings()
}
}
}
I've settled on creating a php script that sits in the folder with the FLV files. Via a cron job, this php file checks the mysql database for new videos with a process tag of 1. Finds new video? Execute ffmpeg on the FLV file to convert it to mp4 and set the process tag to 0. This way, I maintain the FLV for desktop browser streaming and an MP4 file for iOS streaming. If I should ever need to create another file type for android or whatever may come out, I can simply do another ffmpeg conversion when the cron job triggers.
This would also cause less overhead on the server. The mp4 is encoded once by ffmpeg - not every time a request is made to stream the file via FMS. Now the MP4 is just another file on the web server.

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. :)

Run-time loading of external assets and re-using preloaded assets in actionscript 3?

I'm creating a 2d flash game (coded in flex/actionscript 3) where assets are downloaded when they are needed. Currently I have it setup like this:
AssetLoader.as
package
{
import flash.display.Loader;
import flash.net.URLRequest;
public class AssetLoader extends Loader
{
//set vars
private var url:String = "http://test.com/client/assets/";
public function AssetLoader(url:String)
{
Logger.log("AssetLoader request: " + this.url + url);
var request:URLRequest = new URLRequest(this.url + url);
this.load(request);
}
}
}
Then, where I want to load the asset I do the following:
var asset:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
asset.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete, false, 0, true);
private function onShipAssetComplete(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
shipImage = Bitmap(loader.content);
shipImage.smoothing = true;
addChild(shipImage);
}
The thing is, that this method doesn't check for already downloaded assets, so it will redownload them the second time the same asset is being requested (I think).
So, what I need is an array where all downloaded assets are stored, and on request the name of this asset is checked for existance in the array. So if it has already been downloaded, that asset from memory must be returned rather than redownloaded.
I could make the assetloader a static class, but I have to wait for the event to fire when it's done downloading the image - so I can't simply let a static function return the corresponding image. Any idea how I should do this?
EDIT for an attempt after comments:
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
public final class AssetManager
{
private static var assets:Object = {};
private static var preUrl:String = Settings.ASSETS_PRE_URL;
public static function load(postUrl:String):*
{
if (assets[postUrl])
{ //when the asset already exists
//continue
}
else
{ //the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
assets[postUrl] = loader.content;
}, false, 0, true);
}
}
}
}
EDIT2: another attempt
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLRequest;
public final class AssetManager
{
private static var assets:Object = {};
private static var preUrl:String = Settings.ASSETS_PRE_URL;
public static function load(postUrl:String):*
{
if (assets[postUrl])
{ //the asset already exists
var dispatcher:EventDispatcher = new EventDispatcher();
dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
}
else
{ //the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
assets[postUrl] = loader.content;
var dispatcher:EventDispatcher = new EventDispatcher();
dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
}, false, 0, true);
}
}
}
}
Then, I try the following:
var asset:AssetManager = AssetManager.load("ships/" + graphicId + ".gif");
asset.addEventListener(CustomEvent.LOAD_COMPLETE, onShipAssetComplete, false, 0, true);
But get an error, "undefined method addEventListener by a reference of the type static AssetManager" (roughly translated).
You could add a static object (used as a dictionary with urls for assets as keys and the content for assets as values) in the AssetLoader class and in the same time keep using the class in the way you're using it right now.
private static var assets:Object = {};
The difference would be that your class would need to check against that static object if the URL for the content has already been requested previously. If it has, dispatch the complete event immediately. If it hasn't, follow the normal routine and don't forget to populate your static object with the newly loaded asset.
Update:
This is a quick example of what I meant. I haven't had time to test this, but it should work.
Note:
You must invoke the loadAsset() method of the AssetLoader instances you create in order to actually load the asset. This is consistent with the way the Loader class we're extending works.
You should always add all event listeners BEFORE invoking the loadAsset() method. In your question you're calling the load() method from within the constructor and only afterwards add the event listener for Event.COMPLETE. This could produce strange results.
Here's the code:
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
public class AssetLoader extends Loader
{
private static const BASE_URL:String = 'http://test.com/client/assets/';
public static var storedAssets:Object = {};
private var assetURL:String;
private var urlRequest:URLRequest;
private var cached:Boolean = false;
public function AssetLoader(url:String):void
{
trace('Loading: ' + url);
assetURL = url;
if (storedAssets[assetURL] != null)
{
cached = true;
trace('Cached');
}
else
{
trace('Loading uncached asset');
urlRequest = new URLRequest(BASE_URL + assetURL);
contentLoaderInfo.addEventListener(Event.COMPLETE, OnAssetLoadComplete);
}
}
public function loadAsset():void
{
if (cached)
loadBytes(storedAssets[assetURL]);
else
load(urlRequest);
}
private function OnAssetLoadComplete(event:Event):void
{
storedAssets[assetURL] = contentLoaderInfo.bytes;
trace('Loaded ' + contentLoaderInfo.bytesLoaded + ' bytes');
}
}
}
Update 2:
Here's how one would use the class above:
var assetLdr:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
assetLdr.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete);
assetLdr.loadAsset();
private function onShipAssetComplete(event:Event):void
{
var shipImage:Bitmap = Bitmap(event.target.loader.content);
// Do stuff with shipImage
}
Perhaps you should take a look at Bulk Loader. It does the kinds of things your looking to do. If you really want to use a custom solution, it would be a great point of reference, but why reinvent the wheel?
Tyler.
Here is an alteration of your load command to capture the resourceId
public function load(postUrl:String):*
{
var index:int;
if ((index = assetExists(postUrl)) != -1)
{
dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
}
else
{
//the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event)
{
// NOTE: not sure on scoping here ...
// pretty sure you're this will be the Loader
// if not just read it off the event like you were before
assets[postUrl] = content;
dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
}, false, 0, true);
}
}
/* In a new file */
public class CustomEvent extends Event
{
public static const LOAD_COMPLETE:String = "CustomEvent_LoadComplete";
// If you know the type you should use it (e.g. Sprite/DisplayObject)
public var content:*;
public function CustomEvent(type:String, _content:*)
{
content = _content;
super(type);
}
}
Note: when you write an Event descendant you should also override the toString and clone methods. I've also cheated on the constructor since you may want to pass through weakReferences and things like that.