AS3 send BitmapData to backend efficiently

I was recently asked to send a medium/large file to a backend server running with php. At first I tried sending byteArray but that got messed up from JSON.strigify then I thought to convert the byteArray to base64 format. Although it worked an image of 500×700 occupied ~500kb on the database so it was unacceptable.
The solution was to compess the byteArray and send it, and then decompress it when I received it again. And here is how I did it:

public static function deconstructBitmap(bmp:BitmapData):Object
        {
            // converting the bitmapdata into a byteArray
            var byteArray:ByteArray = bmp.getPixels(bmp.rect);
            // compressing using the default algorithm 'zlib'
            byteArray.compress();
            var enc:Base64Encoder = new Base64Encoder();  
            var b64;
            enc.encodeBytes(byteArray);
            // joining all in one line
            b64 = enc.drain().split("\n").join("");
            // the rectangle is needed for re-contstruction
            return {'bmpData':b64,'rect':bmp.rect};
        }

And now we have a compressed byteArray with our bitmapData the 500×700 bitmap data now occupies ~13kb on the database!

And here is the re-construction of the byteArray into bitmapData ready to be inserted to

<s:Image/>

or any other component.

public static function constructBitmap(obj:Object):BitmapData
        {
            // to avoid errors
            if(obj!==''&&obj!==undefined&&obj!==null&&obj.bmpData!=='ByteArray'){
                var dec:Base64Decoder = new Base64Decoder();
                dec.decode(obj.bmpData); // decoding using the same algorithm
                var newByteArr:ByteArray=dec.toByteArray();
                newByteArr.uncompress();
                // end of decoding //
               
                var bitmapData:BitmapData = new BitmapData(obj.rect.width, obj.rect.height);
                bitmapData.setPixels(bitmapData.rect, newByteArr);
                return bitmapData;
            }
            else
            {
                return null;
            }
        }

And thats it, two simple functions to make you and your database happy!

Enjoy!

Capitalize first letter of a word with AS3 (quick tip)

After 10′ of searching for a fast solution I came up with this, I suspect there is a much cooler way of capitalizing with AS3, but if I can’t find it on 2 pages of search results it pretty much doesn’t exist…
So here we go, this might work only for single words but that was the intention.

var cap = String('word').substr(0,1);
cap = String(cap).toUpperCase()+String(item.name).slice(1);
trace('A capitalized '+cap); // A capitalized Word

enjoy!

Re-arrange objects in a VGroup by Drag&Drop (Flex Spark)

Adding drag and drop support for Group components, as well as swapping of children or reorder.
This might seem like a straightforward issue and an easy solution, but as I found out… only list based views have the ability to re-arrange their children via dragging and droping. But there is one hidden gem, all the Group based components have build-in support for drag and drop operations, and whilst the user cannot enable directly the dropEnabled option (which allows the re-arrange like operation), it is fairly easy to create a custom functionality to imitate it.

So here we go. We have a VGroup with some children inside.

<s:VGroup id="mainParent" top="100" gap="5" horizontalCenter="0" dragEnter="dragEnterHandler(event);"
              dragDrop="dragDropHandler(event);">
    <s:Button id="button1" label="Button1" height="20"/>
    <s:Button id="button2" label="Button2" height="20"/>
</s:VGroup>

We will see what these functions (dragEnterHandler, dragDropHandler) do later on.

Now what we need to do is link some eventListeners to these children and the parent:
So we create the handler for the mouse move

function init()
{
    button1.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler,false,0,true);
    button2.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler,false,0,true);
}

And the appropriate function:

private function mouseDownHandler(e)
{
    e.currentTarget.addEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler,false,0,true);
}

And now its time to set the function that handles the mouse movement and starts the dragging of the component:

private function mouseMoveHandler(event:MouseEvent):void {
 event.currentTarget.removeEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler,false);
               
    // Get the drag initiator component from the event object.
    var dragInitiator:Button = Button(event.currentTarget);
               
    var dragItem = dragInitiator;
               
    // Create a DragSource object.
    var ds:DragSource = new DragSource();
               
    // Add the data to the object.
    ds.addData(dragItem, 'item');
               
    // Call the DragManager doDrag() method to start the drag.
    DragManager.doDrag(dragInitiator, ds, event);
}

Now that we listen to the mouse move and mouse down, its time to create some dragging functions, since we previously added the listeners to the MXML (this might not be the best way, it is however quite fast)

// Called when the user moves the drag indicator onto the drop target.
newSlidePos = 0;
function dragEnterHandler(event:DragEvent):void {
               
    //Accept the drag only if the user is dragging data
    if (event.dragSource.hasFormat('item')) {
                                   
    if(event.localY < mainParent.measuredHeight/mainParent.numElements)
    {
        newSlidePos = 0;
    }
    else
    {
        newSlidePos = Math.floor(event.localY / (mainParent.measuredHeight/mainParent.numElements));
    }
       
    //Get the drop target component from the event object.
    var dropTarget = VGroup(event.currentTarget);
    //Accept the drop.
    DragManager.acceptDragDrop(dropTarget);
    }
}
           
// Called if the target accepts the dragged object and the user
// releases the mouse button while over the Canvas container.
private function dragDropHandler(event:DragEvent):void {
    // from the drag source.
    var data = event.dragSource.dataForFormat('item');
    mainParent.setElementIndex(data,newSlidePos);
    newSlidePos = 0;
}

So what we do here is accept the drop from the target onto the parent and check if the position of the mouse is less than the total height of the parent and if it is the NEW position of the object is determined from the position of the mouse. For example as in our code each of the buttons have a height of 20 pixels so the total height of the parent (since it is not explicitly defined) is 40 pixels, knowing that if the mouse drags the component below 21 pixels it means it will be moved to position 2, and if it drags in a position less than 20 pixels the component will be dropped at position 1.

I hope all of that made sense, I think its a neat way to add re-arrange support for VGroup components (I like them better than lists… there Adobe I said it… I dislike itemRenderers… ).

Here is an example of this technique: http://www.netgfx.com/trunk/groupDrag/

TweenMax Bezier example

Hello all,

I haven’t post in a while but I have been quite busy creating some exciting stuff that I will be posting in the coming months.

For starters I thought to post an example of a bezier line as explained in the greensock forums. This is not entirelly my code but rather a re-creation from the .fla

So lets begin:

Here we create the main circle that will tween along the bezier path.

public function createMC()
{
var circ = new Sprite();
circ = new Sprite();
circ.name = 'mc';
circ.graphics.beginFill(0xff00ff,1);
circ.graphics.drawCircle(0,0,6);
circ.graphics.endFill();
circ.x = -10;
circ.y = 80;
stage.addChild(circ);

mc = stage.getChildByName('mc');

var button:Sprite = new Sprite();

button.graphics.beginFill(0xffeecc,1);
button.graphics.drawRect(0,0,30,30);
button.graphics.endFill();
button.x = 10;
button.y = 10;
button.width = 30;
button.height = 30;

button.addEventListener(MouseEvent.CLICK,resetLines,false,0,true);

createNodes();
}

Now we create the lines and the circles between them:
<code>
private function createNodes()
{
var circ = new Sprite();
for(var i=0;i {
circ = new Sprite();
circ.name = 'obj'+i;
circ.graphics.beginFill(0xff0000,1);
circ.graphics.drawCircle(0,0,3);
circ.graphics.endFill();
circ.x = i*100;
circ.y = 80;
stage.addChild(circ);
point_mcs.push(circ);
}

Now that we have created the lines and circles, we create the functions for the drag events:

function dragMe(e:Event){
            TweenMax.killAll();
            var object = e.target;
            object.startDrag();
            stage.addEventListener(MouseEvent.MOUSE_MOVE, update)
        }
       
        function stopDragMe(e:Event):void{
            TweenMax.killAll();
            stage.removeEventListener(MouseEvent.MOUSE_MOVE, update)
            stopDrag();
            mc.x = point_mcs[0].x;
            mc.y = point_mcs[0].y;
            TweenMax.to(mc,5,{bezierThrough:bezierPoints});
        }  
       
        function update(e:Event):void{
            mc.x = point_mcs[0].x;
            mc.y = point_mcs[0].y;
            createBezierPointsObject();
            updatePath();
        }

At this point we should have circles that are draggable but the path does not update properly. Now we will exactly that:

function createBezierPointsObject(){
            bezierPoints = [];
            xA = []
            yA = []
            var max:int = point_mcs.length;
            for(var i:int = 0; i < max; i++){
                var curPoint = point_mcs[i];
                if(i!=0){
                    bezierPoints[i] = {x:curPoint.x, y:curPoint.y};
                }
                xA.push(curPoint.x);
                yA.push(curPoint.y);
            }
        }
       
//updating the path as we drag the nodes.
        function updatePath(){
           
            this.graphics.clear(); 
           
            this.graphics.lineStyle(1, 0x0000);
           
            this.graphics.moveTo(point_mcs[0].x, point_mcs[0].y)
            var bezierObj:Object=BezierPlugin.parseBeziers({"x":xA,"y":yA},true);
           
            var pointCount:int=0;
            while(pointCount<bezierObj["x"].length)
            {
                this.graphics.curveTo(bezierObj.x[pointCount][1], bezierObj.y[pointCount][1],bezierObj.x[pointCount][2],bezierObj.y[pointCount][2]);
                pointCount++
            }
           
        }


for(var i:Number = 0; ipoint_mcs[i].mouseChildren = false;
point_mcs[i].addEventListener(MouseEvent.MOUSE_DOWN, dragMe)
stage.addEventListener(MouseEvent.MOUSE_UP, stopDragMe)
}

createBezierPointsObject();
updatePath();
}

Download the project for Flash Builder here:
Bezier

View Example here:
DEMO

OSMF & Strobe Media Playback insert subtitles (xml, srt)

First off I need to explain that the srt support comes from converting the .srt file into .xml
The xml structure that must be achieved is the following:

<optionContainer>
<que sec="15" duration="3">
<data>
<value>At the left we can see...</value>
</data>
</que>
<que sec="18" duration="2">
<data>
<value>At the right we can see the...</value>
</data>
</que>
</optionContainer

full file sample at the end of page.

And now on to the hard part:

First we need to make a call to download the subtitles XML and parse them into an array:

public function readXML(src:String){
    xmlLoader.load(new URLRequest(src));
    xmlLoader.addEventListener(Event.COMPLETE,readXMLData,false,0,true);
}

private var xml_options:Array = new Array();

private function readXMLData(e:Event):void
        {
            xmlLoader.removeEventListener(Event.COMPLETE,readXMLData,false);
            try{
                var xmlData = new XML(e.target.data);
                var counter:Number = 0;
                for each(var item in xmlData.que)
                {
                    var totalItems:Number = item.data.length();
                   
                    xml_options['que_'+item.@sec] = {'time':Number(item.@sec),
                        'duration':Number(item.@duration),
                        data:[{'title':String(item.data[0].value[0])}
                        ]
                    };
                   
                    counter += 1;
                }
            }
            catch(err:Error){
                trace(err.message);
            }
            addCues();
        }

Now we have an array filled with our subtitles and timings.

At this point we add the addCues function what this function does is to add cue points to the video stream and create a timelineMetaData object, when each entry point of a cue is due time an event will fire, we also create our label here (color is not pure white in order to be visible on white background)

private var subtext:TextField = new TextField();
private function addCues():void
        {
            player.currentTimeUpdateInterval = 100;
            timelineMetaData = new TimelineMetadata(player.media);
            timelineMetaData.addEventListener(TimelineMetadataEvent.MARKER_TIME_REACHED, onCuePointHandler, false, 0, true);
            timelineMetaData.addEventListener(TimelineMetadataEvent.MARKER_DURATION_REACHED, removeCuePoint,false,0,true);
           
            var cuePoint:CuePoint;
            var counter = 0;
           
            for each(var item in xml_options){
                // add a cuepoint below:
                cuePoint = new CuePoint(CuePointType.ACTIONSCRIPT, item.time, "CuePoint"+item.time, [item.data[0].title],item.duration);
               
                timelineMetaData.addMarker(cuePoint);
                counter += 1;
            }
           
            subtext.selectable = false;
            subtext.setTextFormat(format);
            subtext.defaultTextFormat = format;
            subtext.text = '';
            subtext.textColor = 0xFCF5D1;
            subtext.width = _stage.stageWidth/2;
            subtext.height = 22;
            subtext.x = _stage.stageWidth/2-subtext.textWidth;
            subtext.y = _stage.stageHeight-60;
            subtext.wordWrap = true;
            subtext.name  = 'subtext';
           
            this.addChild(subtext);
            SubItem = this.getChildByName('subtext'); //for easy reference
        }

At this point we have our cues loaded into the media and the event listeners ready to fire.
So lets create the corresponding functions the first one will handle the showcasing of the subtitle (we add a timer in order to remove idle subtitles when their time is passed, at this point I will mention that the duration reached event does not fire as it should and has a delay thus not removing the subtitle as it is supposed to, this solution is custom):

var timer:Timer = new Timer(4000,0); // 4secs or 4000ms is the minimum time the human eye can read 2-line subtitles.

private function onCuePointHandler(e:TimelineMetadataEvent):void
        {
            if(timer.running){
                timer.stop();
                timer.removeEventListener(TimerEvent.TIMER,removeCue,false);   
            }
            timer = new Timer((Number(e.marker.duration)+100)*1000,0);
            timer.start();
            timer.addEventListener(TimerEvent.TIMER,removeCue,false,0,true);
           
            var params = e.marker;
           
            SubItem.text = '';
            SubItem.text = String(params.parameters[0]);
            SubItem.x = _stage.stageWidth/2-SubItem.textWidth/2;
        }

//Removes the cue after 2.1 secs of idleness
private function removeCuePoint(e):void
        {
            timer.stop();
            timer.removeEventListener(TimerEvent.TIMER,removeCue,false);
            timer = new Timer(2100,0);
            timer.start();
            timer.addEventListener(TimerEvent.TIMER,removeCue,false,0,true);
        }

//the actual removal function
private function removeCue(e)
        {
            timer.stop();
            timer.removeEventListener(TimerEvent.TIMER,removeCue,false);
            SubItem.text = '';
        }

Now simply call the readXML function after the media has been initialized on SMP put it at the end of the loadMedia(…_) function.

So this is it now you have subtitles on your videos, again this is a custom solution and not the typical OSMF Plugin. But it works and it is simple enough to implement.
The video I used is this: http://mediapm.edgesuite.net/osmf/content/test/manifest-files/dynamic_Streaming.f4m

and the source XML is here: sample.xml

If I can convince my friend that did the conversion script from .srt to XML to release his code I will post it here too. But it should not be difficult just make sure to convert frames into seconds. (Flowplayer captions plugin source code can help a lot with this).
Enjoy!

Android Application Instagib

Yesterday I launched my application in the Android Marketplace called Instagib, it is made on Air with Flex and AS3.

Its a utility application which allows users to exchange info through QR code, even links or contact information. What is important is that the QR works with even the default android QR reader so that no one is “forced” to install the application in order to receive data from someone that has it.

It also features a built-in web-browser using the Air StageWebView, a built-in twitter client! which links open in the application browser so that they can be shared via QR.

Make sure to check it out: Instagib App

I’ll be sure to post some parts of the application as it is free and mostly based on open source libraries.

Leave a comment if you want to request some particular part be explained here.

Enjoy!

Create Social Sharing Buttons in AS3

Hey all, recently I had to create a series of sharing buttons for a client so I went all over the internets in search for those sharing urls, then I had to fight against the popUp blockers and externalInterface problems, but I managed to pull it off and I present you with some very usefull functions, if one of those urls here changes drop me a line and I’ll try to find a replacement.

So here we go I’ll present the functions and then a single function that manages the display:

        // FACEBOOK
        private function shareFB(e:Event):void
        {
            openPage('http://www.facebook.com/sharer/sharer.php?t=A+cool+video&u='+escape("http://flepstudio.org/utilita/VideoPlayer/IronMan2.mov"),"_popup");
        }
       
        // TWITTER
        private function shareTwitter(e:Event):void
        {
            openPage('https://twitter.com/intent/tweet?source=webclient&text=A+cool+video%3A+'+escape("http://flepstudio.org/utilita/VideoPlayer/IronMan2.mov"),"_popup");
        }
       
        // MAIL
        private function shareMail(e:Event):void
        {
            var request:URLRequest = new URLRequest("mailto:"+address+"?subject="+videoInfo.videoTitle+"&body="+"\n\n Video Link: "+videoInfo.videoLink);            
            navigateToURL(request, "_self");
        }
       
        // TUMBLR
        private function shareTumblr(e:Event):void
        {
            openPage("http://www.tumblr.com/share/link?url=" + escape(videoInfo.videoLink) + "&name=" + escape(videoInfo.videoTitle) + "&description=" + escape(videoInfo.videoArtist),'_popup');
        }
       
        // STUMBLE UPON
        private function shareSU(e:Event):void
        {
            openPage("http://www.stumbleupon.com/submit?url="+escape(videoInfo.videoLink)+"&title="+escape(videoInfo.videoTitle));
        }
       
        // GOOGLE +
        private function shareGoogle(e:Event):void
        {
            openPage("https://m.google.com/app/plus/x/?v=compose&content="+escape(videoInfo.videoLink),"_popup");
        }
       
        // LinkedIn
        private function shareLinkedIn(e:Event):void
        {
            openPage("http://www.linkedin.com/shareArticle?mini=true&url=CONTENT-URL&title="+escape(videoInfo.videoArtist)+"&summary="+escape(videoInfo.videoArtist)+"&source="+escape(videoInfo.videoTitle),'_popup');
        }
       
        // DIGG
        private function shareDigg(e:Event):void
        {
            openPage("http://digg.com/submit?phase=2&url="+escape(videoInfo.videoLink)+"&title="+escape(videoInfo.videoTitle)+"&bodytext="+''+"&topic="+escape(videoInfo.videoArtist));
        }
       
        //BEBO
        private function shareBebo(e:Event):void
        {
            openPage("http://www.bebo.com/c/share?Url="+escape(videoInfo.videoLink)+"&Title="+escape(videoInfo.videoTitle),'_popup');
        }
       
        //ORKUT
        private function shareOrkut(e:Event):void
        {  
            openPage("http://www.orkut.com/FavoriteVideos.aspx?u="+escape(videoInfo.videoLink),'_popup');
        }
       
        //REDDIT
        private function shareReddit(e:Event):void
        {
            openPage("http://www.reddit.com/submit?url="+escape(videoInfo.videoLink),'_popup');
        }
       
        // DELICIOUS
        private function shareDelicious(e:Event):void
        {
            openPage("http://www.delicious.com/save?v=5&jump=close&url="+escape(videoInfo.videoLink)+"&title="+escape(videoInfo.videoTitle));
        }
       
        // MYSPACE
        private function shareMySpace(e:Event):void
        {
            openPage("http://www.myspace.com/Modules/PostTo/Pages/?t="+escape(videoInfo.videoTitle)+"&c="+escape(videoInfo.videoArtist)+"&u="+escape(videoInfo.videoLink)+"&l="+escape(videoInfo.videoLink),'_popup');
        }

These are most of the social networks that I could think of, but it is almost the same for everything else I suppose.
And the following is the master function that handles the popup. (This is based on a function used by flowplayer and their sharing plugin, but it is generic stuff).

public static function openPage(url:String, linkWindow:String = "_blank", popUpDimensions:Array = null):void {
            if (linkWindow == "_popup" && ExternalInterface.available) {
                var dimensions:Array = [800,600];
                ExternalInterface.call("window.open('" + url + "','PopUpWindow','width=" + dimensions[0] + ",height=" + dimensions[1] + ",toolbar=yes,scrollbars=yes')");
            } else {
                // Use JS to bypass popup blockers if ExternalInterface is available
                var window:String = linkWindow == "_popup" ? "_blank" : linkWindow;
                if (ExternalInterface.available) {
                    ExternalInterface.call('window.open("' + url + '","' + window + '")');
                } else {
                    //request a blank page
                    navigateToURL(new URLRequest(url), window);
                }
            }
        }

There now you can use these and link them into buttons for your sharing pleasure.
You can view an example here: http://www.netgfx.com/trunk/social/
Enjoy!

Change the event category in the GTrack OSMF plugin (dynamically)

This is somewhat a spin-off from the Media Player Anatomy series of tutorials that I’m working on, but since there is nothing to be found out there I thought to post this in case it helps anyone.
From a series of emails that I had with a representative of the RealEyes Media the only way to change the event category that is being send to the server is by changing the config.xml file.
It seemed rather insane to have a single config.xml for each category so I thought to create one on the fly as it only needs to be xml (file or build with as3 makes no difference).
FYI this is the reops tutorial on the GTrack plugin (a really nice plugin and one of the few left for free nowadays…)
GTrack Plugin Tutorial

We assume that the initial load has been done and now we want to change the category into “music”
we declare an XML template:

public var xmlTemplate:XML = <value key="reTrackConfig" type="class">
            <account>UA-26485023-1</account>
            <account>UA-24092711-1</account>
            <url>http://www.netgfx.com/trunk/samplePlayer/sample.html</url>
        <event name="percentWatched" category="video" action="percentWatched">
            <marker percent="0" label="start"/>
            <marker percent="25" label="view"/>
            <marker percent="50" label="view"/>
            <marker percent="75" label="view"/>
        </event>
        <event name="complete" category="video" action="complete" label="trackingTesting" value="1"/>
        <event name="pageView"/><event name="playStateChange" category="video" action="playStateChange" label="play/pause" value="1"/>
        <debug>true</debug>
        <updateInterval>250</updateInterval>
        </value>;

And then we change it:

public function createXMLConfig(cat:String = 'music'):XML
{
var attr:XML = new XML(xmlTemplate);

var arr:XMLList = new XMLList();
arr = attr.event;
for(var i:Number=0;i {
attr.event[i].@category = cat;
}

return attr;
}

This can be then loaded as follows:

var customPluginConfigXML:XML = createXMLConfig();
pluginResource.addMetadataValue( "http://www.realeyes.com/osmf/plugins/tracking/google", customPluginConfigXML );

At this point you might want to remove the previous MetadataValue before assigning the new one.

Enjoy!

Simple Loader with AS3 and TweenMax

This a loader that I created for my OSMF-based player. This is lightweight and simple to use.
Colors and shapes can easily be customized, text could also be included and event animated to match blocks.

The creation function, simple drawing.

public function createLoader(bgColor:uint=0x2e2e2e):void
{
this.x = 0;
this.y = 0;
bg = new Sprite();
bg.graphics.beginFill(bgColor,1);
bg.graphics.lineStyle(3,0xf7f7f7,1,true);
bg.graphics.drawRoundRectComplex(0,0,200,100,5,5,5,5);
bg.graphics.endFill();
var gap:Number = 45;
var block:Sprite;

for(var i:int = 0;i<6;i++){
                block = new Sprite();
                block.graphics.beginFill(0xffffff,1);
                block.graphics.lineStyle(1,0x7e7e7e,1,false);
                block.graphics.drawRect(gap+(20*i),40,9,22);
                block.graphics.endFill();
                bg.addChild(block);
            }
           
            this.addChild(bg);
            tint();

And the function that handles the animation, it applies colorization to each block in order then calls it self with the index increased by one.

private function tint(value:Number=0):void
{
    var length:Number = bg.numChildren;
    var indexItem:Number;
for(var i:int = 0;ilength-1)
{
indexItem = 0;
}
else
{
indexItem = value;
}

TweenMax.to(bg.getChildAt(indexItem), .2, {colorMatrixFilter:{colorize:0x3399ff, amount:1.2, contrast:1, brightness:1, saturation:1, hue:50}, onComplete:function(){

TweenMax.to(bg.getChildAt(indexItem), .6, {colorMatrixFilter:{colorize:0xffffff, amount:1}, ease:Quad.easeInOut});
tint(indexItem+1);
}});
}

The release version is a mere 20kb file.

Download the source here: UILoader

View Example here: UILoader-Demo