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

5 comentarios:

  1. Dear Victor,

    When I am running the demo code. I get the error


    [MessagingError message='Destination 'chat' either does not exist or the destination has no channels defined (and the application does not define any default channels.)']
    at mx.messaging.config::ServerConfig$/internalGetChannelSet()[E:\dev\4.x\frameworks\projects\rpc\src\mx\messaging\config\ServerConfig.as:727]
    at mx.messaging.config::ServerConfig$/getChannelSet()[E:\dev\4.x\frameworks\projects\rpc\src\mx\messaging\config\ServerConfig.as:295]
    at mx.messaging::MessageAgent/initChannelSet()[E:\dev\4.x\frameworks\projects\rpc\src\mx\messaging\MessageAgent.as:1333]
    at mx.messaging::MessageAgent/internalSend()[E:\dev\4.x\frameworks\projects\rpc\src\mx\messaging\MessageAgent.as:1268]
    at mx.messaging::Producer/internalSend()[E:\dev\4.x\frameworks\projects\rpc\src\mx\messaging\Producer.as:171]
    at mx.messaging::AbstractProducer/connect()[E:\dev\4.x\frameworks\projects\rpc\src\mx\messaging\AbstractProducer.as:494]
    at Red5_BlazeDS_Flex/btnConnect_clickHandler()[C:\Documents and Settings\Administrator\Adobe Flash Builder 4 Plug-in\Red5_BlazeDS_Flex\flex_src\Red5_BlazeDS_Flex.mxml:37]
    at Red5_BlazeDS_Flex/__btnConnect_click()[C:\Documents and Settings\Administrator\Adobe Flash Builder 4 Plug-in\Red5_BlazeDS_Flex\flex_src\Red5_BlazeDS_Flex.mxml:156]

    ResponderEliminar
  2. hi,please check the messaging-config.xml under the java project and make sure that the "chat" destination is in there, another thing check if you setup the streaming channel in your services-config.xml. Hope this solve your problem

    ResponderEliminar
  3. red5 server which version you are using?

    Thank you
    Ronald

    ResponderEliminar
  4. I think your example very well.
    Unfortunately, I do not have enough knowledge.

    How can I add a MySQL database with user data?

    I would be delighted if you could help me.
    Thank you
    Ronald

    ResponderEliminar