/**
 * File.as
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

package com.plupload {
	import com.formatlos.BitmapDataUnlimited;
	import com.formatlos.events.BitmapDataUnlimitedEvent;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.IBitmapDrawable;
	import flash.events.EventDispatcher;
	import flash.geom.Matrix;
	import flash.net.FileReference;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.HTTPStatusEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.DataEvent;
	import flash.net.FileReferenceList;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLRequestMethod;
	import flash.net.URLStream;
	import flash.net.URLVariables;
	import flash.utils.ByteArray;
	import flash.external.ExternalInterface;

	import com.mxi.image.Image;
	import com.mxi.image.events.ImageEvent;

	/**
	 * Container class for file references, this handles upload logic for individual files.
	 */
	public class File extends EventDispatcher {
		// Private fields
		private var _fileRef:FileReference, _cancelled:Boolean;
		private var _uploadUrl:String, _uploadPath:String, _mimeType:String;
		private var _id:String, _fileName:String, _size:Number, _imageData:ByteArray;
		private var _multipart:Boolean, _fileDataName:String, _chunking:Boolean, _chunk:int, _chunks:int, _chunkSize:int, _postvars:Object;
		private var _headers:Object, _settings:Object;

		/**
		 * Id property of file.
		 */
		public function get id():String {
			return this._id;
		}

		/**
		 * File name for the file.
		 */
		public function get fileName():String {
			return this._fileName;
		}

		/**
		 * File name for the file.
		 */
		public function set fileName(value:String):void {
			this._fileName = value;
		}

		/**
		 * File size property.
		 */
		public function get size():Number {
			return this._size;
		}

		/**
		 * Constructs a new file object.
		 *
		 * @param id Unique indentifier for the file.
		 * @param file_ref File reference for the selected file.
		 */
		public function File(id:String, file_ref:FileReference) {
			this._id = id;
			this._fileRef = file_ref;
			this._size = file_ref.size;
			this._fileName = file_ref.name;
		}

		/**
		 * Uploads a the file to the specified url. This method will upload it as a normal
		 * multipart file upload if the file size is smaller than the chunk size. But if the file is to
		 * large it will be chunked into multiple requests.
		 *
		 * @param url Url to upload the file to.
		 * @param settings Settings object.
		 */
		public function upload(url:String, settings:Object):void {
			this._settings = settings;

			if (this.canUseSimpleUpload(settings)) {
				this.simpleUpload(url, settings);
			} else {
				this.advancedUpload(url, settings);
			}
		}

		// Private methods

		public function canUseSimpleUpload(settings:Object):Boolean {
			var multipart:Boolean = new Boolean(settings["multipart"]);
			var resize:Boolean = (settings["width"] || settings["height"] || settings["quality"]);
			var chunking:Boolean = (settings["chunk_size"] > 0);

			// Check if it's not an image, chunking is disabled, multipart enabled and the ref_upload setting isn't forced
			return (!(/\.(jpeg|jpg|png)$/i.test(this._fileName)) || !resize) && multipart && !chunking && !settings.urlstream_upload;
		}

		public function simpleUpload(url:String, settings:Object):void {
			var file:File = this, request:URLRequest, postData:URLVariables, fileDataName:String,
				onProgress:Function, onUploadComplete:Function, onIOError:Function, onSecurityErrorEvent:Function,

				removeAllListeners:Function = function () : void {
					file._fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress);
					file._fileRef.removeEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadComplete);
					file._fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
					file._fileRef.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
				};

			file._postvars = settings["multipart_params"];
			file._chunk = 0;
			file._chunks = 1;

			postData = new URLVariables();

			file._postvars["name"] = settings["name"];

			for (var key:String in file._postvars) {
				if (key != 'Filename') { // Flash will add it by itself, so we need to omit potential duplicate
					postData[key] = file._postvars[key];
				}
			}

			request = new URLRequest();
			request.method = URLRequestMethod.POST;
			request.url = url;
			request.data = postData;

			fileDataName = new String(settings["file_data_name"]);

			onUploadComplete = function(e:DataEvent):void {
				removeAllListeners();

				var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, file._size, file._size);
				dispatchEvent(pe);

				// Fake UPLOAD_COMPLETE_DATA event
				var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
					UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
					false,
					false,
					e.data,
					file._chunk,
					file._chunks
				);

				file._chunk++;
				file._fileRef.data.clear();

				dispatchEvent(uploadChunkEvt);

				dispatchEvent(e);
			};
			file._fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadComplete);

			// Delegate upload IO errors
			onIOError = function(e:IOErrorEvent):void {
				removeAllListeners();
				dispatchEvent(e);
			};
			file._fileRef.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

			// Delegate secuirty errors
			onSecurityErrorEvent = function(e:SecurityErrorEvent):void {
				removeAllListeners();
				dispatchEvent(e);
			};
			file._fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);

			// Delegate progress
			onProgress = function(e:ProgressEvent):void {
				dispatchEvent(e);
			};
			file._fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress);

			file._fileRef.upload(request, fileDataName, false);
		}

		public function advancedUpload(url:String, settings:Object):void {
			var file:File = this, width:int, height:int, quality:int, multipart:Boolean, chunking:Boolean, fileDataName:String;
			var chunk:int, chunks:int, chunkSize:int, postvars:Object;
			var onComplete:Function, onIOError:Function,
				removeAllListeneres:Function = function() : void {
					file._fileRef.removeEventListener(Event.COMPLETE, onComplete);
					file._fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
				};

			// Setup internal vars
			this._uploadUrl = url;
			this._cancelled = false;
			this._headers = settings.headers;
			this._mimeType = settings.mime;

			multipart = new Boolean(settings["multipart"]);
			fileDataName = new String(settings["file_data_name"]);
			chunkSize = settings["chunk_size"];
			chunking = chunkSize > 0;
			postvars = settings["multipart_params"];
			chunk = 0;

			// When file is loaded start uploading
			onComplete = function(e:Event):void {
				removeAllListeneres();

				var startUpload:Function = function() : void
				{
					if (chunking) {
						chunks = Math.ceil(file._size / chunkSize);

						// Force at least 4 chunks to fake progress. We need to fake this since the URLLoader
						// doesn't have a upload progress event and we can't use FileReference.upload since it
						// doesn't support cookies, breaks on HTTPS and doesn't support custom data so client
						// side image resizing will not be possible.
						if (chunks < 4 && file._size > 1024 * 32) {
							chunkSize = Math.ceil(file._size / 4);
							chunks = 4;
						}
					} else {
						// If chunking is disabled then upload file in one huge chunk
						chunkSize = file._size;
						chunks = 1;
					}

					// Start uploading the scaled down image
					file._multipart = multipart;
					file._fileDataName = fileDataName;
					file._chunking = chunking;
					file._chunk = chunk;
					file._chunks = chunks;
					file._chunkSize = chunkSize;
					file._postvars = postvars;

					file.uploadNextChunk();
				}

				if (/\.(jpeg|jpg|png)$/i.test(file._fileName) && (settings["width"] || settings["height"] || settings["quality"])) {
					var image:Image = new Image(file._fileRef.data);
					image.addEventListener(ImageEvent.COMPLETE, function(e:ImageEvent) : void
					{
						image.removeAllEventListeners();
						if (image.imageData) {
							file._imageData = image.imageData;
							file._imageData.position = 0;
							file._size = image.imageData.length;
						}
						startUpload();
					});
					image.addEventListener(ImageEvent.ERROR, function(e:ImageEvent) : void
					{
						image.removeAllEventListeners();
						file.dispatchEvent(e);
					});
					image.scale(settings["width"], settings["height"], settings["quality"]);
				} else {
					startUpload();
				}
			};
			this._fileRef.addEventListener(Event.COMPLETE, onComplete);

			// File load IO error
			onIOError = function(e:Event):void {
				removeAllListeneres();
				this.dispatchEvent(e);
			};
			this._fileRef.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

			// Start loading local file
			this._fileRef.load();
		}

		/**
		 * Uploads the next chunk or terminates the upload loop if all chunks are done.
		 */
		public function uploadNextChunk():Boolean {
			var req:URLRequest, fileData:ByteArray, chunkData:ByteArray;
			var urlStream:URLStream, url:String, file:File = this;

			// All chunks uploaded?
			if (this._chunk >= this._chunks) {
				// Clean up memory
				if(this._fileRef.data) {
					this._fileRef.data.clear();
				}
				this._imageData = null;

				return false;
			}

			// Slice out a chunk
			chunkData = new ByteArray();

			// Use image data if it exists, will exist if the image was resized
			if (this._imageData != null)
				fileData = this._imageData;
			else
				fileData = this._fileRef.data;

			fileData.readBytes(chunkData, 0, fileData.position + this._chunkSize > fileData.length ? fileData.length - fileData.position : this._chunkSize);

			// Setup URL stream
			urlStream = new URLStream();

			// Wait for response and dispatch it
			urlStream.addEventListener(Event.COMPLETE, function(e:Event):void {
				var response:String;

				response = urlStream.readUTFBytes(urlStream.bytesAvailable);

				// Fake UPLOAD_COMPLETE_DATA event
				var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
					UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
					false,
					false,
					response,
					file._chunk,
					file._chunks
				);

				file._chunk++;
				dispatchEvent(uploadChunkEvt);

				// Fake progress event since Flash doesn't have a progress event for streaming data up to the server
				var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, fileData.position, file._size);
				dispatchEvent(pe);

				// Clean up memory
				urlStream.close();
				chunkData.clear();
			});

			// Delegate upload IO errors
			urlStream.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void {
				dispatchEvent(e);
			});

			// Delegate secuirty errors
			urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(e:SecurityErrorEvent):void {
				dispatchEvent(e);
			});

			// Setup URL
			url = this._uploadUrl;

			// Add name and chunk/chunks to URL if we use direct streaming method
			if (!this._multipart) {
				if (url.indexOf('?') == -1)
					url += '?';
				else
					url += '&';

				url += "name=" + encodeURIComponent(this._settings["name"]);

				if (this._chunking) {
					url += "&chunk=" + this._chunk + "&chunks=" + this._chunks;
				}
			}

			// Setup request
			req = new URLRequest(url);
			req.method = URLRequestMethod.POST;

			// Add custom headers
			if (this._headers) {
				for (var headerName:String in this._headers) {
					req.requestHeaders.push(new URLRequestHeader(headerName, this._headers[headerName]));
				}
			}

			// Build multipart request
			if (this._multipart) {
				var boundary:String = '----pluploadboundary' + new Date().getTime(),
					dashdash:String = '--', crlf:String = '\r\n', multipartBlob: ByteArray = new ByteArray();

				req.requestHeaders.push(new URLRequestHeader("Content-Type", 'multipart/form-data; boundary=' + boundary));

				this._postvars["name"] = this._settings["name"];

				// Add chunking parameters if needed
				if (this._chunking) {
					this._postvars["chunk"] = this._chunk;
					this._postvars["chunks"] = this._chunks;
				}

				// Append mutlipart parameters
				for (var name:String in this._postvars) {
					multipartBlob.writeUTFBytes(
						dashdash + boundary + crlf +
						'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
						this._postvars[name] + crlf
					);
				}

				// Add file header
				multipartBlob.writeUTFBytes(
					dashdash + boundary + crlf +
					'Content-Disposition: form-data; name="' + this._fileDataName + '"; filename="' + this._fileName + '"' + crlf +
					'Content-Type: ' + this._mimeType + crlf + crlf
				);

				// Add file data
				multipartBlob.writeBytes(chunkData, 0, chunkData.length);

				// Add file footer
				multipartBlob.writeUTFBytes(crlf + dashdash + boundary + dashdash + crlf);
				req.data = multipartBlob;
			} else {
				req.requestHeaders.push(new URLRequestHeader("Content-Type", "application/octet-stream"));
				req.data = chunkData;
			}

			// Make request
			urlStream.load(req);

			return true;
		}
	}
}

/**
 * File.as
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

package com.plupload {
	import com.formatlos.BitmapDataUnlimited;
	import com.formatlos.events.BitmapDataUnlimitedEvent;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.IBitmapDrawable;
	import flash.events.EventDispatcher;
	import flash.geom.Matrix;
	import flash.net.FileReference;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.HTTPStatusEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.DataEvent;
	import flash.net.FileReferenceList;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLRequestMethod;
	import flash.net.URLStream;
	import flash.net.URLVariables;
	import flash.utils.ByteArray;
	import flash.external.ExternalInterface;

	import com.mxi.image.Image;
	import com.mxi.image.events.ImageEvent;

	/**
	 * Container class for file references, this handles upload logic for individual files.
	 */
	public class File extends EventDispatcher {
		// Private fields
		private var _fileRef:FileReference, _cancelled:Boolean;
		private var _uploadUrl:String, _uploadPath:String, _mimeType:String;
		private var _id:String, _fileName:String, _size:Number, _imageData:ByteArray;
		private var _multipart:Boolean, _fileDataName:String, _chunking:Boolean, _chunk:int, _chunks:int, _chunkSize:int, _postvars:Object;
		private var _headers:Object, _settings:Object;

		/**
		 * Id property of file.
		 */
		public function get id():String {
			return this._id;
		}

		/**
		 * File name for the file.
		 */
		public function get fileName():String {
			return this._fileName;
		}

		/**
		 * File name for the file.
		 */
		public function set fileName(value:String):void {
			this._fileName = value;
		}

		/**
		 * File size property.
		 */
		public function get size():Number {
			return this._size;
		}

		/**
		 * Constructs a new file object.
		 *
		 * @param id Unique indentifier for the file.
		 * @param file_ref File reference for the selected file.
		 */
		public function File(id:String, file_ref:FileReference) {
			this._id = id;
			this._fileRef = file_ref;
			this._size = file_ref.size;
			this._fileName = file_ref.name;
		}

		/**
		 * Uploads a the file to the specified url. This method will upload it as a normal
		 * multipart file upload if the file size is smaller than the chunk size. But if the file is to
		 * large it will be chunked into multiple requests.
		 *
		 * @param url Url to upload the file to.
		 * @param settings Settings object.
		 */
		public function upload(url:String, settings:Object):void {
			this._settings = settings;

			if (this.canUseSimpleUpload(settings)) {
				this.simpleUpload(url, settings);
			} else {
				this.advancedUpload(url, settings);
			}
		}

		// Private methods

		public function canUseSimpleUpload(settings:Object):Boolean {
			var multipart:Boolean = new Boolean(settings["multipart"]);
			var resize:Boolean = (settings["width"] || settings["height"] || settings["quality"]);
			var chunking:Boolean = (settings["chunk_size"] > 0);

			// Check if it's not an image, chunking is disabled, multipart enabled and the ref_upload setting isn't forced
			return (!(/\.(jpeg|jpg|png)$/i.test(this._fileName)) || !resize) && multipart && !chunking && !settings.urlstream_upload;
		}

		public function simpleUpload(url:String, settings:Object):void {
			var file:File = this, request:URLRequest, postData:URLVariables, fileDataName:String,
				onProgress:Function, onUploadComplete:Function, onIOError:Function, onSecurityErrorEvent:Function,

				removeAllListeners:Function = function () : void {
					file._fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress);
					file._fileRef.removeEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadComplete);
					file._fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
					file._fileRef.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
				};

			file._postvars = settings["multipart_params"];
			file._chunk = 0;
			file._chunks = 1;

			postData = new URLVariables();

			file._postvars["name"] = settings["name"];

			for (var key:String in file._postvars) {
				if (key != 'Filename') { // Flash will add it by itself, so we need to omit potential duplicate
					postData[key] = file._postvars[key];
				}
			}

			request = new URLRequest();
			request.method = URLRequestMethod.POST;
			request.url = url;
			request.data = postData;

			fileDataName = new String(settings["file_data_name"]);

			onUploadComplete = function(e:DataEvent):void {
				removeAllListeners();

				var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, file._size, file._size);
				dispatchEvent(pe);

				// Fake UPLOAD_COMPLETE_DATA event
				var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
					UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
					false,
					false,
					e.data,
					file._chunk,
					file._chunks
				);

				file._chunk++;

				dispatchEvent(uploadChunkEvt);

				dispatchEvent(e);
			};
			file._fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadComplete);

			// Delegate upload IO errors
			onIOError = function(e:IOErrorEvent):void {
				removeAllListeners();
				dispatchEvent(e);
			};
			file._fileRef.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

			// Delegate secuirty errors
			onSecurityErrorEvent = function(e:SecurityErrorEvent):void {
				removeAllListeners();
				dispatchEvent(e);
			};
			file._fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);

			// Delegate progress
			onProgress = function(e:ProgressEvent):void {
				dispatchEvent(e);
			};
			file._fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress);

			file._fileRef.upload(request, fileDataName, false);
		}

		public function advancedUpload(url:String, settings:Object):void {
			var file:File = this, width:int, height:int, quality:int, multipart:Boolean, chunking:Boolean, fileDataName:String;
			var chunk:int, chunks:int, chunkSize:int, postvars:Object;
			var onComplete:Function, onIOError:Function,
				removeAllListeneres:Function = function() : void {
					file._fileRef.removeEventListener(Event.COMPLETE, onComplete);
					file._fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
				};

			// Setup internal vars
			this._uploadUrl = url;
			this._cancelled = false;
			this._headers = settings.headers;
			this._mimeType = settings.mime;

			multipart = new Boolean(settings["multipart"]);
			fileDataName = new String(settings["file_data_name"]);
			chunkSize = settings["chunk_size"];
			chunking = chunkSize > 0;
			postvars = settings["multipart_params"];
			chunk = 0;

			// When file is loaded start uploading
			onComplete = function(e:Event):void {
				removeAllListeneres();

				var startUpload:Function = function() : void
				{
					if (chunking) {
						chunks = Math.ceil(file._size / chunkSize);

						// Force at least 4 chunks to fake progress. We need to fake this since the URLLoader
						// doesn't have a upload progress event and we can't use FileReference.upload since it
						// doesn't support cookies, breaks on HTTPS and doesn't support custom data so client
						// side image resizing will not be possible.
						if (chunks < 4 && file._size > 1024 * 32) {
							chunkSize = Math.ceil(file._size / 4);
							chunks = 4;
						}
					} else {
						// If chunking is disabled then upload file in one huge chunk
						chunkSize = file._size;
						chunks = 1;
					}

					// Start uploading the scaled down image
					file._multipart = multipart;
					file._fileDataName = fileDataName;
					file._chunking = chunking;
					file._chunk = chunk;
					file._chunks = chunks;
					file._chunkSize = chunkSize;
					file._postvars = postvars;

					file.uploadNextChunk();
				}

				if (/\.(jpeg|jpg|png)$/i.test(file._fileName) && (settings["width"] || settings["height"] || settings["quality"])) {
					var image:Image = new Image(file._fileRef.data);
					image.addEventListener(ImageEvent.COMPLETE, function(e:ImageEvent) : void
					{
						image.removeAllEventListeners();
						if (image.imageData) {
							file._imageData = image.imageData;
							file._imageData.position = 0;
							file._size = image.imageData.length;
						}
						startUpload();
					});
					image.addEventListener(ImageEvent.ERROR, function(e:ImageEvent) : void
					{
						image.removeAllEventListeners();
						file.dispatchEvent(e);
					});
					image.scale(settings["width"], settings["height"], settings["quality"]);
				} else {
					startUpload();
				}
			};
			this._fileRef.addEventListener(Event.COMPLETE, onComplete);

			// File load IO error
			onIOError = function(e:Event):void {
				removeAllListeneres();
				this.dispatchEvent(e);
			};
			this._fileRef.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

			// Start loading local file
			this._fileRef.load();
		}

		/**
		 * Uploads the next chunk or terminates the upload loop if all chunks are done.
		 */
		public function uploadNextChunk():Boolean {
			var req:URLRequest, fileData:ByteArray, chunkData:ByteArray;
			var urlStream:URLStream, url:String, file:File = this;

			// All chunks uploaded?
			if (this._chunk >= this._chunks) {
				// Clean up memory
				if(this._fileRef.data) {
					this._fileRef.data.clear();
				}
				this._imageData = null;

				return false;
			}

			// Slice out a chunk
			chunkData = new ByteArray();

			// Use image data if it exists, will exist if the image was resized
			if (this._imageData != null)
				fileData = this._imageData;
			else
				fileData = this._fileRef.data;

			fileData.readBytes(chunkData, 0, fileData.position + this._chunkSize > fileData.length ? fileData.length - fileData.position : this._chunkSize);

			// Setup URL stream
			urlStream = new URLStream();

			// Wait for response and dispatch it
			urlStream.addEventListener(Event.COMPLETE, function(e:Event):void {
				var response:String;

				response = urlStream.readUTFBytes(urlStream.bytesAvailable);

				// Fake UPLOAD_COMPLETE_DATA event
				var uploadChunkEvt:UploadChunkEvent = new UploadChunkEvent(
					UploadChunkEvent.UPLOAD_CHUNK_COMPLETE_DATA,
					false,
					false,
					response,
					file._chunk,
					file._chunks
				);

				file._chunk++;
				dispatchEvent(uploadChunkEvt);

				// Fake progress event since Flash doesn't have a progress event for streaming data up to the server
				var pe:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS, false, false, fileData.position, file._size);
				dispatchEvent(pe);

				// Clean up memory
				urlStream.close();
				chunkData.clear();
			});

			// Delegate upload IO errors
			urlStream.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void {
				dispatchEvent(e);
			});

			// Delegate secuirty errors
			urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(e:SecurityErrorEvent):void {
				dispatchEvent(e);
			});

			// Setup URL
			url = this._uploadUrl;

			// Add name and chunk/chunks to URL if we use direct streaming method
			if (!this._multipart) {
				if (url.indexOf('?') == -1)
					url += '?';
				else
					url += '&';

				url += "name=" + encodeURIComponent(this._settings["name"]);

				if (this._chunking) {
					url += "&chunk=" + this._chunk + "&chunks=" + this._chunks;
				}
			}

			// Setup request
			req = new URLRequest(url);
			req.method = URLRequestMethod.POST;

			// Add custom headers
			if (this._headers) {
				for (var headerName:String in this._headers) {
					req.requestHeaders.push(new URLRequestHeader(headerName, this._headers[headerName]));
				}
			}

			// Build multipart request
			if (this._multipart) {
				var boundary:String = '----pluploadboundary' + new Date().getTime(),
					dashdash:String = '--', crlf:String = '\r\n', multipartBlob: ByteArray = new ByteArray();

				req.requestHeaders.push(new URLRequestHeader("Content-Type", 'multipart/form-data; boundary=' + boundary));

				this._postvars["name"] = this._settings["name"];

				// Add chunking parameters if needed
				if (this._chunking) {
					this._postvars["chunk"] = this._chunk;
					this._postvars["chunks"] = this._chunks;
				}

				// Append mutlipart parameters
				for (var name:String in this._postvars) {
					multipartBlob.writeUTFBytes(
						dashdash + boundary + crlf +
						'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
						this._postvars[name] + crlf
					);
				}

				// Add file header
				multipartBlob.writeUTFBytes(
					dashdash + boundary + crlf +
					'Content-Disposition: form-data; name="' + this._fileDataName + '"; filename="' + this._fileName + '"' + crlf +
					'Content-Type: ' + this._mimeType + crlf + crlf
				);

				// Add file data
				multipartBlob.writeBytes(chunkData, 0, chunkData.length);

				// Add file footer
				multipartBlob.writeUTFBytes(crlf + dashdash + boundary + dashdash + crlf);
				req.data = multipartBlob;
			} else {
				req.requestHeaders.push(new URLRequestHeader("Content-Type", "application/octet-stream"));
				req.data = chunkData;
			}

			// Make request
			urlStream.load(req);

			return true;
		}
	}
}

Flash: Remove extra clear() operation on SimpleUpload FileReference, causing Plupload to hang in some cases.


package justpinegames.Logi
{
	import flash.events.Event;
	import flash.desktop.Clipboard;
	import flash.desktop.ClipboardFormats;
	import flash.utils.getQualifiedClassName;
	import feathers.display.Sprite;
	import feathers.controls.Button;
	import feathers.controls.List;
	import feathers.controls.Scroller;
	import feathers.controls.renderers.IListItemRenderer;
	import feathers.controls.ScrollContainer;
    import feathers.controls.text.BitmapFontTextRenderer;
    import feathers.core.FeathersControl;
	import feathers.data.ListCollection;
	import feathers.layout.VerticalLayout;
	import feathers.text.BitmapFontTextFormat;
	import starling.animation.Tween;
	import starling.core.Starling;
	import starling.display.Quad;
	import starling.events.Event;
	import starling.text.BitmapFont;
	import starling.textures.TextureSmoothing;

	/**
	 * Main class, used to display console and handle its events.
	 */
	public class Console extends Sprite
	{
		private static var _console:Console;
		private static var _archiveOfUndisplayedLogs:Array = [];

		private var _consoleSettings:ConsoleSettings;
		private var _defaultFont:BitmapFont;
		private var _format:BitmapFontTextFormat;
		private var _formatBackground:BitmapFontTextFormat;
		private var _consoleContainer:Sprite;
		private var _hudContainer:ScrollContainer;
		private var _consoleHeight:Number;
		private var _isShown:Boolean;
		private var _copyButton:Button;
		private var _data:Array;
		private var _quad:Quad;
		private var _list:List;

		private const VERTICAL_PADDING: Number = 5;
		private const HORIZONTAL_PADDING: Number = 5;

		/**
		 * You need to create the instance of this class and add it to the stage in order to use this library.
		 *
		 * @param	consoleSettings   Optional parameter which can specify the look and behaviour of the console.
		 */
		public function Console(consoleSettings:ConsoleSettings = null)
		{
			_consoleSettings = consoleSettings ? consoleSettings : new ConsoleSettings();

			_console = _console ? _console : this;

			_data = [];

			_defaultFont = new BitmapFont();
			_format = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.textColor);
			_format.letterSpacing = 2;
			_formatBackground = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.textBackgroundColor);
			_formatBackground.letterSpacing = 2;

			this.addEventListener(starling.events.Event.ADDED_TO_STAGE, addedToStageHandler);
		}

		public function get isShown():Boolean
		{
			return _isShown;
		}

		public function set isShown(value:Boolean):void
		{
			if (_isShown == value)
			{
				return;
			}

			_isShown = value;

			if (_isShown)
			{
				show();
			}
			else
			{
				hide();
			}
		}

		private function addedToStageHandler(e:starling.events.Event):void
		{
			_consoleHeight = this.stage.stageHeight * _consoleSettings.consoleSize;

			_isShown = false;

			_consoleContainer = new FeathersControl();
			_consoleContainer.alpha = 0;
			_consoleContainer.y = -_consoleHeight;
			this.addChild(_consoleContainer);

			_quad = new Quad(this.stage.stageWidth, _consoleHeight, _consoleSettings.consoleBackground);
			_quad.alpha = _consoleSettings.consoleTransparency;
			_consoleContainer.addChild(_quad);

			// TODO Make the list selection work correctly.
			_list = new List();
			_list.x = HORIZONTAL_PADDING;
			_list.y = VERTICAL_PADDING;
			_list.dataProvider = new ListCollection(_data);
			_list.itemRendererFactory = function():IListItemRenderer
			{
				var consoleItemRenderer:ConsoleItemRenderer = new ConsoleItemRenderer(_consoleSettings.textColor, _consoleSettings.highlightColor);
				consoleItemRenderer.width = _list.width;
				consoleItemRenderer.height = 20;
				return consoleItemRenderer;
			};
			_list.addEventListener(starling.events.Event.CHANGE, copyLine);
			_consoleContainer.addChild(_list);

			_copyButton = new Button();

			_copyButton.label = "Copy All";
			_copyButton.addEventListener(starling.events.Event.ADDED, function(e:starling.events.Event):void
			{
                _copyButton.defaultLabelProperties.smoothing = TextureSmoothing.NONE;
                _copyButton.downLabelProperties.smoothing = TextureSmoothing.NONE;

				_copyButton.defaultLabelProperties.textFormat = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.textColor);
				_copyButton.downLabelProperties.textFormat = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.highlightColor);

                _copyButton.stateToSkinFunction = function(target:Object, state:Object, oldValue:Object = null):Object
                {
                    return null;
                };

				_copyButton.width = 150;
				_copyButton.height = 40;
			});
			_copyButton.addEventListener(starling.events.Event.SELECT, copy);
			_consoleContainer.addChild(_copyButton);

			_hudContainer = new ScrollContainer();
			// TODO This should be changed to prevent the hud from even creating, not just making it invisible.
			if (!_consoleSettings.hudEnabled)
			{
				_hudContainer.visible = false;
			}
			_hudContainer.x = HORIZONTAL_PADDING;
			_hudContainer.y = VERTICAL_PADDING;
			_hudContainer.touchable = false;
			_hudContainer.layout = new VerticalLayout();
			_hudContainer.scrollerProperties.verticalScrollPolicy = Scroller.SCROLL_POLICY_OFF;
			this.addChild(_hudContainer);

			this.setScreenSize(Starling.current.nativeStage.stageWidth, Starling.current.nativeStage.stageHeight);

			for each (var undisplayedMessage:* in _archiveOfUndisplayedLogs)
			{
				this.logMessage(undisplayedMessage);
			}

			_archiveOfUndisplayedLogs = [];

			Starling.current.nativeStage.addEventListener(flash.events.Event.RESIZE, function(e:flash.events.Event):void
			{
				setScreenSize(Starling.current.nativeStage.stageWidth, Starling.current.nativeStage.stageHeight);
			});
		}

		private function setScreenSize(width:Number, height:Number):void
		{
			_consoleContainer.width = width;
			_consoleContainer.height = height;

			_consoleHeight = height * _consoleSettings.consoleSize;

			_quad.width = width;
			_quad.height = _consoleHeight;

			_copyButton.x = width - 110 - HORIZONTAL_PADDING;
			_copyButton.y = _consoleHeight - 33 - VERTICAL_PADDING;

			_list.width = this.stage.stageWidth - HORIZONTAL_PADDING * 2;
			_list.height = _consoleHeight - VERTICAL_PADDING * 2;

			if (!_isShown)
			{
				_consoleContainer.y = -_consoleHeight;
			}
		}

		private function show():void
		{
			_consoleContainer.visible = true;

			var __tween1:Tween = new Tween(_consoleContainer, _consoleSettings.animationTime);
			__tween1.animate("y", 0);
			__tween1.fadeTo(1);
			Starling.juggler.add(__tween1);

			var __tween2:Tween = new Tween(_hudContainer, _consoleSettings.animationTime);
			__tween2.fadeTo(0);
			Starling.juggler.add(__tween2);

			_isShown = true;
		}

		private function hide():void
		{
			var __tween1:Tween = new Tween(_consoleContainer, _consoleSettings.animationTime);
			__tween1.animate("y",  -_consoleHeight);
			__tween1.fadeTo(0);
			__tween1.onComplete = function():void
			{
				_consoleContainer.visible = false;
			};
			Starling.juggler.add(__tween1);

			var __tween2:Tween = new Tween(_hudContainer, _consoleSettings.animationTime);
			__tween2.fadeTo(1);
			Starling.juggler.add(__tween2);

			_isShown = false;
		}

		private function copyLine(list:List):void
		{
			//log(list.selectedItem.data);
		}

		/**
		 * You can use this data to save a log to the file.
		 *
		 * @return  Log messages joined into a String with new lines.
		 */
		public function getLogData():String
		{
			var text:String = "";

			for each (var object:Object in _data)
			{
				text += object.data + "\n";
			}

			return text;
		}

		private function copy(button:Button):void
		{
			var text:String = this.getLogData();
			Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, text);
		}

		/**
		 * Displays the message string in the console, or on the HUD if the console is hidden.
		 *
		 * @param	message   String to display
		 */
		public function logMessage(message:String):void
		{
			if (_consoleSettings.traceEnabled)
			{
				trace(message);
			}

			var labelDisplay: String = (new Date()).toLocaleTimeString() + ": " + message;

			_list.dataProvider.push({label: labelDisplay, data: message});

			var createLabel:Function = function(text:String, format:BitmapFontTextFormat):BitmapFontTextRenderer
			{
				var label:BitmapFontTextRenderer = new BitmapFontTextRenderer();
				label.addEventListener(starling.events.Event.ADDED, function(e:starling.events.Event):void
				{
					label.textFormat = format;
				});
				label.smoothing = TextureSmoothing.NONE;
				label.text = text;
				label.validate();
				return label;
			};

			var hudLabelContainer:FeathersControl = new FeathersControl();
			hudLabelContainer.width = 640;
			hudLabelContainer.height = 20;

			var addBackground:Function = function(offsetX:int, offsetY: int):void
			{
				var hudLabelBackground:BitmapFontTextRenderer = createLabel(message, _formatBackground);
				hudLabelBackground.x = offsetX;
				hudLabelBackground.y = offsetY;
				hudLabelContainer.addChild(hudLabelBackground);
			};

			addBackground(0, 0);
			addBackground(2, 0);
			addBackground(0, 2);
			addBackground(2, 2);

			var hudLabel:BitmapFontTextRenderer = createLabel(message, _format);
			hudLabel.x += 1;
			hudLabel.y += 1;
			hudLabelContainer.addChild(hudLabel);

			_hudContainer.addChildAt(hudLabelContainer, 0);

			var __tween1:Tween = new Tween(hudLabelContainer, _consoleSettings.hudMessageFadeOutTime);
			__tween1.delay = _consoleSettings.hudMessageDisplayTime
			__tween1.fadeTo(0);
			__tween1.onComplete = function():void
			{
				_hudContainer.removeChild(hudLabelContainer);
			};
			Starling.juggler.add(__tween1);

			// TODO use the correct API, currently there is a problem with List max vertical position. A bug in foxhole?
			_list.verticalScrollPosition = Math.max(_list.dataProvider.length * 20 - _list.height, 0);

		}

		/**
		 * Returns the fist created Console instance.
		 *
		 * @return Console instance
		 */
		public static function getMainConsoleInstance():Console
		{
			return _console;
		}

		/**
		* Main log function. Usage is the same as for the trace statement.
		*
		* For data sent to the log function to be displayed, you need to first create a LogConsole instance, and add it to the Starling stage.
		*
		* @param	... arguments   Variable number of arguments, which will be displayed in the log
		*/
		public static function staticLogMessage(... arguments):void
		{
			var message:String = "";
			var firstTime:Boolean = true;

			for each (var argument:* in arguments)
			{
				var description:String;

				if (argument == null)
				{
					description = "[null]"
				}
				else if (!("toString" in argument))
				{
					description = "[object " + getQualifiedClassName(argument) + "]";
				}
				else
				{
					description = argument;
				}

				if (firstTime)
				{
					message = description;
					firstTime = false;
				}
				else
				{
					message += ", " + description;
				}
			}

			if (Console.getMainConsoleInstance() == null)
			{
				_archiveOfUndisplayedLogs.push(message);
			}
			else
			{
				Console.getMainConsoleInstance().logMessage(message);
			}
		}
	}
}

package justpinegames.Logi
{
	import flash.desktop.Clipboard;
	import flash.desktop.ClipboardFormats;
	import flash.utils.getQualifiedClassName;
	import feathers.display.Sprite;
	import feathers.controls.Button;
	import feathers.controls.List;
	import feathers.controls.Scroller;
	import feathers.controls.renderers.IListItemRenderer;
	import feathers.controls.ScrollContainer;
        import feathers.controls.text.BitmapFontTextRenderer;
        import feathers.core.FeathersControl;
	import feathers.data.ListCollection;
	import feathers.layout.VerticalLayout;
	import feathers.text.BitmapFontTextFormat;
	import starling.animation.Tween;
	import starling.core.Starling;
	import starling.display.Quad;
	import starling.events.Event;
	import starling.events.ResizeEvent;
	import starling.text.BitmapFont;
	import starling.textures.TextureSmoothing;

	/**
	 * Main class, used to display console and handle its events.
	 */
	public class Console extends Sprite
	{
		private static var _console:Console;
		private static var _archiveOfUndisplayedLogs:Array = [];

		private var _consoleSettings:ConsoleSettings;
		private var _defaultFont:BitmapFont;
		private var _format:BitmapFontTextFormat;
		private var _formatBackground:BitmapFontTextFormat;
		private var _consoleContainer:Sprite;
		private var _hudContainer:ScrollContainer;
		private var _consoleHeight:Number;
		private var _isShown:Boolean;
		private var _copyButton:Button;
		private var _data:Array;
		private var _quad:Quad;
		private var _list:List;

		private const VERTICAL_PADDING: Number = 5;
		private const HORIZONTAL_PADDING: Number = 5;

		/**
		 * You need to create the instance of this class and add it to the stage in order to use this library.
		 *
		 * @param	consoleSettings   Optional parameter which can specify the look and behaviour of the console.
		 */
		public function Console(consoleSettings:ConsoleSettings = null)
		{
			_consoleSettings = consoleSettings ? consoleSettings : new ConsoleSettings();

			_console = _console ? _console : this;

			_data = [];

			_defaultFont = new BitmapFont();
			_format = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.textColor);
			_format.letterSpacing = 2;
			_formatBackground = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.textBackgroundColor);
			_formatBackground.letterSpacing = 2;

			this.addEventListener(starling.events.Event.ADDED_TO_STAGE, addedToStageHandler);
		}

		public function get isShown():Boolean
		{
			return _isShown;
		}

		public function set isShown(value:Boolean):void
		{
			if (_isShown == value)
			{
				return;
			}

			_isShown = value;

			if (_isShown)
			{
				show();
			}
			else
			{
				hide();
			}
		}

		private function addedToStageHandler(e:Event):void
		{
			_consoleHeight = this.stage.stageHeight * _consoleSettings.consoleSize;

			_isShown = false;

			_consoleContainer = new FeathersControl();
			_consoleContainer.alpha = 0;
			_consoleContainer.y = -_consoleHeight;
			this.addChild(_consoleContainer);

			_quad = new Quad(this.stage.stageWidth, _consoleHeight, _consoleSettings.consoleBackground);
			_quad.alpha = _consoleSettings.consoleTransparency;
			_consoleContainer.addChild(_quad);

			// TODO Make the list selection work correctly.
			_list = new List();
			_list.x = HORIZONTAL_PADDING;
			_list.y = VERTICAL_PADDING;
			_list.dataProvider = new ListCollection(_data);
			_list.itemRendererFactory = function():IListItemRenderer
			{
				var consoleItemRenderer:ConsoleItemRenderer = new ConsoleItemRenderer(_consoleSettings.textColor, _consoleSettings.highlightColor);
				consoleItemRenderer.width = _list.width;
				consoleItemRenderer.height = 20;
				return consoleItemRenderer;
			};
			_list.addEventListener(Event.CHANGE, copyLine);
			_consoleContainer.addChild(_list);

			_copyButton = new Button();

			_copyButton.label = "Copy All";
			_copyButton.addEventListener(Event.ADDED, function(e:Event):void
			{
                _copyButton.defaultLabelProperties.smoothing = TextureSmoothing.NONE;
                _copyButton.downLabelProperties.smoothing = TextureSmoothing.NONE;

				_copyButton.defaultLabelProperties.textFormat = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.textColor);
				_copyButton.downLabelProperties.textFormat = new BitmapFontTextFormat(_defaultFont, 16, _consoleSettings.highlightColor);

                _copyButton.stateToSkinFunction = function(target:Object, state:Object, oldValue:Object = null):Object
                {
                    return null;
                };

				_copyButton.width = 150;
				_copyButton.height = 40;
			});
			_copyButton.addEventListener(Event.SELECT, copy);
			_consoleContainer.addChild(_copyButton);

			_hudContainer = new ScrollContainer();
			// TODO This should be changed to prevent the hud from even creating, not just making it invisible.
			if (!_consoleSettings.hudEnabled)
			{
				_hudContainer.visible = false;
			}
			_hudContainer.x = HORIZONTAL_PADDING;
			_hudContainer.y = VERTICAL_PADDING;
			_hudContainer.touchable = false;
			_hudContainer.layout = new VerticalLayout();
			_hudContainer.scrollerProperties.verticalScrollPolicy = Scroller.SCROLL_POLICY_OFF;
			this.addChild(_hudContainer);

			this.setScreenSize(Starling.current.nativeStage.stageWidth, Starling.current.nativeStage.stageHeight);

			for each (var undisplayedMessage:* in _archiveOfUndisplayedLogs)
			{
				this.logMessage(undisplayedMessage);
			}

			_archiveOfUndisplayedLogs = [];

			stage.addEventListener(ResizeEvent.RESIZE, function(e:ResizeEvent):void
			{
				setScreenSize(stage.stageWidth, stage.stageHeight);
			});
		}

		private function setScreenSize(width:Number, height:Number):void
		{
			_consoleContainer.width = width;
			_consoleContainer.height = height;

			_consoleHeight = height * _consoleSettings.consoleSize;

			_quad.width = width;
			_quad.height = _consoleHeight;

			_copyButton.x = width - 110 - HORIZONTAL_PADDING;
			_copyButton.y = _consoleHeight - 33 - VERTICAL_PADDING;

			_list.width = this.stage.stageWidth - HORIZONTAL_PADDING * 2;
			_list.height = _consoleHeight - VERTICAL_PADDING * 2;

			if (!_isShown)
			{
				_consoleContainer.y = -_consoleHeight;
			}
		}

		private function show():void
		{
			_consoleContainer.visible = true;

			var __tween1:Tween = new Tween(_consoleContainer, _consoleSettings.animationTime);
			__tween1.animate("y", 0);
			__tween1.fadeTo(1);
			Starling.juggler.add(__tween1);

			var __tween2:Tween = new Tween(_hudContainer, _consoleSettings.animationTime);
			__tween2.fadeTo(0);
			Starling.juggler.add(__tween2);

			_isShown = true;
		}

		private function hide():void
		{
			var __tween1:Tween = new Tween(_consoleContainer, _consoleSettings.animationTime);
			__tween1.animate("y",  -_consoleHeight);
			__tween1.fadeTo(0);
			__tween1.onComplete = function():void
			{
				_consoleContainer.visible = false;
			};
			Starling.juggler.add(__tween1);

			var __tween2:Tween = new Tween(_hudContainer, _consoleSettings.animationTime);
			__tween2.fadeTo(1);
			Starling.juggler.add(__tween2);

			_isShown = false;
		}

		private function copyLine(list:List):void
		{
			//log(list.selectedItem.data);
		}

		/**
		 * You can use this data to save a log to the file.
		 *
		 * @return  Log messages joined into a String with new lines.
		 */
		public function getLogData():String
		{
			var text:String = "";

			for each (var object:Object in _data)
			{
				text += object.data + "\n";
			}

			return text;
		}

		private function copy(button:Button):void
		{
			var text:String = this.getLogData();
			Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, text);
		}

		/**
		 * Displays the message string in the console, or on the HUD if the console is hidden.
		 *
		 * @param	message   String to display
		 */
		public function logMessage(message:String):void
		{
			if (_consoleSettings.traceEnabled)
			{
				trace(message);
			}

			var labelDisplay: String = (new Date()).toLocaleTimeString() + ": " + message;

			_list.dataProvider.push({label: labelDisplay, data: message});

			var createLabel:Function = function(text:String, format:BitmapFontTextFormat):BitmapFontTextRenderer
			{
				var label:BitmapFontTextRenderer = new BitmapFontTextRenderer();
				label.addEventListener(Event.ADDED, function(e:Event):void
				{
					label.textFormat = format;
				});
				label.smoothing = TextureSmoothing.NONE;
				label.text = text;
				label.validate();
				return label;
			};

			var hudLabelContainer:FeathersControl = new FeathersControl();
			hudLabelContainer.width = 640;
			hudLabelContainer.height = 20;

			var addBackground:Function = function(offsetX:int, offsetY: int):void
			{
				var hudLabelBackground:BitmapFontTextRenderer = createLabel(message, _formatBackground);
				hudLabelBackground.x = offsetX;
				hudLabelBackground.y = offsetY;
				hudLabelContainer.addChild(hudLabelBackground);
			};

			addBackground(0, 0);
			addBackground(2, 0);
			addBackground(0, 2);
			addBackground(2, 2);

			var hudLabel:BitmapFontTextRenderer = createLabel(message, _format);
			hudLabel.x += 1;
			hudLabel.y += 1;
			hudLabelContainer.addChild(hudLabel);

			_hudContainer.addChildAt(hudLabelContainer, 0);

			var __tween1:Tween = new Tween(hudLabelContainer, _consoleSettings.hudMessageFadeOutTime);
			__tween1.delay = _consoleSettings.hudMessageDisplayTime
			__tween1.fadeTo(0);
			__tween1.onComplete = function():void
			{
				_hudContainer.removeChild(hudLabelContainer);
			};
			Starling.juggler.add(__tween1);

			// TODO use the correct API, currently there is a problem with List max vertical position. A bug in foxhole?
			_list.verticalScrollPosition = Math.max(_list.dataProvider.length * 20 - _list.height, 0);

		}

		/**
		 * Returns the fist created Console instance.
		 *
		 * @return Console instance
		 */
		public static function getMainConsoleInstance():Console
		{
			return _console;
		}

		/**
		* Main log function. Usage is the same as for the trace statement.
		*
		* For data sent to the log function to be displayed, you need to first create a LogConsole instance, and add it to the Starling stage.
		*
		* @param	... arguments   Variable number of arguments, which will be displayed in the log
		*/
		public static function staticLogMessage(... arguments):void
		{
			var message:String = "";
			var firstTime:Boolean = true;

			for each (var argument:* in arguments)
			{
				var description:String;

				if (argument == null)
				{
					description = "[null]"
				}
				else if (!("toString" in argument))
				{
					description = "[object " + getQualifiedClassName(argument) + "]";
				}
				else
				{
					description = argument;
				}

				if (firstTime)
				{
					message = description;
					firstTime = false;
				}
				else
				{
					message += ", " + description;
				}
			}

			if (Console.getMainConsoleInstance() == null)
			{
				_archiveOfUndisplayedLogs.push(message);
			}
			else
			{
				Console.getMainConsoleInstance().logMessage(message);
			}
		}
	}
}

Update src/justpinegames/Logi/Console.as

Removed flash.display.Events and used starling.events.Event instead


package net.manaca.tracking
{
import net.manaca.data.Set;
import net.manaca.tracking.sender.ITrackingSender;

/**
 *
 * @author Sean
 *
 */
public class Tracking
{
    //==========================================================================
    //  Class variables
    //==========================================================================
    static private const senderList:Set = new Set();

    //==========================================================================
    //  Class methods
    //==========================================================================

    /**
     * Do the tracking.
     *
     * @param action    The value to track.
     * @param rest        Additional parameters.
     */
    static public function action(action:String, ... rest):void
    {
        var list:Array = senderList.toArray();
        for each (var sender:ITrackingSender in list)
        {
            sender.externalTrack(action, rest);
        }
    }

    /**
     * Add a sender.
     *
     * @param sender    The sender to add.
     */
    static public function addSender(sender:ITrackingSender):void
    {
        senderList.add(sender);
    }

    /**
     * Remove a sender.
     *
     * @param sender    The sender to remove.
     */
    static public function removeSender(sender:ITrackingSender):void
    {
        senderList.remove(sender);
    }
}
}

package net.manaca.tracking
{
import net.manaca.tracking.sender.ITrackingSender;
import net.manaca.utils.ArrayUtil;

/**
 *
 * @author Sean
 *
 */
public class Tracking
{
    //==========================================================================
    //  Class variables
    //==========================================================================
    static private const senderList:Array = [];

    //==========================================================================
    //  Class methods
    //==========================================================================

    /**
     * Do the tracking.
     *
     * @param action    The value to track.
     * @param rest        Additional parameters.
     */
    static public function action(action:String, ... rest):void
    {
        for each (var sender:ITrackingSender in senderList)
        {
            sender.externalTrack(action, rest);
        }
    }

    /**
     * Add a sender.
     *
     * @param sender    The sender to add.
     */
    static public function addSender(sender:ITrackingSender):void
    {
        senderList.push(sender);
    }

    /**
     * Remove a sender.
     *
     * @param sender    The sender to remove.
     */
    static public function removeSender(sender:ITrackingSender):void
    {
        ArrayUtil.removeValueFromArray(senderList, sender);
    }
}
}

change senderList type to Array

change senderList type to Array


package org.servebox.cafe.core.layout.impl
{
	import flash.events.Event;
	import flash.events.IEventDispatcher;

	import mx.collections.ArrayCollection;
	import mx.core.IVisualElementContainer;
	import mx.events.ChildExistenceChangedEvent;
	import mx.events.FlexEvent;

	import org.flexunit.internals.namespaces.classInternal;
	import org.servebox.cafe.core.Container;
	import org.servebox.cafe.core.layout.ILayoutArea;
	import org.servebox.cafe.core.layout.ILayoutAreaManager;
	import org.servebox.cafe.core.signal.SignalAggregator;
	import org.servebox.cafe.core.view.IView;

	import spark.events.ElementExistenceEvent;

	public class DelegateLayoutArea implements ILayoutArea
	{
		private var _container : IVisualElementContainer;
		private var _views : Vector.<IView> = new Vector.<IView>();
		private var _name : String;

		public function DelegateLayoutArea( container : IVisualElementContainer = null )
		{
			this.container = container;
		}

		public function add(view:IView):void
		{
			cleanAllViews();
			IEventDispatcher( container ).addEventListener( ElementExistenceEvent.ELEMENT_ADD, viewCreationCompleteHandler );
			_views.push( view );
			container.addElement( view );
		}

		protected function cleanAllViews() : void
		{
			container.removeAllElements();
			_views = new Vector.<IView>();
		}

		protected function viewCreationCompleteHandler( event : ElementExistenceEvent ) : void
		{
			trace("SIGNAL : " + Object(event.element).className + "_LOADED");
			Container.getInstance().signalAggregator.signal( Object(event.element).className + "_LOADED" );
			IEventDispatcher( container ).removeEventListener( ElementExistenceEvent.ELEMENT_ADD, viewCreationCompleteHandler );
		}

		public function remove(view:IView):void
		{
			container.removeElement( view );
			for(var i : int = 0; i < _views.length; i++ )
			{
				if( _views[i] == view )
				{
					_views.splice( i, 1 );
					break;
				}
			}
		}

		public function getViews() : ArrayCollection
		{
			var viewList : ArrayCollection = new ArrayCollection();
			for each ( var view : IView in _views )
			{
				viewList.addItem(view);
			}
			return viewList;
		}

		[Bindable]
		public function set container( value : IVisualElementContainer ) : void
		{
			_container = value;
			if( value && name )
			{
				register();
			}
		}

		public function get container() : IVisualElementContainer
		{
			return _container;
		}

		[Bindable]
		public function get name() : String
		{
			return _name;
		}

		public function set name( value : String ): void
		{
			_name = value;
			if( value && name )
			{
				register();
			}
		}

		private function register() : void
		{
			var manager : ILayoutAreaManager = Container.getInstance().getLayoutAreaManager();
			manager.addArea( name, this );
		}

	}
}

package org.servebox.cafe.core.layout.impl
{
	import flash.events.IEventDispatcher;

	import mx.collections.ArrayCollection;
	import mx.core.IVisualElementContainer;

	import org.servebox.cafe.core.Container;
	import org.servebox.cafe.core.layout.ILayoutArea;
	import org.servebox.cafe.core.layout.ILayoutAreaManager;
	import org.servebox.cafe.core.view.IView;

	import spark.events.ElementExistenceEvent;

	public class DelegateLayoutArea implements ILayoutArea
	{
		private var _container : IVisualElementContainer;
		private var _views : Vector.<IView> = new Vector.<IView>();
		private var _name : String;

		public function DelegateLayoutArea( container : IVisualElementContainer = null )
		{
			this.container = container;
		}

		public function add(view:IView):void
		{
			cleanAllViews();
			IEventDispatcher( container ).addEventListener( ElementExistenceEvent.ELEMENT_ADD, viewCreationCompleteHandler );
			_views.push( view );
			container.addElement( view );
		}

		protected function cleanAllViews() : void
		{
			container.removeAllElements();
			_views = new Vector.<IView>();
		}

		protected function viewCreationCompleteHandler( event : ElementExistenceEvent ) : void
		{
			trace("SIGNAL : " + Object(event.element).className + "_LOADED");
			Container.getInstance().signalAggregator.signal( Object(event.element).className + "_LOADED" );
			IEventDispatcher( container ).removeEventListener( ElementExistenceEvent.ELEMENT_ADD, viewCreationCompleteHandler );
		}

		public function remove(view:IView):void
		{
			container.removeElement( view );
			for(var i : int = 0; i < _views.length; i++ )
			{
				if( _views[i] == view )
				{
					_views.splice( i, 1 );
					break;
				}
			}
		}

		public function getViews() : ArrayCollection
		{
			var viewList : ArrayCollection = new ArrayCollection();
			for each ( var view : IView in _views )
			{
				viewList.addItem(view);
			}
			return viewList;
		}

		[Bindable]
		public function set container( value : IVisualElementContainer ) : void
		{
			_container = value;
			if( value && name )
			{
				register();
			}
		}

		public function get container() : IVisualElementContainer
		{
			return _container;
		}

		[Bindable]
		public function get name() : String
		{
			return _name;
		}

		public function set name( value : String ): void
		{
			_name = value;
			if( value && name )
			{
				register();
			}
		}

		private function register() : void
		{
			var manager : ILayoutAreaManager = Container.getInstance().getLayoutAreaManager();
			manager.addArea( name, this );
		}

	}
}

Fix import.

Fix import.


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.display.MiracleImage;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler():void {
			if(_model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler)
				_model.viewerInput.browseForOpen("Open file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			var name:String = asset.name;
			Miracle.createScene(new <Asset>[asset], 1);
			Miracle.currentScene.createImage(name)
					.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
			Miracle.resume();
		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleImage = event.target as MiracleImage;
			target.moveTO(
					this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1
			);
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.display.MiracleImage;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TEXTURE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose mesh");
					var list:List = new List(_window, 0, 0, this.getTexture(asset.output));
					_window.x = this.stage.stageWidth - _window.width >> 1;
					_window.y = this.stage.stageHeight - _window.height >> 1;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}

		}

		private function getTexture(bytes:ByteArray):Array {
			var result:Array = [1, 2, 3];
			return result;
		}

		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [1, 2, 3];
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			_assets.push(new Asset(_model.viewerInput.name, byteArray));
//			var name:String = asset.name;
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			list.removeEventListener(event.type, this.selectAnimationHandler);
			log(this, "selectAnimationHandler", list.selectedItem);
			this.removeChild(_window);
			_window = null;
			//add animation to miracle
			var name:String = list.selectedItem.toString();
			Miracle.currentScene.createImage(name)
			 .addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleImage = event.target as MiracleImage;
			target.moveTO(
					this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1
			);
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

change viewer

change viewer


package com.kaltura.hls.m2ts
{
    import flash.utils.ByteArray;
    import flash.net.ObjectEncoding;
    import flash.utils.ByteArray;
    import flash.utils.Endian;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;

    /**
     * Responsible for emitting FLV data. Also handles AAC conversion
     * config and buffering.
     */
    public class FLVTranscoder
    {
        public const MIN_FILE_HEADER_BYTE_COUNT:int = 9;

        public var callback:Function;

        private var lastTagSize:uint = 0;

        private var _aacConfig:ByteArray;
        private var _aacRemainder:ByteArray;
        private var _aacTimestamp:Number = 0;

        public function clear(clearAACConfig:Boolean = false):void
        {
            if(clearAACConfig)
                _aacConfig = null;
            _aacRemainder = null;
            _aacTimestamp = 0;
        }

        private static var tag:ByteArray = new ByteArray();

        private function sendFLVTag(flvts:uint, type:uint, codec:int, mode:int, bytes:ByteArray, offset:uint, length:uint):void
        {
            tag.position = 0;
            var msgLength:uint = length + ((codec >= 0) ? 1 : 0) + ((mode >= 0) ? 1 : 0);
            var cursor:uint = 0;

            if(msgLength > 0xffffff)
                return; // too big for the length field

            tag.length = FLVTags.HEADER_LENGTH + msgLength;
            tag[cursor++] = type;
            tag[cursor++] = (msgLength >> 16) & 0xff;
            tag[cursor++] = (msgLength >>  8) & 0xff;
            tag[cursor++] = (msgLength      ) & 0xff;
            tag[cursor++] = (flvts >> 16) & 0xff;
            tag[cursor++] = (flvts >>  8) & 0xff;
            tag[cursor++] = (flvts      ) & 0xff;
            tag[cursor++] = (flvts >> 24) & 0xff;
            tag[cursor++] = 0x00; // stream ID
            tag[cursor++] = 0x00;
            tag[cursor++] = 0x00;

            if(codec >= 0)
                tag[cursor++] = codec;
            if(mode >= 0)
                tag[cursor++] = mode;

            tag.position = cursor;
            tag.writeBytes(bytes, offset, length);

            cursor += length;
            msgLength += 11; // account for message header in back pointer

            tag.writeUnsignedInt(lastTagSize);

            // Dispatch tag.
            if(callback != null)
                callback(flvts, tag);

            lastTagSize = tag.length;
        }

        public function convertFLVTimestamp(pts:Number):Number
        {
            return pts / 90.0;
        }

        private static var flvGenerationBuffer:ByteArray = new ByteArray();

        /**
         * Convert and amit AVC NALU data.
         */
        public function convert(unit:NALU):void
        {
            // Emit an AVCC.
            var avcc:ByteArray = NALUProcessor.extractAVCC(unit);
            if(avcc)
                sendFLVTag(convertFLVTimestamp(unit.pts), FLVTags.TYPE_VIDEO, FLVTags.VIDEO_CODEC_AVC_KEYFRAME, FLVTags.AVC_MODE_AVCC, avcc, 0, avcc.length);

            // Accumulate NALUs into buffer.
            flvGenerationBuffer.length = 3;
            flvGenerationBuffer[0] = (tsu >> 16) & 0xff;
            flvGenerationBuffer[1] = (tsu >>  8) & 0xff;
            flvGenerationBuffer[2] = (tsu      ) & 0xff;
            flvGenerationBuffer.position = 3;

            // Check keyframe status.
            var keyFrame:Boolean = false;
            var totalAppended:int = 0;
            NALUProcessor.walkNALUs(unit.buffer, 0, function(bytes:ByteArray, cursor:uint, length:uint):void
            {
                // Check for a NALU that is keyframe type.
                var naluType:uint = bytes[cursor] & 0x1f;
                //trace(naluType + " length=" + length);
                switch(naluType)
                {
                    case 0x09: // "access unit delimiter"
                        switch((bytes[cursor + 1] >> 5) & 0x07) // access unit type
                        {
                            case 0:
                            case 3:
                            case 5:
                                keyFrame = true;
                                break;
                            default:
                                keyFrame = false;
                                break;
                        }
                        break;

                    default:
                        // Infer keyframe state.
                        if(naluType == 5)
                            keyFrame = true;
                        else if(naluType == 1)
                            keyFrame = false;
                }

                // Append.
                flvGenerationBuffer.writeUnsignedInt(length);
                flvGenerationBuffer.writeBytes(bytes, cursor, length);
                totalAppended += length;
            }, true );

            var flvts:uint = convertFLVTimestamp(unit.pts);
            var tsu:uint = convertFLVTimestamp(unit.pts - unit.dts);

            var codec:uint;
            if(keyFrame)
                codec = FLVTags.VIDEO_CODEC_AVC_KEYFRAME;
            else
                codec = FLVTags.VIDEO_CODEC_AVC_PREDICTIVEFRAME;

            //trace("ts=" + flvts + " tsu=" + tsu + " keyframe = " + keyFrame);

            sendFLVTag(flvts, FLVTags.TYPE_VIDEO, codec, FLVTags.AVC_MODE_PICTURE, flvGenerationBuffer, 0, flvGenerationBuffer.length);
        }

        private function compareBytesHelper(b1:ByteArray, b2:ByteArray):Boolean
        {
            var curPos:uint;

            if(b1.length != b2.length)
                return false;

            for(curPos = 0; curPos < b1.length; curPos++)
            {
                if(b1[curPos] != b2[curPos])
                    return false;
            }

            return true;
        }

        private function sendAACConfigFLVTag(flvts:uint, profile:uint, sampleRateIndex:uint, channelConfig:uint):void
        {
            var isNewConfig:Boolean = true;
            var audioSpecificConfig:ByteArray = new ByteArray();
            var audioObjectType:uint;

            audioSpecificConfig.length = 2;

            switch(profile)
            {
                case 0x00:
                    audioObjectType = 0x01;
                    break;
                case 0x01:
                    audioObjectType = 0x02;
                    break;
                case 0x02:
                    audioObjectType = 0x03;
                    break;
                default:
                    return;
            }

            audioSpecificConfig[0] = ((audioObjectType << 3) & 0xf8) + ((sampleRateIndex >> 1) & 0x07);
            audioSpecificConfig[1] = ((sampleRateIndex << 7) & 0x80) + ((channelConfig << 3) & 0x78);

            if(_aacConfig && compareBytesHelper(_aacConfig, audioSpecificConfig))
                isNewConfig = false;

            if(!isNewConfig)
                return;

            _aacConfig = audioSpecificConfig;
            sendFLVTag(flvts, FLVTags.TYPE_AUDIO, FLVTags.AUDIO_CODEC_AAC, FLVTags.AAC_MODE_CONFIG, _aacConfig, 0, _aacConfig.length);
        }

        /**
         * Convert and amit AAC data.
         */
        public function convertAAC(pes:PESPacket):void
        {
            var timeAccumulation:Number = 0.0;
            var limit:uint;
            var stream:ByteArray;
            var hadRemainder:Boolean = false;
            var cursor:int = 0;
            var length:int = pes.buffer.length;
            var bytes:ByteArray = pes.buffer;
            var timestamp:Number = pes.pts;

            if(_aacRemainder)
            {
                stream = _aacRemainder;
                stream.writeBytes(bytes, cursor, length);
                cursor = 0;
                length = stream.length;
                _aacRemainder = null;
                hadRemainder = true;
                timeAccumulation = _aacTimestamp - timestamp;
            }
            else
                stream = bytes;

            limit = cursor + length;

            // an AAC PES packet can contain multiple ADTS frames
            while(cursor < limit)
            {
                var remaining:uint = limit - cursor;
                var sampleRateIndex:uint;
                var sampleRate:Number = undefined;
                var profile:uint;
                var channelConfig:uint;
                var frameLength:uint;

                if(remaining < FLVTags.ADTS_FRAME_HEADER_LENGTH)
                    break;

                // search for syncword
                if(stream[cursor] != 0xff || (stream[cursor + 1] & 0xf0) != 0xf0)
                {
                    cursor++;
                    continue;
                }

                frameLength  = (stream[cursor + 3] & 0x03) << 11;
                frameLength += (stream[cursor + 4]) << 3;
                frameLength += (stream[cursor + 5] >> 5) & 0x07;

                // Check for an invalid ADTS header; if so look for next syncword.
                if(frameLength < FLVTags.ADTS_FRAME_HEADER_LENGTH)
                {
                    cursor++;
                    continue;
                }

                // Skip it till next PES packet.
                if(frameLength > remaining)
                    break;

                profile = (stream[cursor + 2] >> 6) & 0x03;

                sampleRateIndex = (stream[cursor + 2] >> 2) & 0x0f;
                switch(sampleRateIndex)
                {
                    case 0x00:
                        sampleRate = 96000.0;
                        break;
                    case 0x01:
                        sampleRate = 88200.0;
                        break;
                    case 0x02:
                        sampleRate = 64000.0;
                        break;
                    case 0x03:
                        sampleRate = 48000.0;
                        break;
                    case 0x04:
                        sampleRate = 44100.0;
                        break;
                    case 0x05:
                        sampleRate = 32000.0;
                        break;
                    case 0x06:
                        sampleRate = 24000.0;
                        break;
                    case 0x07:
                        sampleRate = 22050.0;
                        break;
                    case 0x08:
                        sampleRate = 16000.0;
                        break;
                    case 0x09:
                        sampleRate = 12000.0;
                        break;
                    case 0x0a:
                        sampleRate = 11025.0;
                        break;
                    case 0x0b:
                        sampleRate = 8000.0;
                        break;
                    case 0x0c:
                        sampleRate = 7350.0;
                        break;
                }

                channelConfig = ((stream[cursor + 2] & 0x01) << 2) + ((stream[cursor + 3] >> 6) & 0x03);

                if(sampleRate)
                {
                    var flvts:uint = convertFLVTimestamp(timestamp + timeAccumulation);

                    sendAACConfigFLVTag(flvts, profile, sampleRateIndex, channelConfig);
                    //trace("Sending AAC");
                    sendFLVTag(flvts, FLVTags.TYPE_AUDIO, FLVTags.AUDIO_CODEC_AAC, FLVTags.AAC_MODE_FRAME, stream, cursor + FLVTags.ADTS_FRAME_HEADER_LENGTH, frameLength - FLVTags.ADTS_FRAME_HEADER_LENGTH);

                    timeAccumulation += (1024.0 / sampleRate) * 90000.0; // account for the duration of this frame

                    if(hadRemainder)
                    {
                        timeAccumulation = 0.0;
                        hadRemainder = false;
                    }
                }

                cursor += frameLength;
            }

            if(cursor < limit)
            {
                //trace("AAC timestamp was " + _aacTimestamp);
                _aacRemainder = new ByteArray();
                _aacRemainder.writeBytes(stream, cursor, limit - cursor);
                _aacTimestamp = timestamp + timeAccumulation;
                //trace("AAC timestamp now " + _aacTimestamp);
            }
        }

        /**
         * Convert and amit MP3 data.
         */
        public function convertMP3(pes:PESPacket):void
        {
            sendFLVTag(convertFLVTimestamp(pes.pts), FLVTags.TYPE_AUDIO, FLVTags.AUDIO_CODEC_MP3, -1, pes.buffer, 0, pes.buffer.length);
        }

        private function generateScriptData(values:Array):ByteArray
        {
            var bytes:ByteArray = new ByteArray();
            bytes.objectEncoding = ObjectEncoding.AMF0;

            for each (var object:Object in values)
                bytes.writeObject(object);

            return bytes;
        }

        private function sendScriptDataFLVTag(flvts:uint, values:Array):void
        {
            var bytes:ByteArray = generateScriptData(values);
            sendFLVTag(flvts, FLVTags.TYPE_SCRIPTDATA, -1, -1, bytes, 0, bytes.length);
        }

        /**
         * Fire off a subtitle caption.
         */
        public function createAndSendCaptionMessage( timeStamp:Number, captionBuffer:String, lang:String="", textid:Number=99):void
        {
            var captionObject:Array = ["onCaptionInfo", { type:"WebVTT", data:captionBuffer }];
            //sendScriptDataFLVTag( timeStamp * 1000, captionObject);

            // We need to strip the timestamp off of the text data
            captionBuffer = captionBuffer.slice(captionBuffer.indexOf('\n') + 1);

            var subtitleObject:Array = ["onTextData", { text:captionBuffer, language:lang, trackid:textid }];
            sendScriptDataFLVTag( timeStamp * 1000, subtitleObject);
        }

    }
}

package com.kaltura.hls.m2ts
{
    import flash.utils.ByteArray;
    import flash.net.ObjectEncoding;
    import flash.utils.ByteArray;
    import flash.utils.Endian;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;

    /**
     * Responsible for emitting FLV data. Also handles AAC conversion
     * config and buffering.
     */
    public class FLVTranscoder
    {
        public const MIN_FILE_HEADER_BYTE_COUNT:int = 9;

        public var callback:Function;

        private var lastTagSize:uint = 0;

        private var _aacConfig:ByteArray;
        private var _aacRemainder:ByteArray;
        private var _aacTimestamp:Number = 0;

        public function clear(clearAACConfig:Boolean = false):void
        {
            if(clearAACConfig)
                _aacConfig = null;
            _aacRemainder = null;
            _aacTimestamp = 0;
        }

        private function sendFLVTag(flvts:uint, type:uint, codec:int, mode:int, bytes:ByteArray, offset:uint, length:uint):void
        {
            var tag:ByteArray = new ByteArray();
            tag.position = 0;
            var msgLength:uint = length + ((codec >= 0) ? 1 : 0) + ((mode >= 0) ? 1 : 0);
            var cursor:uint = 0;

            if(msgLength > 0xffffff)
                return; // too big for the length field

            tag.length = FLVTags.HEADER_LENGTH + msgLength;
            tag[cursor++] = type;
            tag[cursor++] = (msgLength >> 16) & 0xff;
            tag[cursor++] = (msgLength >>  8) & 0xff;
            tag[cursor++] = (msgLength      ) & 0xff;
            tag[cursor++] = (flvts >> 16) & 0xff;
            tag[cursor++] = (flvts >>  8) & 0xff;
            tag[cursor++] = (flvts      ) & 0xff;
            tag[cursor++] = (flvts >> 24) & 0xff;
            tag[cursor++] = 0x00; // stream ID
            tag[cursor++] = 0x00;
            tag[cursor++] = 0x00;

            if(codec >= 0)
                tag[cursor++] = codec;
            if(mode >= 0)
                tag[cursor++] = mode;

            tag.position = cursor;
            tag.writeBytes(bytes, offset, length);

            cursor += length;
            msgLength += 11; // account for message header in back pointer

            tag.writeUnsignedInt(lastTagSize);

            lastTagSize = tag.length;

            // Dispatch tag.
            if(callback != null)
                callback(flvts, tag);

        }

        public function convertFLVTimestamp(pts:Number):Number
        {
            return pts / 90.0;
        }

        private static var flvGenerationBuffer:ByteArray = new ByteArray();

        /**
         * Convert and amit AVC NALU data.
         */
        public function convert(unit:NALU):void
        {
            // Emit an AVCC.
            var avcc:ByteArray = NALUProcessor.extractAVCC(unit);
            if(avcc)
                sendFLVTag(convertFLVTimestamp(unit.pts), FLVTags.TYPE_VIDEO, FLVTags.VIDEO_CODEC_AVC_KEYFRAME, FLVTags.AVC_MODE_AVCC, avcc, 0, avcc.length);

            // Accumulate NALUs into buffer.
            flvGenerationBuffer.length = 3;
            flvGenerationBuffer[0] = (tsu >> 16) & 0xff;
            flvGenerationBuffer[1] = (tsu >>  8) & 0xff;
            flvGenerationBuffer[2] = (tsu      ) & 0xff;
            flvGenerationBuffer.position = 3;

            // Check keyframe status.
            var keyFrame:Boolean = false;
            var totalAppended:int = 0;
            NALUProcessor.walkNALUs(unit.buffer, 0, function(bytes:ByteArray, cursor:uint, length:uint):void
            {
                // Check for a NALU that is keyframe type.
                var naluType:uint = bytes[cursor] & 0x1f;
                //trace(naluType + " length=" + length);
                switch(naluType)
                {
                    case 0x09: // "access unit delimiter"
                        switch((bytes[cursor + 1] >> 5) & 0x07) // access unit type
                        {
                            case 0:
                            case 3:
                            case 5:
                                keyFrame = true;
                                break;
                            default:
                                keyFrame = false;
                                break;
                        }
                        break;

                    default:
                        // Infer keyframe state.
                        if(naluType == 5)
                            keyFrame = true;
                        else if(naluType == 1)
                            keyFrame = false;
                }

                // Append.
                flvGenerationBuffer.writeUnsignedInt(length);
                flvGenerationBuffer.writeBytes(bytes, cursor, length);
                totalAppended += length;
            }, true );

            var flvts:uint = convertFLVTimestamp(unit.pts);
            var tsu:uint = convertFLVTimestamp(unit.pts - unit.dts);

            var codec:uint;
            if(keyFrame)
                codec = FLVTags.VIDEO_CODEC_AVC_KEYFRAME;
            else
                codec = FLVTags.VIDEO_CODEC_AVC_PREDICTIVEFRAME;

            //trace("ts=" + flvts + " tsu=" + tsu + " keyframe = " + keyFrame);

            sendFLVTag(flvts, FLVTags.TYPE_VIDEO, codec, FLVTags.AVC_MODE_PICTURE, flvGenerationBuffer, 0, flvGenerationBuffer.length);
        }

        private function compareBytesHelper(b1:ByteArray, b2:ByteArray):Boolean
        {
            var curPos:uint;

            if(b1.length != b2.length)
                return false;

            for(curPos = 0; curPos < b1.length; curPos++)
            {
                if(b1[curPos] != b2[curPos])
                    return false;
            }

            return true;
        }

        private function sendAACConfigFLVTag(flvts:uint, profile:uint, sampleRateIndex:uint, channelConfig:uint):void
        {
            var isNewConfig:Boolean = true;
            var audioSpecificConfig:ByteArray = new ByteArray();
            var audioObjectType:uint;

            audioSpecificConfig.length = 2;

            switch(profile)
            {
                case 0x00:
                    audioObjectType = 0x01;
                    break;
                case 0x01:
                    audioObjectType = 0x02;
                    break;
                case 0x02:
                    audioObjectType = 0x03;
                    break;
                default:
                    return;
            }

            audioSpecificConfig[0] = ((audioObjectType << 3) & 0xf8) + ((sampleRateIndex >> 1) & 0x07);
            audioSpecificConfig[1] = ((sampleRateIndex << 7) & 0x80) + ((channelConfig << 3) & 0x78);

            if(_aacConfig && compareBytesHelper(_aacConfig, audioSpecificConfig))
                isNewConfig = false;

            if(!isNewConfig)
                return;

            _aacConfig = audioSpecificConfig;
            sendFLVTag(flvts, FLVTags.TYPE_AUDIO, FLVTags.AUDIO_CODEC_AAC, FLVTags.AAC_MODE_CONFIG, _aacConfig, 0, _aacConfig.length);
        }

        /**
         * Convert and amit AAC data.
         */
        public function convertAAC(pes:PESPacket):void
        {
            var timeAccumulation:Number = 0.0;
            var limit:uint;
            var stream:ByteArray;
            var hadRemainder:Boolean = false;
            var cursor:int = 0;
            var length:int = pes.buffer.length;
            var bytes:ByteArray = pes.buffer;
            var timestamp:Number = pes.pts;

            if(_aacRemainder)
            {
                stream = _aacRemainder;
                stream.writeBytes(bytes, cursor, length);
                cursor = 0;
                length = stream.length;
                _aacRemainder = null;
                hadRemainder = true;
                timeAccumulation = _aacTimestamp - timestamp;
            }
            else
                stream = bytes;

            limit = cursor + length;

            // an AAC PES packet can contain multiple ADTS frames
            while(cursor < limit)
            {
                var remaining:uint = limit - cursor;
                var sampleRateIndex:uint;
                var sampleRate:Number = undefined;
                var profile:uint;
                var channelConfig:uint;
                var frameLength:uint;

                if(remaining < FLVTags.ADTS_FRAME_HEADER_LENGTH)
                    break;

                // search for syncword
                if(stream[cursor] != 0xff || (stream[cursor + 1] & 0xf0) != 0xf0)
                {
                    cursor++;
                    continue;
                }

                frameLength  = (stream[cursor + 3] & 0x03) << 11;
                frameLength += (stream[cursor + 4]) << 3;
                frameLength += (stream[cursor + 5] >> 5) & 0x07;

                // Check for an invalid ADTS header; if so look for next syncword.
                if(frameLength < FLVTags.ADTS_FRAME_HEADER_LENGTH)
                {
                    cursor++;
                    continue;
                }

                // Skip it till next PES packet.
                if(frameLength > remaining)
                    break;

                profile = (stream[cursor + 2] >> 6) & 0x03;

                sampleRateIndex = (stream[cursor + 2] >> 2) & 0x0f;
                switch(sampleRateIndex)
                {
                    case 0x00:
                        sampleRate = 96000.0;
                        break;
                    case 0x01:
                        sampleRate = 88200.0;
                        break;
                    case 0x02:
                        sampleRate = 64000.0;
                        break;
                    case 0x03:
                        sampleRate = 48000.0;
                        break;
                    case 0x04:
                        sampleRate = 44100.0;
                        break;
                    case 0x05:
                        sampleRate = 32000.0;
                        break;
                    case 0x06:
                        sampleRate = 24000.0;
                        break;
                    case 0x07:
                        sampleRate = 22050.0;
                        break;
                    case 0x08:
                        sampleRate = 16000.0;
                        break;
                    case 0x09:
                        sampleRate = 12000.0;
                        break;
                    case 0x0a:
                        sampleRate = 11025.0;
                        break;
                    case 0x0b:
                        sampleRate = 8000.0;
                        break;
                    case 0x0c:
                        sampleRate = 7350.0;
                        break;
                }

                channelConfig = ((stream[cursor + 2] & 0x01) << 2) + ((stream[cursor + 3] >> 6) & 0x03);

                if(sampleRate)
                {
                    var flvts:uint = convertFLVTimestamp(timestamp + timeAccumulation);

                    sendAACConfigFLVTag(flvts, profile, sampleRateIndex, channelConfig);
                    //trace("Sending AAC");
                    sendFLVTag(flvts, FLVTags.TYPE_AUDIO, FLVTags.AUDIO_CODEC_AAC, FLVTags.AAC_MODE_FRAME, stream, cursor + FLVTags.ADTS_FRAME_HEADER_LENGTH, frameLength - FLVTags.ADTS_FRAME_HEADER_LENGTH);

                    timeAccumulation += (1024.0 / sampleRate) * 90000.0; // account for the duration of this frame

                    if(hadRemainder)
                    {
                        timeAccumulation = 0.0;
                        hadRemainder = false;
                    }
                }

                cursor += frameLength;
            }

            if(cursor < limit)
            {
                //trace("AAC timestamp was " + _aacTimestamp);
                _aacRemainder = new ByteArray();
                _aacRemainder.writeBytes(stream, cursor, limit - cursor);
                _aacTimestamp = timestamp + timeAccumulation;
                //trace("AAC timestamp now " + _aacTimestamp);
            }
        }

        /**
         * Convert and amit MP3 data.
         */
        public function convertMP3(pes:PESPacket):void
        {
            sendFLVTag(convertFLVTimestamp(pes.pts), FLVTags.TYPE_AUDIO, FLVTags.AUDIO_CODEC_MP3, -1, pes.buffer, 0, pes.buffer.length);
        }

        private function generateScriptData(values:Array):ByteArray
        {
            var bytes:ByteArray = new ByteArray();
            bytes.objectEncoding = ObjectEncoding.AMF0;

            for each (var object:Object in values)
                bytes.writeObject(object);

            return bytes;
        }

        private function sendScriptDataFLVTag(flvts:uint, values:Array):void
        {
            var bytes:ByteArray = generateScriptData(values);
            sendFLVTag(flvts, FLVTags.TYPE_SCRIPTDATA, -1, -1, bytes, 0, bytes.length);
        }

        /**
         * Fire off a subtitle caption.
         */
        public function createAndSendCaptionMessage( timeStamp:Number, captionBuffer:String, lang:String="", textid:Number=99):void
        {
            //var captionObject:Array = ["onCaptionInfo", { type:"WebVTT", data:captionBuffer }];
            //sendScriptDataFLVTag( timeStamp * 1000, captionObject);

            // We need to strip the timestamp off of the text data
            captionBuffer = captionBuffer.slice(captionBuffer.indexOf('\n') + 1);

            var subtitleObject:Array = ["onTextData", { text:captionBuffer, language:lang, trackid:textid }];
            sendScriptDataFLVTag( timeStamp * 1000, subtitleObject);
        }

    }
}

Fix CC generation breaking video/dupe'ing tags.