Mostrando entradas con la etiqueta Flex. Mostrar todas las entradas
Mostrando entradas con la etiqueta Flex. Mostrar todas las entradas

miércoles, 7 de agosto de 2013

Install Apache Flex 4.10 Ubuntu 13.04 64 bits

So finally! The guys at Apache Flex has made available an installer for the latest release of the 4.10 Apache Flex SDK version for Linux/Ubuntu. It take some time, but finally an installer for our beloved platform. So let's install it:

First you must have Adobe Air 2.6 installed, here is a great tutorial. Next download the deb file containing the installer here.


Next double click and ... what an error ?


If you have Adobe Air installed you need to check the version to do this just run this command:


dpkg -l adobeair



and you should get something like this:


So, i if my math is still good i suppose that version 1:2.6.0.1917 is greater than 1:2.6.0.0, so i just ignore this and use this command to install the package:

sudo dpkg -i --force-depends apache-flex-sdk-installer-2.6.0-bin.deb 

and voilà:



From there just use the handy wizzard, and after some time you will get this.




Congratulations. I hope this short tutorial helps, if you need any extra help just comment and i will be happy to help you.


lunes, 3 de octubre de 2011

Using Embed Jetty and BlazeDS Remoting with Adobe AIR

When we need to communicate with server side data from an Adobe AIR application we usually use HTTP services, Web Services or Remoting this works great, but what happens when we need to create an standalone application? We have options like merapi, flerry and transmission...

I want to show you how to embed Jetty Server in an Adobe Air application and use BlazeDS for communication. The use case is simple access a MySQL server directly from Adobe AIR but with this option you might experiment and create better use cases for this.


We need to download the following:
  • Jetty library (i use jetty-all-7.4.0.RC0.jar)
  • Servlet api (i use servlet-api-2.5.jar)
  • Mysql Connector
  • BlazeDS 4

Let's coding:

First we create a simple table for sample purposes:


Java side:
First we create a simple Java Project and add the 2 jars in the classpath.

Product.java

Here i use plain JDBC but you can use JPA, Hibernate, etc. 
ProductDAO.java

ProductServices.java

This is a very important class, here we are going to create the Server instance and start it. 
Note: I assume that you know something about embedding servers, if you want some background here you can find a great resource.

BlazeDSServer.java

Now we need to extract the BlazeDS.war content. create a folder called webapp inside your src folder with this content, your project structure should look like this.



The last thing you need to do is declare a remote destination on your services-config.xml



Now we code the Air side:
Fist create an asset folder inside your air src folder and copy all the bin folder from your java project

As you can see i made the same copy into the bin-debug folder, i don't know why it doesn't export all the files to the bin-debug.

Now let's code the application, first in the descriptor file enable the extended desktop profile so you can use Native Process




Now  the code:

EmbedJetty.mxml


Now let's take a more closer view to the code:
  • First the executable file will be the Java path so it depends on your machine.
  • I use NativeProcess to start the embed Jetty instance and a socket to shutdown the embed jetty instance.
  • The arguments for the NativeProcessStartupInfo are the necessary classes to make the Java desktop app run. In the application:
args.push("-classpath");
args.push(".;lib/jetty-all-7.4.0.RC0.jar;lib/servlet-api-2.5.jar;lib/mysql-connector.jar");
args.push("BlazeDSServer"); 
  •  Once it connects you can use RemoteObjects to make rpc call to your Java code.
  • You can use messaging too.
Before you run check your ip match with the socket connection ip, and verify the JDBC setting in the Java code.

When you click the start server button, in the text area you see the log comming from the server.

Now click the Get Data button and in my case the screen will be the following:
 

Stop the server by clicking the Stop Server button.

And that's all, hope this will help you, any question or bug please let me know.
Here is the source code for the Java Project
Here is the Source code for the Flex Project

viernes, 26 de agosto de 2011

Using BlazeDS Remote Object and Java to Upload and get Files

In a Java/Flex project i have been working on we need to upload and download images, simply task no? Yes... is simple we use the FileReference class and point this to a servlet then simply use the upload method.

Really easy when you run in your local machine but what happens when we run this in a remote machine... let me think... ah! yes the lovely Flash Player Sandbox Security Exception... ok! we can handle it, just use crossdomain.xml file and run your application again and... Flash Player Sandbox Security Exception again (dear this is becoming my best friend), then i read, to upload a file Flash player uses the socket api and now we need a socket policy file... so i give up and try to do it in another way.

Today i show you how you can do a kind of  "upload" and "show" a remote file using Remote Objects and the lovely BlazeDS. So let's start

We use a Java class (not servlet) to manage the upload and the get of a file.

In the Java side we have two methods uploadFile and getFile, the first one uses the name, the directory and the content as an byte array to write a file in the disk using the FileOutputStream class. For the second method we need the full file name (directory + file name) and use the FileInputStream to read it and put the data in a byte array and the send it to Flex.

FileUtils.java  


Don't forget to change your remoting-config.xml :-)

And in the flex side we still use the FileReference but this time only for load the file, and then we use remote objects to send the chunk of data, the file name and the directory to the upload method, for getting the file the java side just send the chunk of data and we manage it with a ByteArray and show it.



That's all now you can upload a file using a remote object and show this file with this two methods, and remember to secure your endpoints and externalize your server configuration. Any question or mistakes just let me know. Thanks for reading.

domingo, 15 de mayo de 2011

Creating Dynamic Remote Destinations with BlazeDS

Doing remoting with Flex and BlazeDS is great, you set up your remoting-config.xml with your destinations and them consume this destination with your Flex client, that's good if you have your destinations hardcoded in your xml file, but there are times when you want to create your remoting destinations dynamically, taking this post as a base i will show you how to do this with BlazeDS in a simple example.

First we create a class called DestinationHelper this will contain all the classes you want to create at run time, read the comments so you know exactly what are you doing for a deeper explanation please read this post.




I take the great explanations of Nick Kwiatkowski  and change a little so this can match the example, please all the credits go to him.


The first thing we do is call the createDestination() function on the remoting-service and cast this to a RemotingDestination.  This will pass back a reference to the new destination that was created for us with the name we passed in. Next, we set the source property.  Since we are working within Java, this could be the dot-path-name of the Java class we are working with, or * to allow our dynamic destination to talk to ANY Java Class.   
The next thing we do is set up the Adapter.  The adapter is what actually processes the requests from our connected Flex client and sends it over to Java to be worked on.  In our case, I am having a Java Adapter do the processing, so I went with the “java-object” adapter.  There are a variety of adapters available, and depending on your situation, you may want to choose from a list of adapters, or use the getDefaultAdapter() method to find out what is the default for the service you are working on.  Creating an instance of the adapter involves calling the createAdapter() method on the destination.  What is returned to you is a reference to the adapter that isn’t initialized yet.  When diving through the source code — this is what screwed me up the most, as I was under the impression that the createAdapter() was all I had to call to get things working.  If that is all you do, you will get NullPointerException errors when you try to pass data to the destination. 

In order to initialize the adapter, you need to pass in a property with the type of a ConfigMap.  The ConfigMap holds the configuration properties that the adapter is expecting.  There are lots of properties that could be set (and if you want to get a general idea of what they are, take a look at the XML config).  In theory, you should be able to pass in an empty ConfigMap to the initialize method and it will take all the defaults (which work in my case), but there is a bug in the JavaAdapter (Yes in the Java adapter too) that requires to you set the use-mappings and method-access-level properties to something or you can’t send any data to the destination.  These two properties are within a ConfigMap named “access” (again, take a look at the XML in the remoting-config.xml to see how this translates).  Finally, in order to initialize the adapter, we pass in the ConfigMap we just setup to the initialize() method and we are set.

Our final steps include binding our newly created adapter instance to the destination by using the setAdapter() method, and setting the channel that the destination will bind to.  If I wasn’t making the assumption that the channel “my-amf” already exists, I could call the getChannels() method on the Message Broker to verify that it is one of the ones available.  If the channel wasn’t available, I could create using a similar method as above.  And finally, we need to start the instance.  Call the start() method to start it.  If everything is set up right, you should be able to send data to your new destination!

jueves, 3 de febrero de 2011

Realtime Data Synchronization between Flex and Air for Android using Red5

After a January break, i going to write more about Air for Android specifically targeting to Realtime data synchronization, in this post i'm going to show you how you can synchronize Adobe Air with Adobe Air for Android in true realtime using Red5 RTMP.

Requirements:
  • Adobe Flash Builder Burrito Preview.
  •  Flex 4.5 "Hero" (Comes with Flash Builder Burrito)
  • Red5 Server.
  • Eclipse 3.4+ with Red5 Plugin installed. If you don't know how to install here is a little tutorial.
  • Some basic AS3 knowledge is recommended. 
Hardware:
  • Toshiba dualcore laptop 6gb ram running windows 7.
  • Google Nexus S smartphone with Air for Android installed.  
 So let's code.

Server Side:
If you successfully install Red5 plugin into eclipse, this part will be straightforward. Create a new Dynamic Web Application and call it MoveServer, set the Target Runtime to Red5, in Configuration click the Modify button and make sure  that Red5 Application Generation is Selected.



Now click finish and the Red5 Plugin will generate two Projects a Java Dynamic Web Application and a Flex application, delete the flex application. Now one more change, go to the red5-web.properties located in under WebContent/Web-INF and add the ip of your machine in your virtualHosts. You might wondering why do this since there is a localhost well when you try to connect from your phone it will try to connect to localhost and since there is no localhost in the phone it will not work.

red5-web.properties
webapp.contextPath=/MoveServer
webapp.virtualHosts=localhost, 127.0.0.1, 192.168.1.2,


Thats all for the server side. Now let's continue with the client side: The application that i will build is pretty simple an image in the screen that uses the phone accelerometer to move around the screen and for the desktop version instead of the accelerometer y use mouse events to move the image this to applications will be synchronize in real time.

Client Side:
In Flash Builder Burrito create a new Flex Mobile Project call this BallMovementMobile and Select Hero has your Flex SDK.


Next target to Google Android, select Blank as the application template and finally uncheck Automatically Reorient, you don't need any server settings so hit finish.



 Now search for any image and use it, i use a soccer ball. Put the image in a assets folder under the src folder



 Now add and image component to your mxml file:

BallMovementMobile.mxml


Now let check if our phone has accelerator and add the event listeners so we can use the accelerometer events, add an creation complete event in your main application tag and add the following:

BallMovementMobile.mxml


The next step is add a net connection and a shared object to out application:

BallMovementMobile.mxml


Some words about the code above:
  • I put the accelerometer.setRequestUpdateInterval to 50 so i can get a faster update from the accelerometer, here you can play with the values to preserve your battery life.
  • In the SharedObject getRemote method i named it "victor" you can put any value you want.
  • The Sync event is where all the magic happens, here all the clients are notified that something has change and they must synchronize with that changes.
 Now let's add the code for sync and accelerometer update events:

BallMovementMobile.mxml


Some words about the code:
  • In the accelerometerUpdate handler i calculate the upper bound and the lower bound for the width and height, then get the x and y values given by the accelerometer, check if the value is not greater than the upper bound or lower than the lower bound and update the image x and y has necessary.
  • When i update the shared object i call it "ballCoordinates" again you can use any word here.
  • In the sync event i get the shared object data and update the image x and y.
  • Finally i add and native deactivate event so the application will not run in the background, again this is for cpu saving. (You can read more Air for Android tips here)
The final step for the mobile application, change the application descriptor properties as follows:

Now create an Air application (not mobile) use the same image and add the following code:



There are a little code differences but it's almost the same application.

Now run both applications and move your phone and the two screens will be synchronized in realtime.

That's all here is the source code for the mobile the desktop and the server application. Here is a quick demo of the application:



Recommendation: When you use the source code please make sure you change the parameters according to your system.

If you have any question or found a mistake or bug please let me know. Thanks

sábado, 4 de diciembre de 2010

Red5 + BlazeDS = Realtime Video Chat Tutorial Part 4: BlazeDS chat.

This is the last part of the Series Red5 + BlazeDS = Realtime Video Chat. In this part we are going to build a normal chat with BlazeDS so users can chat with al the people connected on the application. To accomplish this we need explore the messaging features in BlazeDS and also see a little about Consumer / Producer in Flex.

First in our server side open the services-config.xml and add the following channel:

services-config.xml

        <channel-definition id="my-streaming-amf" class="mx.messaging.channels.StreamingAMFChannel">
        <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/streamingamf"
                class="flex.messaging.endpoints.StreamingAMFEndpoint"/>
               <properties>
                   <idle-timeout-minutes>0</idle-timeout-minutes>
                   <max-streaming-clients>10</max-streaming-clients>
                   <server-to-client-heartbeat-millis>5000</server-to-client-heartbeat-millis>
               </properties>      
        </channel-definition>

In the code above we define a new StreamingAMF channel called my-streaming-channel now we need to specify that we are going to use this channel in our chat, now open the messaging-config.xml and add:

    <destination id="chat">
        <channels>
            <channel ref="my-streaming-amf"/>
        </channels>
    </destination>

We are creating a destination for out chat and specify that our chat will use a Streaming channel. That’s all in our server side, now let’s make an interface for out chat.

Open Red5_BlazeDS_Flex.mxml and add a TextArea and a TextInput inside a form like this:

<s:states>
        <s:State name="login"/>
        <s:State name="main"/>
    </s:states>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:Group verticalCenter="0" horizontalCenter="0" includeIn="login">
        <s:Form>
            <s:FormItem label="Username">
                <s:HGroup>
                    <s:TextInput id="txtUsername"/>
                    <s:Button id="btnConnect" label="Login" click="btnConnect_clickHandler(event)"/>
                </s:HGroup>
            </s:FormItem>
        </s:Form>
    </s:Group>
    <s:HGroup width="100%" height="100%" includeIn="main">
        <s:VGroup width="30%" height="100%" paddingBottom="10" paddingLeft="10" paddingRight="10"
                  paddingTop="10">
            <s:Form width="100%">
                <s:FormItem label="Broadcast">
                    <s:HGroup>
                        <s:TextInput id="txtBroadcast"/>
                        <s:Button id="btnBroadcast" label="Broadcast" click="btnBroadcast_clickHandler(event)"/>
                    </s:HGroup>
                </s:FormItem>
            </s:Form>
            <mx:UIComponent id="outVideoWrapper" width="300" height="200"/>
            <s:Form>
                <s:FormItem label="Subscribe">
                    <s:HGroup>
                        <s:TextInput id="txtSubscribe"/>
                        <s:Button id="btnSubscribe" label="Subscribe" click="btnSubscribe_clickHandler(event)"/>
                    </s:HGroup>
                </s:FormItem>
            </s:Form>
            <mx:UIComponent id="inVideoWrapper" width="300" height="200"/>
        </s:VGroup>
        <s:VGroup width="100%" height="80%" paddingBottom="10" paddingLeft="10" paddingTop="10" paddingRight="10">
            <mx:Form width="100%" height="100%">
                <mx:FormItem width="100%" height="100%">
                    <s:TextArea id="txtConversation" width="100%" height="100%" editable="false"/>
                </mx:FormItem>
                <mx:FormItem  width="100%">
                    <s:TextInput id="txtChat" width="100%" enter="txtChat_enterHandler(event)"/>
                    <s:Button id="btnSend" label="Send" click="btnSend_clickHandler(event)"/>
                </mx:FormItem>
            </mx:Form>
        </s:VGroup>
    </s:HGroup>
    <s:TextArea id="txtLog" width="100%" height="100" color="red" bottom="0"
                editable="false" includeIn="main"/>

 

Now we must add the chat logic as follows:

  • Add a Producer and Consumer in the declarations tags:

   <fx:Declarations>
        <s:Producer id="producer" destination="chat"/>
        <s:Consumer id="consumer" destination="chat" message="consumer_messageHandler(event)" fault="consumer_faultHandler(event)"/>
        <mx:DateFormatter id="hourFormatter" formatString="KK:NN:SS"/>
    </fx:Declarations>

 

  • Create the events handlers for txtChat enter event and for btnSend click event:

           protected function btnSend_clickHandler(event:MouseEvent):void
            {
                var message:AsyncMessage = new AsyncMessage();
                message.body = txtChat.text;
                message.headers.user = txtUsername.text;
                producer.send(message);
                txtChat.text = "";
            }


            protected function consumer_messageHandler(event:MessageEvent):void
            {
                var hora:Date = new Date();
                var message:String = event.message.body as String;
                var usuario:String = event.message.headers.user as String;
                txtConversation.text += "[" + hourFormatter.format(hora) + "]" + usuario + " says: " + message + "\n";
            }


            protected function txtChat_enterHandler(event:FlexEvent):void
            {
                var message:AsyncMessage = new AsyncMessage();
                message.headers.user = txtUsername.text;
                message.body = txtChat.text;
                producer.send(message);
                txtChat.text = "";   
            }

            protected function consumer_faultHandler(event:MessageFaultEvent):void
            {
                txtLog.text += event.faultString + "\n";
            }

Now test your application and your application should look like this:

Here is the complete Source code:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               creationComplete="initApp()" width="100%" height="100%">
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import mx.controls.Alert;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            import mx.messaging.events.MessageEvent;
            import mx.messaging.events.MessageFaultEvent;
            import mx.messaging.messages.AsyncMessage;
            import mx.rpc.events.FaultEvent;
           
            private var connection:NetConnection;
            private var userWindow:UsersWindow;
            private var timer:Timer;
            //Streams
            private var inStream:NetStream;
            private var outStream:NetStream;
            //Devices
            private var camera:Camera;
            private var microphone:Microphone;
            //Video
            private var inVideo:Video;
            private var outVideo:Video;
           
            private function initApp():void {
                this.systemManager.stage.scaleMode = StageScaleMode.NO_SCALE;
               
            }
            protected function btnConnect_clickHandler(event:MouseEvent):void
            {
                if(txtUsername.text.length >= 3){
                    producer.connect();
                    consumer.subscribe();
                    userWindow = new UsersWindow();
                    timer = new Timer(2000);
                    timer.start();
                    timer.addEventListener(TimerEvent.TIMER, onTimerEvent);
                    currentState = "main";
                    connection = new NetConnection();
                    connection.connect("rtmp://localhost/Red5_BlazeDS_Java", txtUsername.text);
                    connection.addEventListener(NetStatusEvent.NET_STATUS, onConnectionStatus);
                    connection.client = this;
                }else{
                    txtUsername.errorString = "Enter a valid name";
                }
            }
            protected function onTimerEvent(event:TimerEvent):void {
                connection.call("getConnectedClients", new Responder(onResult, onFault));
            }
            protected function onResult(obj:Object):void {
                userWindow.setUsers(new ArrayCollection(obj as Array));
            }
            protected function onFault(obj:Object):void {
                txtLog.text += "Error " + obj.fault.message + "\n";
             }
            protected function onConnectionStatus(event:NetStatusEvent):void {
                if(event.info.code == "NetConnection.Connect.Success"){
                    txtLog.text += "Connection to RTMP successfully established\n";
                    connection.call("getConnectedClients", new Responder(onResult, onFault));
                    userWindow = UsersWindow(PopUpManager.createPopUp(this, UsersWindow, false));
                    PopUpManager.centerPopUp(userWindow);
                    userWindow.usuarioActual = txtUsername.text;
                }else{
                    txtLog.text += "Connection to RTMP fail\n";
                }
            }

            protected function btnBroadcast_clickHandler(event:MouseEvent):void
            {
                if(txtBroadcast.text.length > 3){
                    txtBroadcast.errorString = "";
                    //setup devices
                    camera = Camera.getCamera();
                    microphone = Microphone.getMicrophone();
                    //setup the streams
                    outStream = new NetStream(connection);
                    outStream.attachAudio(microphone);
                    outStream.attachCamera(camera);
                    outStream.publish(txtBroadcast.text);
                    //setup out video
                    outVideo = new Video(300,200);
                    outVideo.attachCamera(camera);
                    outVideoWrapper.addChild(outVideo)
                }else{
                    txtBroadcast.errorString = "Put a valid broadcast name";
                }
            }


            protected function btnSubscribe_clickHandler(event:MouseEvent):void
            {
                if(txtSubscribe.text.length > 3){
                    inStream = new NetStream(connection);
                    inStream.play(txtSubscribe.text);
                    inVideo = new Video(300,200);
                    inVideo.attachNetStream(inStream);
                    inVideoWrapper.addChild(inVideo);
                }
            }


            protected function btnSend_clickHandler(event:MouseEvent):void
            {
                var message:AsyncMessage = new AsyncMessage();
                message.body = txtChat.text;
                message.headers.user = txtUsername.text;
                producer.send(message);
                txtChat.text = "";
            }


            protected function consumer_messageHandler(event:MessageEvent):void
            {
                var hora:Date = new Date();
                var message:String = event.message.body as String;
                var usuario:String = event.message.headers.user as String;
                txtConversation.text += "[" + hourFormatter.format(hora) + "]" + usuario + " says: " + message + "\n";
            }


            protected function txtChat_enterHandler(event:FlexEvent):void
            {
                var message:AsyncMessage = new AsyncMessage();
                message.headers.user = txtUsername.text;
                message.body = txtChat.text;
                producer.send(message);
                txtChat.text = "";   
            }

            protected function consumer_faultHandler(event:MessageFaultEvent):void
            {
                txtLog.text += event.faultString + "\n";
            }

        ]]>
    </fx:Script>
    <s:states>
        <s:State name="login"/>
        <s:State name="main"/>
    </s:states>
    <fx:Declarations>
        <s:Producer id="producer" destination="chat"/>
        <s:Consumer id="consumer" destination="chat" message="consumer_messageHandler(event)" fault="consumer_faultHandler(event)"/>
        <mx:DateFormatter id="hourFormatter" formatString="KK:NN:SS"/>
    </fx:Declarations>
    <s:Group verticalCenter="0" horizontalCenter="0" includeIn="login">
        <s:Form>
            <s:FormItem label="Username">
                <s:HGroup>
                    <s:TextInput id="txtUsername"/>
                    <s:Button id="btnConnect" label="Login" click="btnConnect_clickHandler(event)"/>
                </s:HGroup>
            </s:FormItem>
        </s:Form>
    </s:Group>
    <s:HGroup width="100%" height="100%" includeIn="main">
        <s:VGroup width="30%" height="100%" paddingBottom="10" paddingLeft="10" paddingRight="10"
                  paddingTop="10">
            <s:Form width="100%">
                <s:FormItem label="Broadcast">
                    <s:HGroup>
                        <s:TextInput id="txtBroadcast"/>
                        <s:Button id="btnBroadcast" label="Broadcast" click="btnBroadcast_clickHandler(event)"/>
                    </s:HGroup>
                </s:FormItem>
            </s:Form>
            <mx:UIComponent id="outVideoWrapper" width="300" height="200"/>
            <s:Form>
                <s:FormItem label="Subscribe">
                    <s:HGroup>
                        <s:TextInput id="txtSubscribe"/>
                        <s:Button id="btnSubscribe" label="Subscribe" click="btnSubscribe_clickHandler(event)"/>
                    </s:HGroup>
                </s:FormItem>
            </s:Form>
            <mx:UIComponent id="inVideoWrapper" width="300" height="200"/>
        </s:VGroup>
        <s:VGroup width="100%" height="80%" paddingBottom="10" paddingLeft="10" paddingTop="10" paddingRight="10">
            <mx:Form width="100%" height="100%">
                <mx:FormItem width="100%" height="100%">
                    <s:TextArea id="txtConversation" width="100%" height="100%" editable="false"/>
                </mx:FormItem>
                <mx:FormItem  width="100%" direction="horizontal">
                    <s:TextInput id="txtChat" width="100%" enter="txtChat_enterHandler(event)" />
                    <s:Button id="btnSend" label="Send" click="btnSend_clickHandler(event)"/>
                </mx:FormItem>
            </mx:Form>
        </s:VGroup>
    </s:HGroup>
    <s:TextArea id="txtLog" width="100%" height="100" color="red" bottom="0"
                editable="false" includeIn="main"/>
</s:Application>


Here is the complete project and the other project that I show in the video demo.

So that’s all, this is my first series and I don’t want to be the last. If you found any error or have any suggestion, please let me know. Thanks

Red5 + BlazeDS = Realtime Video Chat Tutorial Part 3: Red5 video streaming

Hi, today i will show you how to enhance the application that we build in Tutorial part 1 and part 2, let add some video and voice to make things a little bit interesting. This tutorial continues part 1 and part 2 so take this parts first before reading this part.

So let's continue...

First we need to know a little about NetConnection and how to attach video and audio to the RTMP connection that we made in the last part of this tutorial. NetConnection comunicates with the server establishing a full duplex open connection in both the server and the client so it's perfect for use with the RTMP protocol.

Let's make some changes in the UI so we can add our camera, open Red5_BlazeDS_Flex.mxml and change the following (some update are made so we can use Flex 4.5 new components)

Red5_BlazeDS_Flex.mxml

<s:states>
        <s:State name="login"/>
        <s:State name="main"/>
    </s:states>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:Group verticalCenter="0" horizontalCenter="0" includeIn="login">
        <s:Form>
            <s:FormItem label="Username">
                <s:HGroup>
                    <s:TextInput id="txtUsername"/>
                    <s:Button id="btnConnect" label="Login" click="btnConnect_clickHandler(event)"/>
                </s:HGroup>
            </s:FormItem>
        </s:Form>
    </s:Group>
    <s:HGroup width="100%" height="100%" includeIn="main">
        <s:VGroup width="30%" height="100%" paddingBottom="10" paddingLeft="10" paddingRight="10"
                  paddingTop="10">
            <s:Form width="100%">
                <s:FormItem label="Broadcast" >
                    <s:HGroup>
                        <s:TextInput id="txtBroadcast"/>
                        <s:Button id="btnBroadcast" label="Broadcast" click="btnBroadcast_clickHandler(event)"/>
                    </s:HGroup>
                </s:FormItem>
            </s:Form>
            <mx:UIComponent id="outVideoWrapper" width="300" height="200"/>
            <s:Form>
                <s:FormItem label="Subscribe">
                    <s:HGroup>
                        <s:TextInput id="txtSubscribe"/>
                        <s:Button id="btnSubscribe" label="Subscribe"/>
                    </s:HGroup>
                </s:FormItem>
            </s:Form>
            <mx:UIComponent id="inVideoWrapper" width="300" height="200"/>
        </s:VGroup>
    </s:HGroup>
    <s:TextArea id="txtLog" width="100%" height="100" color="red" bottom="0"
                editable="false" includeIn="main"/>

This code add to our user interface 2 UIComponent this components will be the video wrappers in our application.

Now let’s attach the video, for make this possible we must do some steps:
  1. Make the  connection (done in the last step)
  2. Create the streams in/out
  3. Setup  the devices
  4. Create the video.
First we declare the variables:

In our Red5_BlazeDS_Flex.mxml in after the <s:Script>:

            import mx.collections.ArrayCollection;
            import mx.controls.Alert;
            import mx.managers.PopUpManager;
            private var connection:NetConnection;          
            private var userWindow:UsersWindow;
            private var timer:Timer;
           //Streams             
           private var inStream:NetStream;             
           private var outStream:NetStream;             
           //Devices             
           private var camera:Camera;             
           private var microphone:Microphone;             
           //Video             
           private var inVideo:Video;             
           private var outVideo:Video;

Then we need to add click event to our Broadcast button and then write a click handler function has follow:


           protected function btnBroadcast_clickHandler(event:MouseEvent):void {
                if(txtBroadcast.text.length > 3){
                    txtBroadcast.errorString = "";
                    //setup devices
                    camera = Camera.getCamera(); 
                    microphone = Microphone.getMicrophone();
                    //setup the streams
                    outStream = new NetStream(connection);
                    outStream.attachAudio(microphone);
                    outStream.attachCamera(camera);
                    outStream.publish(txtBroadcast.text);
                    //setup out video
                    outVideo = new Video(300,200);
                    outVideo.attachCamera(camera);
                    outVideoWrapper.addChild(outVideo)
                }else{
                    txtBroadcast.errorString = "Put a valid broadcast name";
                }
            }

Now add click event to our Subscribe button and then write a click handler function has follow:

           protected function btnSubscribe_clickHandler(event:MouseEvent):void
            {                 
                    if(txtSubscribe.text.length > 3){
                          inStream = new NetStream(connection);
                          inStream.play(txtSubscribe.text);
                          inVideo = new Video(300,200);
                          inVideo.attachNetStream(inStream);
                          inVideoWrapper.addChild(inVideo);
                }
            }

Now run your application, write a text to Broadcast and then test this in a different browser client open the same application put in the subscribe textinput the name of your broadcast and voila!! you will have the following:


That's all and of course here is the complete code, If you found any error or have any suggestion, please let me know. 

lunes, 8 de noviembre de 2010

Red5 + BlazeDS = Realtime Video Chat Tutorial Part 2: Tracking all the current users.

Ok so here is the second part of the tutorial, I made some changes when I stared to create this tutorial I write this in Ubuntu now because I’m writing my flex thesis and using flash builder burrito I’m using Windows 7 . Ok ok back to the tutorial we are going to keep track of all the current users in our application. This tutorial continues the Tutorial Part 1.

So let’s continue:

First in our java side we need to create a class called ClientManager this class will allow us to use SharedObject and use the actual scope of the application for keep track of the current users. The code is commented to read it carefully to understand what each of this classes are doing.

ClientManager.java

package com.jdesconectado;
import org.red5.server.api.IScope;
import org.red5.server.api.ScopeUtils;
import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectService;
public class ClientManager {
    /** Stores the name of the SharedObject to use. */
    private String name;
    /** Should the SharedObject be persistent? */
    private boolean persistent;
    /**
     * Create a new instance of the client manager.
     *
     * @param name
     *             name of the shared object to use
     * @param persistent
     *             should the shared object be persistent
     */
    public ClientManager(String name, boolean persistent) {
        this.name = name;
        this.persistent = persistent;
    }
    /**
     * Return the shared object to use for the given scope.
     *
     * @param scope
     *             the scope to return the shared object for
     * @return the shared object to use
     */
    private ISharedObject getSharedObject(IScope scope) {
        ISharedObjectService service = (ISharedObjectService) ScopeUtils
                .getScopeService(scope,
                        ISharedObjectService.class,
                        false);
        return service.getSharedObject(scope, name, persistent);
    }
    /**
     * A new client connected. This adds the username to
     * the shared object of the passed scope.
     *
     * @param scope
     *             scope the client connected to
     * @param username
     *             name of the user that connected
     * @param uid
     *             the unique id of the user that connected
     */
    public void addClient(IScope scope, String username, String uid) {
        ISharedObject so = getSharedObject(scope);
        so.setAttribute(uid, username);
    }
    /**
     * A client disconnected. This removes the username from
     * the shared object of the passed scope.
     *
     * @param scope
     *             scope the client disconnected from
     * @param uid
     *             unique id of the user that disconnected
     * @return the username of the disconnected user
     */
    public String removeClient(IScope scope, String uid) {
        ISharedObject so = getSharedObject(scope);
        if (!so.hasAttribute(uid)) {
            // SharedObject is empty. This happes when the last client
            // disconnects.
            return null;
        }
        String username = so.getStringAttribute(uid);
        so.removeAttribute(uid);
        return username;
    }
}


And in our Application.java class we made some changes. Basically we need a collection to keep the users and instantiate the ClientManager.

Application.java:

package com.jdesconectado;
import java.util.ArrayList;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
public class Application extends ApplicationAdapter{
    private ClientManager clientManager = new ClientManager("userList", false);
    private ArrayList<String> connectedClients = new ArrayList<String>();
    @Override
    public synchronized boolean connect(IConnection conn, IScope scope,
            Object[] params) {
        //Verify is username is in params
        if(params == null || params.length == 0){
            rejectClient("No username was provided");
        }
        if(!super.connect(conn, scope, params)){
            return false;
        }
        String username = params[0].toString();
        String uid = conn.getClient().getId();
        //add the usename to the collection
        connectedClients.add(username);
        clientManager.addClient(scope, username, uid);
        return true;
    }
   
    @Override
    public synchronized void disconnect(IConnection conn, IScope scope) {
        String uid = conn.getClient().getId();
        String username = clientManager.removeClient(scope, uid);
        connectedClients.remove(username);
        super.disconnect(conn, scope);
    }
    //Get the current connected clients   
    public ArrayList<String> getConnectedClients(){
        return connectedClients;
    }
}
This is all we need in our server side. The server side should look like this:



In the client side we need to made some changes to the UI so we can made use of our recently changed server side. First we need a custom component to show the current connected users, we call it UsersWindow.mxml and it will be based on a TitleWindow.



In our custom component UsersWindow we write this code, only a Label and List is all we need.

UsersWindow.mxml

<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Script>
        <![CDATA[
            public function setUsers(users:ArrayCollection):void {
                this.users = users;
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:ArrayCollection id="users"/>
    </fx:Declarations>
    <s:Label text="There is {users.length} users connected"/>
    <s:List id="lstUsers" dataProvider="{users}"/>
</s:TitleWindow>

Now in our file Red5_BlazeDS_Flex.mxml we change some parts:

Red5_BlazeDS_Flex.mxml
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               minWidth="955" minHeight="600">
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import mx.controls.Alert;
            import mx.managers.PopUpManager;
           
            private var connection:NetConnection;
            private var userWindow:UsersWindow;
           
            protected function btnConnect_clickHandler(event:MouseEvent):void
            {
                if(txtUsername.text.length >= 3){
                    userWindow = new UsersWindow();
                    currentState = "main";
                    connection = new NetConnection();
                    connection.connect("rtmp://localhost/Red5_BlazeDS_Java", txtUsername.text);
                    connection.addEventListener(NetStatusEvent.NET_STATUS, onConnectionStatus);
                    connection.client = this;
                }else{
                    txtUsername.errorString = "Enter a valid name";
                }
            }
            protected function onResult(obj:Object):void {
                userWindow.setUsers(new ArrayCollection(obj as Array));
            }
            protected function onFault(obj:Object):void {
                txtLog.text += "Error " + obj.fault.message + "\n";
             }
            protected function onConnectionStatus(event:NetStatusEvent):void {
                if(event.info.code == "NetConnection.Connect.Success"){
                    txtLog.text += "Connection to RTMP successfully established\n";
                    connection.call("getConnectedClients", new Responder(onResult, onFault));
                    userWindow = UsersWindow(PopUpManager.createPopUp(this, UsersWindow, false));
                    userWindow.usuarioActual = txtUsername.text;
                }else{
                    txtLog.text += "Connection to RTMP fail\n";
                }
            }
        ]]>
    </fx:Script>
    <s:states>
        <s:State name="login"/>
        <s:State name="main"/>
    </s:states>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:Group verticalCenter="0" horizontalCenter="0" includeIn="login">
        <s:Form>
            <s:FormItem name="Username" direction="ltr">
                <s:TextInput id="txtUsername"/>
                <s:Button id="btnConnect" label="Login" click="btnConnect_clickHandler(event)"/>
            </s:FormItem>
        </s:Form>
    </s:Group>
    <s:TextArea id="txtLog" width="100%" height="100" color="red" bottom="0"
                editable="false" includeIn="main"/>
</s:Application>
We create two states one login this will handle the username and in the line:
connection.connect("rtmp://localhost/Red5_BlazeDS_Java", txtUsername.text);
we are passing the user name as a param for our server. Then we simply create a instance of out UserWindow class so we can see all the users available in the chat. There is only one thing to add to our program we need a timer. This timer will allow us to update the connected users in all the clients.

Red5_BlazeDS_Flex.mxml

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               minWidth="955" minHeight="600">
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import mx.controls.Alert;
            import mx.managers.PopUpManager;
           
            private var connection:NetConnection;
            private var userWindow:UsersWindow;
            private var timer:Timer;
           
            protected function btnConnect_clickHandler(event:MouseEvent):void
            {
                if(txtUsername.text.length >= 3){
                    userWindow = new UsersWindow();
                    timer = new Timer(2000);
                    timer.start();
                    timer.addEventListener(TimerEvent.TIMER, onTimerEvent);
                    currentState = "main";
                    connection = new NetConnection();
                    connection.connect("rtmp://localhost/Red5_BlazeDS_Java", txtUsername.text);
                    connection.addEventListener(NetStatusEvent.NET_STATUS, onConnectionStatus);
                    connection.client = this;
                }else{
                    txtUsername.errorString = "Enter a valid name";
                }
            }
            protected function onTimerEvent(event:TimerEvent):void {
                connection.call("getConnectedClients", new Responder(onResult, onFault));
            }
            protected function onResult(obj:Object):void {
                userWindow.setUsers(new ArrayCollection(obj as Array));
            }
            protected function onFault(obj:Object):void {
                txtLog.text += "Error " + obj.fault.message + "\n";
             }
            protected function onConnectionStatus(event:NetStatusEvent):void {
                if(event.info.code == "NetConnection.Connect.Success"){
                    txtLog.text += "Connection to RTMP successfully established\n";
                    connection.call("getConnectedClients", new Responder(onResult, onFault));
                    userWindow = UsersWindow(PopUpManager.createPopUp(this, UsersWindow, false));
                    userWindow.usuarioActual = txtUsername.text;
                }else{
                    txtLog.text += "Connection to RTMP fail\n";
                }
            }
        ]]>
    </fx:Script>
    <s:states>
        <s:State name="login"/>
        <s:State name="main"/>
    </s:states>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:Group verticalCenter="0" horizontalCenter="0" includeIn="login">
        <s:Form>
            <s:FormItem name="Username" direction="ltr">
                <s:TextInput id="txtUsername"/>
                <s:Button id="btnConnect" label="Login" click="btnConnect_clickHandler(event)"/>
            </s:FormItem>
        </s:Form>
    </s:Group>
    <s:TextArea id="txtLog" width="100%" height="100" color="red" bottom="0"
                editable="false" includeIn="main"/>
</s:Application>

The timer is set to 2 seconds, every 2 seconds the application will ask the server who is connected and get it.

Finally run the application in two different browsers and the result must match this:


That’s all and of course here is the complete code, If you found any error or have any suggestion, please let me know.