Compartilhando Câmera e Chat com o Red5

Este exemplo pode usar o serviço fitcDemo do Red5, mais preferi criar o meu próprio por necessitar de muitas alterações e novas implementações.

Basicamente é o serviço fitcDemo que vou adicionar algumas funcionalidades novas ao serviço e para isso você precisa saber um pouco de Java ou pelo manos a não básica.

/**
 * Se você não sabe nada de Java baixe o Zip que conterá toda a biblioteca compilada.
 */

Vamos começar criando um projeto no Eclipse For Java com o nome de confDemo. conforme imagens abaixo:


Imagem 01:Novo projeto


Imagem 02: Criando o projeto no Java

Agora antes de prosseguir temos que importar a biblioteca do Red5 no projeto. Para isso clique com o botão direito sobre o projeto e vá em Proprietes. Em Java Build Path >> Libraries clique em Add External JARs e selecione Red5.jar que se encontra na raiz da instalação do Red5 e clique em OK.


Figura 03: Importando o JAR

Agora que o projeto esta criado configurado, vamos criar um novo arquivo java e nomeá-lo de Application.java no Package org.red5.confDemo que estenderá org.red5.server.adapter.ApplicationAdapter e implementará org.red5.server.api.service.IPendingServiceCallback.


Figura 04: Nova Classe

O arquivo gerado esta abaixo e

package org.red5.confDemo;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;

public class Application extends ApplicationAdapter implements
    IPendingServiceCallback {

    @Override
    public void resultReceived(IPendingServiceCall call) {
        // TODO Auto-generated method stub
    }
}

O método resultReceived implementado server para tratar o retorno do Flex em métodos invocados no Java. Criar também uma classe User para definir os dados dos usuários conectados. Esta classe terá apenas campo USER_ID e USER_NOME.

package org.red5.confDemo;

public class User {

    public String USER_ID = null;
    public String USER_NOME = null;

    public User(String USER_ID, String USER_NOME) {
        this.USER_ID = USER_ID;
        this.USER_NOME = USER_NOME;
    }
}

Bom, agora vamos rescrever os seguintes métodos:

  • appStart
    É invocado quando o primeiro usuário se conectar na aplicação. Ou seja, quando a aplicação for iniciada;
  • appConnect
    Invocado quando um novo usuário se conectar;
  • disconnect
    Invocado quando o usuário sair da aplicação. Neste processo deve se excluir o usuário da lista de conectados;
  • appJoin
    Invocado quando após o usuario ser aceito na conexão.

E criar os seguintes métodos:

  • enviarNovosUsuarios
    Envia a lista de usuários conectados a todos os usuários conectados;
  • listaUsuarios
    Retorna a lista de usuários que estão conectados. O problema é que na variável users os desconectados ficam como null, então

Abaixo esta toda a classe montada:

package org.red5.confDemo;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import org.red5.server.adapter.*;
import org.red5.server.api.*;
import org.red5.server.api.service.*;

public class Application extends ApplicationAdapter implements
        IPendingServiceCallback {

    protected HashMap users = new HashMap();

    @Override
    public boolean appStart(IScope scope) {
        return true;
    }

    @Override
    public boolean appConnect(IConnection conn, Object[] params) {
        // quem não envia nome, não ganha conexão
        if (params.length != 1) {
            rejectClient("Sem permissão de acesso ao sistema!");
            return false;
        }
        String username = params[0].toString();
        String uid = conn.getClient().getId();
        IServiceCapableConnection service = (IServiceCapableConnection) conn;

        User user = new User(uid, username);
        users.put(uid, user);

        service.invoke("listaUsuarios", new Object[] { listaUsuarios() }, this);
        enviarNovosUsuarios();

        System.out.println("Novo usuario: " + uid);
        return true;
    }

    @Override
    public boolean appJoin(IClient client, IScope scope) {

        super.appJoin(client, scope);

        // envia a lista de usuários conectados a todos os conectados
        enviarNovosUsuarios();

        return true;
    }

    @Override
    public synchronized void disconnect(IConnection conn, IScope scope) {
        // ID da conexão do usuário
        String uid = conn.getClient().getId();
        // O usuário que desconectou
        users.remove(uid);
        enviarNovosUsuarios();

        System.out.println("Desconectado: " + uid);
        super.disconnect(conn, scope);
    }

    @Override
    public void resultReceived(IPendingServiceCall call) {
        System.out.println(call);
    }

    /**
     * Envia a lista de usuários conectados a todos os usuarios
     */
    protected void enviarNovosUsuarios() {
        Object[] params = new Object[] { listaUsuarios() };
        ServiceUtils.invokeOnAllConnections(scope, "listaUsuarios", params);
    }

    /**
     * Retorna a lista de usuários sem os campos nulos
     */
    protected HashMap listaUsuarios() {
        HashMap params = new HashMap();
        Integer key = 0;
        Set s = users.keySet();
        for (Iterator it = s.iterator(); it.hasNext();) {
            User value = users.get(it.next());
            if (value != null) {
                params.put(key, value);
                key++;
            }
        }
        return params;
    }
}

Baixe aqui os arquivos java:

Agora dentro da pasta Red5/webapps vamos criar uma nova pasta chamada confDemo. Dentro desta pasta criar uma pastas chamada WEB-INF que receberá as bibliotecas e configurações deste serviço. Dentro desta pasta você criará três arquivos:

Agora exporte o seu aplicativo java e deposite dentro da pasta Red5/webapps/confDemo/WEB-INF/lib, reinicia o serviço do Red5. Para isso vá em File >> Export e abrirá a tela abaixo:


Imagem 05: exportando o projeto java

Agora marque os arquivos que devem ser exportados e selecione a pasta a salvar o Jar.


Figura 6: Finalizando a exportação

Clique em Finish e reinicie o serviço do Red5.

/**
 * Sempre que alterar o jar na pasta lib terás que reiniciar o o serviço do Red5.
 */

Agora que o serviço esta criado, vamos criar o aplicativo Flex que trabalhará com este serviço. Esta parte já deve ser mais fácil para a maioria dos leitores.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
        layout="vertical"
        horizontalAlign="left"
        backgroundColor="#f6f6f6"
        backgroundGradientColors="[#f6f6f6, #bbbbbb]"
        creationComplete="init()"
        viewSourceURL="srcview/index.html">

  <mx:Script>
    <![CDATA[
      import mx.controls.Alert;

      // Conexão com o Red5
      private var netConnection:NetConnection;

      // NetStream responsável por apresentar o vídeo
      private var netStream_play:NetStream;

      // NetStream responsável por publicar o vídeo
      private var netStream_publish:NetStream;

      // Canal para o Chat
      private var sharedObject:SharedObject;

      // Componente para apresnetar o vídeo
      private var video:Video

      // Nome da publicação do vídeo
      private var streamingNome:String="publishVideo"

      /**
       * executa após carregar o aplicativo
       */
      private function init():void
      {
        video=new Video();

        video.height=uiCaixaDoVideo.height;
        video.width=uiCaixaDoVideo.width;
      }

      /**
       * Inicia a aplicação e faz a conexão
       */
      private function conectar():void
      {
        if ( hostRTMP.text.length < 15 )
        {
          Alert.show( "URL muito pequena!" )
          return;
        }
        if ( nome.text.length < 3 )
        {
          Alert.show( "Nome tem que ter mais de 3 caracteres!" )
          return;
        }
        if ( netConnection != null )
          netConnection=null;
        netConnection=new NetConnection();
        netConnection.addEventListener( NetStatusEvent.NET_STATUS, netStatus );
        netConnection.addEventListener( AsyncErrorEvent.ASYNC_ERROR, asyncError )
        netConnection.connect( hostRTMP.text, nome.text );
        netConnection.client=this;
      }

      /**
       * Método que recebe a lista de usuários enviado pleo Red5
       */
      public function listaUsuarios( lista:Array ):void
      {
        datagridUsuarios.dataProvider=lista;
      }

      /**
       * Método que recebe o Status da conexão
       */
      private function netStatus( e:NetStatusEvent ):void
      {
        switch ( e.info.code )
        {
          case "NetConnection.Connect.Success":
            connectado()
            viewstack.selectedIndex=1;
            break;
          case "NetConnection.Connect.Closed":
            viewstack.selectedIndex=0;
            break;
          case "NetConnection.Connect.Rejected":
            viewstack.selectedIndex=0;
            break;
          case "NetConnection.Connect.Failed":
            viewstack.selectedIndex=0;
            break;
          default:
            viewstack.selectedIndex=0;
        }
      }

      /**
       * Chamado quando deu sucesso na conexão com o Red5
       */
      private function connectado():void
      {
        sharedObject=SharedObject.getRemote( "chat", netConnection.uri, true )
        sharedObject.addEventListener( SyncEvent.SYNC, OnSync );
        sharedObject.addEventListener( AsyncErrorEvent.ASYNC_ERROR, asyncError );
        sharedObject.connect( netConnection );
        sharedObject.client=this;

        netStream_play=new NetStream( netConnection )
        video.attachNetStream( netStream_play );
        uiCaixaDoVideo.addChild( video );
        netStream_play.play( streamingNome );
      }

      /**
       * Método responsável por tratar os AsyncErrorEvent
       */
      private function asyncError( e:AsyncErrorEvent ):void
      {
        trace( e )
      }

      /**
       * Método responsável por tratar os SyncEvent
       */
      private function OnSync( e:SyncEvent ):void
      {
        trace( e )
      }

      /**
       * Trata o click do Botão Publicar WebCam
       *
       * Se marcado publica, ao desmarcar para a publicação.
       */
      private function publicar():void
      {
        if ( btPublicar.selected )
        {
          btPublicar.label="Parar publicação!"

          netStream_publish=new NetStream( netConnection );
          netStream_publish.attachCamera( Camera.getCamera());
          netStream_publish.attachAudio( Microphone.getMicrophone());

          // nome que será publicado
          netStream_publish.publish( streamingNome );
        }
        else
        {
          btPublicar.label="Publicar no servidor RTMP!"

          netStream_publish.close()
          netStream_publish=null
        }
      }

      /**
       * Envia o testo por Chat e limpa o campo.
       */
      private function enviarMensagem():void
      {
        var msg:String="<p><b>" + nome.text + " diz:</b> " + mensagem.text + "</p>";
        sharedObject.send( "recebemsg", msg );

        mensagem.text='';
      }

      /**
       * Recebe as mensagens postada no Chat
       */
      public function recebemsg( msg:String ):void
      {
        areaMensagem.htmlText+=msg;
        areaMensagem.validateNow();
        areaMensagem.verticalScrollPosition=areaMensagem.maxVerticalScrollPosition;
      }
    ]]>
  </mx:Script>
  <mx:ViewStack id="viewstack"
          width="100%"
          height="100%"
          creationPolicy="all">
    <mx:VBox width="100%"
         height="100%"
         verticalAlign="middle"
         horizontalAlign="center">
      <mx:Form>
        <mx:FormItem label="URL do servidor:" fontWeight="bold">
          <mx:TextInput id="hostRTMP"
                  text="rtmp://localhost/confDemo"
                  width="200"
                  fontWeight="normal"/>
        </mx:FormItem>
        <mx:FormItem label="Nome:" fontWeight="bold">
          <mx:TextInput id="nome" width="200" fontWeight="normal"/>
        </mx:FormItem>
        <mx:FormItem>
          <mx:Button label="Conectar no Red5" click="conectar()" fontWeight="normal"/>
        </mx:FormItem>
      </mx:Form>
    </mx:VBox>
    <mx:VBox width="100%" height="100%">
      <mx:HBox width="100%" height="100%">
        <mx:VBox height="100%">
          <mx:Canvas left="10"
                 top="85"
                 width="160"
                 height="120"
                 backgroundColor="#000000">
            <mx:UIComponent width="100%" height="100%" id="uiCaixaDoVideo"/>
          </mx:Canvas>
          <mx:Button id="btPublicar"
                 label="Publicar WebCam"
                 click="publicar()"
                 toggle="true"/>
          <mx:DataGrid id="datagridUsuarios" width="100%" height="100%">
            <mx:columns>
              <mx:DataGridColumn headerText="Usuários" dataField="USER_NOME"/>
            </mx:columns>
          </mx:DataGrid>
        </mx:VBox>
        <mx:VBox height="100%" width="100%">
          <mx:TextArea id="areaMensagem"
                 width="100%"
                 height="100%"
                 borderStyle="none"
                 alpha="0.63"
                 editable="false"/>
          <mx:HBox width="100%">
            <mx:TextInput id="mensagem" width="100%" enter="enviarMensagem()"/>
            <mx:Button label="Enviar" click="enviarMensagem()"/>
          </mx:HBox>
        </mx:VBox>
      </mx:HBox>
    </mx:VBox>
  </mx:ViewStack>
</mx:Application>

Basicamente o funcionamento é o seguinte: Quando clicar o em conectar, é criado uma instancia do NetConnection e enviado junto o nome da pessoa que esta conectando. Assim que a classe que criamos receber esta conexão, ela replica a todas as todas as conexões enviando a lista de usuários conectados. Estalista é recebida pelo método listaUsuarios do Flex.

No Red5 é verificado se foi enviado o nome e e aceita a conexão. Assim que a conexão for aceita, o Flex cria uma instancia do SharedObject e da play no canal publishVideo para quando alguém publicar um vídeo já estar instanciado.

A instancia do SharedObject é responsável pelo chat. para enviar uma mensagem, utiliza-se o método send, passando como parametro a o método que irá receber a resposta e a mensagem.

Quando clicamos em “publicar vídeo!”, uma instancia do NetStream é criada e publicamos no mesmo canal que foi dados play na lista acima.

Abaixo segue o exemplo finalizado, mais para testar terás que possuir um servidor de Red5.

Fonte do aplicativo Flex esta aqui.
O Aplicativo confDemo compilado para ser depositado no Red5 esta aqui.

Fique por dentro de nossas novidades, ideias e atualizações