UCC API – Unified Communications Client API – Parte 6 – Conferências

Overview

No post anterior, vimos como criar uma sessão do tipo instant messaging. As sessões do tipo conferência usam praticamente a mesma mecânica.

A diferença é que a conference session é o lugar onde será criada uma conferência no OCS server.

Cada participante deve se adicionar na sessão.

Tudo parece muito fácil na teoria, mas alguams particuularidades na API tornam o trabalho um pouco complicado.

Assim como nas sessões de instant messaging, precisamos tratar os dois caminhos: outgoing session e incoming session.

Fluxo de criação da conferência

Este desenho mostra a idéia geral de como é o processo de criação de conferência na UCC API:

  • O criador da conferência cria um “lugar” no servidor onde a conferência vai acontecer.
  • O criador da conferência manda o endereço deste lugar para os usuários convidados.
  • Cada usuário convidado cria uma sessão de comunicação entre ele e o lugar onde a conferência acontece.

Obtendo a Focus Uri

O primeiro desafio é obter o focus Uri. O Focus URI é um “lugar” no servidor onde os usuários criam as sessões de conferência. Nós precisamos obter a Focus Uri para poder obter uma Conference Uri posteriormente.

Para obter a Focus Uri, precisamos fazer uma “query” na categoria “Server Configuration”.

    public void SubscribeServerConfiguration(IUccEndpoint endpoint)
    {
      IUccSubscriptionManager subscriptionManager = (IUccSubscriptionManager)endpoint;

      IUccSubscription serverConfigurationSubscription = subscriptionManager.CreateSubscription(null);

      UccPresentity presentity = serverConfigurationSubscription.CreatePresentity(myEndpoint.uccEndpoint.Uri, null);
      ComHelper.Advise<_IUccPresentityEvents>(presentity, this);
      serverConfigurationSubscription.AddPresentity(presentity);
      serverConfigurationSubscription.AddCategoryName("ServerConfiguration");
      serverConfigurationSubscription.Query(null);
    }

A idéia por trás deste código está descrita em: Category e Category Instance.

As informações são devolvidasp elo servidor no método _IUccCategoryContextEvents.OnCategoryInstanceAdded:

    public void OnCategoryInstanceAdded(IUccCategoryContext pCategoryCtxt, UccCategoryInstanceEvent pCategoryInstanceEvent)
    {
      if (pCategoryCtxt.Name == "ServerConfiguration")
      {
        IUccServerConfigurationCategory category = (IUccServerConfigurationCategory)pCategoryInstanceEvent.CategoryInstance;
        this.FocusUri = category.FocusFactory;
        MessageBox.Show(FocusUri.Value);
      }
    }

O valor que precisamos é devolvido em FocusUri.Value.

Criando uma conferência (ou Scheduling Conference)

O processo de criação de uma conferência no servidor é chamado de “Schedule”. As vezes nos confunde porque pensamos em criar uma conferência no futuro, mas a idéia aqui é criar um lugar no servidor onde as pessoas vão compartilhar a conversação (idéia similar a salas de chat).

Para fazer o schedule da conferência:


    public void ScheduleConference(IUccEndpoint endpoint, string strFocusUri)
    {
      UccUriManager uriManager = new UccUriManager();
      UccUri focusUri = uriManager.ParseUri(strFocusUri);

      IUccConferenceManager confManager = (IUccConferenceManager)endpoint;
      IUccConferenceManagerSession confManagerSession = confManager.CreateConferenceManagerSession(focusUri);

      ComHelper.Advise<_IUccConferenceManagerSessionEvents>(confManagerSession, this);

      UccConferenceInformation ci = confManagerSession.CreateConferenceInformation();
      ci.Properties.AddProperty((int)UCC_CONFERENCE_INFORMATION_PROPERTY.UCCCIP_ADMISSION_TYPE,   UCC_CONFERENCE_ADMISSION_TYPE.UCCCAT_OPEN_AUTHENTICATED);

      UccPropertyCollection mcuAttributeCollection = new UccPropertyCollection();
      mcuAttributeCollection.AddProperty((int)UCC_CONFERENCE_ENTITY_SETTING_PROPERTY.UCCCESP_TYPE, UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING);

      UccPropertyCollection mcuPropertyCollection = new UccPropertyCollection();
      mcuPropertyCollection.AddProperty((int)UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING, mcuAttributeCollection);

      ci.Properties.AddProperty((int)UCC_CONFERENCE_INFORMATION_PROPERTY.UCCCIP_MCUS, mcuPropertyCollection);

      UccOperationContext operationContext = new UccOperationContext();
      UccContext mcuContext = new UccContext();
      mcuContext.AddNamedProperty("ScheduleConference", true);
      operationContext.Initialize(1, mcuContext);

      confManagerSession.ScheduleConference(ci, operationContext);
    }

Este código tem muita coisa específica da UCC API, mas vou tentar explicar a idéia geral. Primeiro fazemos um cast do endpoint para IUccConferenceManager.

Então precisamos criar uma sessão do conference manager. Precisamos assinar sua interface dispatch para ouvir o evento OnScheduleConference. O Conference Uri é obtido através deste evento.

Após este passo, criamos um objeto Conference Information. Aqui apenas colcoamos algumas informações de quem pode participar dessa conferência, que tipos de mídia são suportados.

Então criamos a propriedade ScheduleConference (acho que isso é fixo no servidor OCS) e chamamos o método ScheduleConference.

A resposta é obtida no método OnScheduleConference:


    public void OnScheduleConference(UccConferenceManagerSession pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Error scheduling conference");

      UccProperty confUriProperty = pEventData.Properties.get_Property((int)UCC_CONFERENCE_MANAGER_OPERATION_COMPLETED_EVENT_PROPERTY.UCCCMOCEP_CONFERENCE_URI);
      conferenceUri = confUriProperty.StringValue;
    }

Aqui, pegamos a Conference Uri como uma propriedade.

Se algo der errado, recebemos StatusCode diferente de 0.

Precisamos guardar esse conference uri, pois precisaremos dele no próximo passo.

Criando uma Conference Session

Uma conference session é a idéia de um usuário interagindo com uma “scheduled conference” (aquilo que fizemos no passo anterior). Não é só o criador da conferência quem cria conference sessions. Veremos adiante que quando convidamos um usuário para uma conferência (uma incoming session), vamos usar o mesmo processo para interagir com a conferência.

Para criar a conference session, precisamos da conference Uri.

O processo segue:


    public void CreateConferenceSession(IUccEndpoint endpoint, string conferenceUri)
    {
      this.endpoint = endpoint;
      this.conferenceUri = conferenceUri;

      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;

      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_CONFERENCE, null);
      confSession = (IUccConferenceSession)session;

      ComHelper.Advise<_IUccConferenceSessionEvents>(confSession, this);

      confSession.Enter(UccHelper.GetUri(conferenceUri), null);

    }

Esse código não tem segredo. Logo depois que o método “Enter” é chamado, o evento _IUccConferenceSessionEvents.OnEnter é chamado assincronamente.

    public void OnEnter(UccConferenceSession pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Nope");
            

      IUccConferenceSessionParticipant localParticipant = (IUccConferenceSessionParticipant)((IUccSession)pEventSource).LocalParticipant;
      UccConferenceSessionParticipantEndpoint partEndpoint = localParticipant.CreateParticipantEndpoint(this.endpoint.uccEndpoint.Uri, this.endpoint.uccEndpoint.Id, UCC_CONFERENCE_ENTITY_TYPE.UCCCET_INSTANT_MESSAGING, null);
      ComHelper.Advise<_IUccConferenceSessionParticipantEvents>(localParticipant, this);

      IUccMediaChannel channel = partEndpoint.CreateChannel(UCC_MEDIA_TYPES.UCCMT_MESSAGE, null);
      ((IUccMediaChannel)channel).PreferredMedia = (int)(UCC_MEDIA_DIRECTIONS.UCCMD_RECEIVE | UCC_MEDIA_DIRECTIONS.UCCMD_SEND);
      partEndpoint.AddChannel(channel);

      ((IUccSession)pEventSource).LocalParticipant.AddParticipantEndpoint((IUccSessionParticipantEndpoint)partEndpoint, null);
    }

No método OnEnter, checamos por erros e na sequencia criamos o endpoint local do participante. No endpoint é onde especificamos o tipo de mídia que irá interagir com a conferência. No nosso exemplo, usaremos instant message.

Vamos fazer um advise nos eventos de _IUccConferenceSessionParticipantEvents. Após a chamada de OnEnter, _IUccConferenceSessionParticipantEvents.OnParticipantAdded será chamado assincronamente.

    public void OnAddEndpoint(UccConferenceSessionParticipant pEventSource, IUccOperationProgressEvent pEventData)
    {
      if (pEventData.StatusCode != 0)
        throw new Exception("Nope");

      IUccSessionManager sessionManager = (IUccSessionManager)this.endpoint.uccEndpoint;
      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_INSTANT_MESSAGING, null);

      string imMCUUri = confSession.Properties.get_Property((int)UCC_CONFERENCE_SESSION_PROPERTY.UCCCSPID_CONFERENCE_INSTANT_MESSAGING_URI).StringValue;

      IUccInstantMessagingSessionParticipant imParticipant = (IUccInstantMessagingSessionParticipant)session.CreateParticipant(
        UccHelper.GetUri(imMCUUri), null);

      ComHelper.Advise<_IUccInstantMessagingSessionParticipantEvents>(imParticipant, this);

      session.AddParticipant((IUccSessionParticipant)imParticipant);

      this.imSession = (IUccInstantMessagingSession)session;

      ComHelper.Advise<_IUccSessionEvents>(session, this);
    }

No método OnAddEndponit, vamos checar por erros e então criaremos uma sessão de instant messaging, da mesma forma que criamos na parte 5. A diferença é que o participante é a conference Uri.

Os mesmos métodos são usados para mandar e receber mensagens na conferência.

Outros métodos interessantes na interface _IUccInstantMessagingSessionParticipantEvents são: OnRemoveParticipant, OnAddParticipant e OnTerminate. Esses eventos são usados para controlar quando os usuários estão entrando ou saindo da conferência.

Convidando usuários para uma conferência

Os passos anteriores nos levaram a um estado muito interessante. Temos uma conferência criada e uma sessão de instant messaging apontando pra ela, mas como convidamos usuários para a conferência que acabamos de criar?

Vamos entrar nesse ambiente nebuloso.

Para permitir que um usuário entre na conferência, devemos enviar o conference Uri para ele. O único mecanismo disponível para fazer isso na UCC API é a “application session”. Application session é um tipo de sessão que permite que qualquer tipo de informação seja transferida entre os usuários. O Microsoft Office Communicator usa o mesmo mecanismo descrito aqui.

Primeiro vamos explicar como enviar o convite. Posteriormente veremos como receber o convite e tratar a application session.

Enviando o convite:


    public void InviteUser(IUccEndpoint endpoint, string userUri, string conferenceUri)
    {
      this.conferenceUri = conferenceUri;

      UccContext inviteContext = new UccContext();
      inviteContext.AddNamedProperty("ConfInvitee", userUri);

      IUccSessionManager sessionManager = (IUccSessionManager)endpoint;
      IUccSession session = sessionManager.CreateSession(UCC_SESSION_TYPE.UCCST_APPLICATION, inviteContext);

      UccUriManager uriManager = new UccUriManager();
      UccUri userUriUri = uriManager.ParseUri(userUri);

      IUccSessionParticipant participant = session.CreateParticipant(userUriUri, inviteContext);
      ComHelper.Advise<_IUccApplicationSessionParticipantEvents>(participant, this);
      session.AddParticipant(participant, null);
    }

Precisamos salvar o conferenceUri pois será usado posteriormente numa chamada assíncrona.

A idéia aqui é muito simples. Criamos uma sessão do tipo application (uma sessão para mandar informações arbitrárias), então criamos o participante com a Uri do usuário remoto. Então fazemos um advise na interface _IUccApplicationSessionParticipantEvents e adicionamos o participante na sessão.

O método _IUccApplicationSessionParticipantEvents.OnOutgoingInvitation será chamado:


    public void OnOutgoingInvitation(UccApplicationSessionParticipant pEventSource, UccOutgoingInvitationEvent pEventData)
    {
      pEventData.ContentType = "application/ms-conf-invite+xml";
      pEventData.SessionDescription =
        "<Conferencing version=\"2.0\"><focus-uri>" + this.conferenceUri + "</focus-uri><subject>AE Chat</subject><im available=\"true\"><first-im></first-im></im></Conferencing>";

      pEventData.Send();
    }

Por que todo esse XML inventado? Resp: Porque é o formato usado pelo Microsoft Office Communicator. POdemos mudar esse formato para qualquer outro, desde que seja uma convenção entre a aplicação cliente. O OCS Server e a UCC API somente propagam o que você preencher aqui.

Posteriormente, o método OnInvitationAccepted será chamado quando o usuário aceitar o convite.

Esse fluxo é olhando pela perspectiva do usuário que cria a conferência e convida os outros usuários. Quando o usuário entra na conferência, o método OnParticipantAdded será chamado.

Recebendo convites

Agora veremos a mesma lógica sob a ótica de quem está sendo convidado para a conferência.

O convite é enviado através de uma application session. A primeira pergunta é: Como a UCC API sabe que uma sessão que está sendo recebida (incoming session) é uma application session?

A resposta é: Ela não sabe. Você precisa dizer pra ela.

A mágica é:


    public void EnableEndpoint(IUccPlatform uccPlatform, string UserUri, string UserName, string UserPassword, string UserDomain, string ServerName, UCC_TRANSPORT_MODE transportMode)
    {
      UccUri uri = UccHelper.GetUri(UserUri);
      uccEndpoint = uccPlatform.CreateEndpoint(UCC_ENDPOINT_TYPE.UCCET_PRINCIPAL_SERVER_BASED, uri, null, null);

      ComHelper.Advise<_IUccEndpointEvents>(uccEndpoint, this);     

      IUccSessionManager sm = (IUccSessionManager)uccEndpoint;
      sm.RegisterSessionDescriptionEvaluator(this);
      ComHelper.Advise<_IUccSessionManagerEvents>(sm, this);
      

      IUccServerSignalingSettings signalingSettings = (IUccServerSignalingSettings)uccEndpoint;
      
      UccCredential credential = signalingSettings.CredentialCache.CreateCredential(UserName, UserPassword, UserDomain);
      signalingSettings.CredentialCache.SetCredential("*", credential);
      signalingSettings.AllowedAuthenticationModes = (int)UCC_AUTHENTICATION_MODES.UCCAM_NTLM;
      signalingSettings.Server = signalingSettings.CreateSignalingServer(ServerName, transportMode);

      uccEndpoint.Enable(null);
    }

Esse código está no momento em que o endpoint é habilitado. O método RegisterSessionDescriptorEvaluator method força o objeto passado como parâmetro a implementar _IUccSessionDescriptionEvaluator.

Essa interface pede a implementação do seguinte método:

    public bool Evaluate(string bstrContentType, string bstrSessionDescription)
    {
      if (bstrContentType == "application/ms-conf-invite+xml")
        return true;
      else
        return false;
    }

Isso é complicadinho. Para cada incoming session, o método “Evaluate” será chamado. Se ele retornar “true”, estamos dizendo para a API que esta sessão é uma application session. Se ele retornar false, a UCC API vai tentar descobrir o tipo de sessão. Claro que a UCC API conhece os tipos default.

Quando o usuário recebe o convite, ele detecta que este content-type mapeia para uma application session então método “OnIncomingSession” é chamado:

    public void OnIncomingSession(IUccEndpoint pEventSource, UccIncomingSessionEvent pEventData)
    {
      if (pEventData.Session.Type == UCC_SESSION_TYPE.UCCST_APPLICATION)
      {
        pEventData.Accept();
        UccIncomingInvitationEvent inInvitation = (UccIncomingInvitationEvent)pEventData;
        
        TextReader tr = new StringReader(inInvitation.RemoteSessionDescription);
        XmlDocument inviteDoc = new XmlDocument();
        inviteDoc.Load(tr);

        // Get the conference Focus URI from the XML stream.
        string conferenceUri = inviteDoc.SelectSingleNode("Conferencing/focus-uri").InnerText;

        CreateConferenceSession(this, conferenceUri);
      }
    }

O código acima interpreta o XML e obtém o conferenceURI enviado através da application session. O CreateConferenceSession faz o mesmo explicado anteriormente no tópico “Criando uma Conference Session”.

Depois que o usuário cria a conference session, habilita o endpoint local ele consegue enviar e receber mensagens através da conference session.

Informações importantes sobre a Conferência

Quando estava desenvolvendo uma aplicação de chat sobre a UCC API, eu tive vários issues estranhos com a conferênica.

Algumas mensagens não estavam sendo roteadas para todos os usuários na conferência. Após uma semana lutando com este problema, reparei que o problema estava no content-type.

Quando o content-type da mensagem é diferente de text/plain, a mensagem não é roteada para todos os usuários. Eu não encontrei nenhuma informação sobre isso em nenhuma documentação oficial (ou forums).

Após fazer alguns sniffs bore os pacotes do Microsoft Office Communicator, reparei que o MOC manda todas as mensagens em text/plain e dentro do content-type manda a mensagem formata, codificada em Base64.

Código fonte

Aqui está todo o código fonte de todas as partes do tutorial: UCCApiSample. Está bastante bagunçado e confuso, mas mesmo assim decidi publicá-lo.

Conclusão

Existem muitas particularidades sobre a conferência na UCC API. Eu espero que este overview possa poupá-lo de grande parte do trabalho.

Infelizmente a falta de recursos sobre a UCC API na web não ajuda muito. Isso foi uma das minhas principais motivações para escrever tudo isso.

Esse é o último. Divirta-se.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s