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.

No hay comentarios:

Publicar un comentario