UnifySquare Logo
 
Nav Accent Bar
From technical articles to tid-bits of important news and information, stay up to date on the latest UC happenings. Unify Square Blog
Helping you deploy the world's leading platform for Unified Communications.
 

One of the samples in the UCMA 2.0 SDK for Office Communication Server 2007 is the "ConferenceEscalation” sample application.  The UCMA 2.0 SDK samples demonstrate how to use the new Microsoft.Rtc.Collaboration name space to easily perform many collaboration activities, without having an in-depth understanding of how OCS uses the SIP protocol.

Let’s take a closer look at the “ConferenceEscalation” sample.  This sample sets up a peer-to-peer audio call between two user endpoints.  Next, the first user endpoint creates an ad-hoc (unscheduled) conference. Then, the first endpoint calls the new Conversation.BeginEscalateToConference() and Conversation.EndEscalateToConference() methods to automatically transfer (escalate) the second endpoint’s conversation to the ad-hoc conference.

Unfortunately, the “ConferenceEscalation” sample does not work as shipped with OCS 2007 R2.  Instead, a Microsoft.Rtc.Signalling.OperationFailureException is thrown by the EndEscalateToConference() method.  The failure reason is that this media provider does not support escalation.  And, if you read the fine print in the UCMACore2.0 help file for OCS 2007 R2, you will indeed find that the Conversation EscalateToConference methods are only supported for the Instant Messaging modality.  Indeed, as an exercise, if you change the AudioVideoCall object to an InstantMessagingCall object, and register to receive incoming InstantMessagingCall events, the sample will work.

The code that I will share with you, however, shows how to successfully transfer an existing audio/video call to a conference call using the UCMA 2.0 SDK.  Accomplishing this involves the following steps:

  1. Create a new conversation.
  2. Create a conference using the new conversation.
  3. Join the conference.
  4. Call into the conference (establish a call with the conference MCU).
  5. Transfer the original P2P call to the conference audio/video MCU.
  6. Terminate the original P2P call, since it is now defunct.

The following code is a modification of the code in the Escalate To Conference sample application from the UCMA 2.0 SDK for OCS 2007 R2.  First, of course, you must download and install the UCMA 2.0 SDK.  Follow the Microsoft instructions for installing carefully.  There are several pre-requisites and the SDK setup itself is a two-part install. 

Note that Chris Mayo’s blog – “Unified Communications Development” at http://blogs.msdn.com/cmayo/ contains lots of information on getting setup to do UCMA 2.0 development. 

Once you have the SDK installed, navigate to the “Program Files\Microsoft Office Communications Server 2007 R2\UCMA SDK 2.0\UCMACore\Sample Applications\Collaboration\QuickStarts\ConferenceEscalation” folder.  Take a look at the original EscalateToConference.cs file.  Build the sample and run it, noting the exception. 

Then, try changing the AudioVideoCall object and related callbacks to be an InstantMessagingCall object.  Run the sample again and note that this succeeds.

Finally, follow the instructions below to modify the code in the EscalateToConference.cs file.  Make sure that you change the server and user settings to match your environment.  Pay particular attention to the Run() and EndJoinConference() methods:

  1. Add the following namespaces to the EscalateToConference.cs file:
    • using System.Collections.Generic;
    • using Microsoft.Rtc.Collaboration.Conferencing;
    • using Microsoft.Rtc.Collaboration.ConferenceManagement;
  2. Add the following global variables before the Main() method:
    • private AutoResetEvent _waitForFlowActive = new AutoResetEvent(false);
    • private AudioVideoCall audioVideoCall;   // original AV call
  3. Replace the existing Run() method with the following:

              public void Run()
              {
                  // A helper class to take care of platform and endpoint setup and cleanup.

                  UCMADemoHelper odh = new UCMADemoHelper();

                  // Prepare and instantiate the platform.
                  _collabPlatform = odh.InitalizePlatform(_applicationName, _transportType);

                  // Create a user endpoint, using the network credential object defined above.
                  UserEndpoint callerEndpoint = odh.InitalizeRegisteredUserEndpoint(_collabPlatform, _callerUserURI, _userServer, _callerCredential);

                  // Create a second user endpoint, using the network credential object defined above.
                  UserEndpoint calleeEndpoint = odh.InitalizeRegisteredUserEndpoint(_collabPlatform, _calleeUserURI, _userServer, _calleeCredential);

                  // Here, we are accepting an Audio Video call only.
                  // If the incoming call is not an Audio Video call (for example, a custom (Foo) Call,
                  // then it will not get raised to the application. UCMA 2.0 handles this silently by having the call types register
                  // for various modalities (as part of the extensibility framework). The appropriate action (accepting the call)
                  // will be handled in the handler assigned to the method call below.
                  calleeEndpoint.RegisterForIncomingCall<AudioVideoCall>(On_AudioVideoCall_Received);

                  // Setup the call and conversation objects for the initial call (IM), and place the call (synchronously).
                  Conversation conversation = new Conversation(callerEndpoint);
                  audioVideoCall = new AudioVideoCall(conversation);
                  audioVideoCall.BeginEstablish(_calleeUserURI , null, EndCallEstablish, audioVideoCall);

                  // Force synchronization to ensure that the call is now complete.
                  _waitForCallAccept.WaitOne();
                  _waitForCallEstablish.WaitOne();

                  Console.WriteLine("");
                  Console.WriteLine(" Beginning conference creation and escalation...");
                  Console.WriteLine("");

                  //
                  // START OF MODIFIED CODE.
                  //

                  // Create a new conversation and call with the Audio Video MCU.
                  Conversation confConversation = new Conversation(callerEndpoint);

                  confConversation.ConferenceSession.StateChanged += new EventHandler<StateChangedEventArgs<ConferenceSessionState>>(ConferenceSession_StateChanged);

                  // Create and join an ad-hoc conference with two participants
                  ConferenceParticipantInformation[] participants = new ConferenceParticipantInformation[2];

                  participants[0] = new ConferenceParticipantInformation(_calleeUserURI, ConferencingRole.Attendee);
                  participants[1] = new ConferenceParticipantInformation(_callerUserURI, ConferencingRole.Leader);

                  // create an ad-hoc conference (added to sample to show more conferencing features)
                  Conference conference = CreateAdHocConference("Ad-hoc Conference", callerEndpoint, McuType.AudioVideo, participants);

                  ConferenceJoinInformation confJoinInfo = new ConferenceJoinInformation(new RealTimeAddress(conference.ConferenceUri));

                  confConversation.ConferenceSession.BeginJoin(confJoinInfo, EndJoinConference, confConversation.ConferenceSession);

                  //Wait for call to be transfered.
                  _waitForConferenceEscalation.WaitOne();

                  // Note that the original peer-to-peer conversation is now defunct.
                  // Go ahead and shut it down.
                  // check the call state on the audioVideoCall object
                  audioVideoCall.EndTerminate(audioVideoCall.BeginTerminate(null, null));

                  // just so don't have to change the rest of the code
                  conversation = confConversation;    

                  Console.WriteLine("");
                  Console.WriteLine(" Beginning conference command and control..." );
                  Console.WriteLine("");

        // END OF MODIFIED CODE

                  // Promote a participant to leader; Leaders can lock and unlock the conference, as well as possessing the ability
                  // to eject and control other participants, and mute participants (in an Audio conference).
                  // For purposes of the demonstration, choose an arbitrary conferene participant, here, the first.

                  ConversationParticipant target = null;

                  if (conversation.RemoteParticipants.Count >= 1)
                  {
                      target = conversation.RemoteParticipants[0];
                  }
                  else
                  {
                      // TODO: Error handling is left to the reader.
                  }

                  Console.WriteLine("User " + target.UserAtHost + " is currently an " + target.Role + ".");

                  // Note: This is the naive synch implementation, and is not generally suitable for production code.

        // It is only used here for brevity.
        conversation.ConferenceSession.EndModifyRole(conversation.ConferenceSession.BeginModifyRole(target, ConferencingRole.Leader, null, null));
                  Console.WriteLine("User " + target.UserAtHost + " is now a " + target.Role + ".");

                  Console.WriteLine("The conference lock's state is currently " + conversation.ConferenceSession.IsLocked + ".");
                  // Locking the conference prevents new users from joining the conference, unless explicitly called into the

      // conference through the dialout API.


                  conversation.ConferenceSession.EndLockConference(conversation.ConferenceSession.BeginLockConference(null,null));
                  Console.WriteLine("The conference lock's state is now " + conversation.ConferenceSession.IsLocked + ".");

                  // On the AudioVideoMCU, a leader can mute remote, or local participants at will.
                  // This line will mute the target's endpoint until Unmuted, or the call is torn down.
                  ParticipantEndpoint targetPE = null;
                  if (conversation.ConferenceSession.AudioVideoMcuSession.GetRemoteParticipantEndpoints()[0] != null)
                  {
                      targetPE = conversation.ConferenceSession.AudioVideoMcuSession.GetRemoteParticipantEndpoints()[0]; //Again, we assume that there is the participant from above present.
                  }
                  else
                  {
                      // TODO: Error handling is left to the reader.
                  }
                  conversation.ConferenceSession.AudioVideoMcuSession.EndMute(conversation.ConferenceSession.AudioVideoMcuSession.BeginMute(targetPE, null, null));

                  // Now, eject the participant, and then shut down the platform.

                  Console.WriteLine("The conference currently has " + conversation.ConferenceSession.GetRemoteParticipantEndpoints().Count + " attendees.");
                  //Ejection can only be performed by leaders of the conference.
                  conversation.ConferenceSession.EndEject(conversation.ConferenceSession.BeginEject(target, null, null));
                  Console.WriteLine("The conference now has " + conversation.ConferenceSession.GetRemoteParticipantEndpoints().Count + " attendees.");

                  Console.WriteLine("");
                  Console.WriteLine("Success!");
                  Console.WriteLine("");

                  Console.WriteLine("Now shutting down the platform...");
                  odh.ShutdownPlatform(_collabPlatform);

                  Console.WriteLine("");
                  Console.WriteLine("Press any key to exit");
                  Console.ReadLine();
              }

              /// <summary>
              /// Added by Unify Square just to show more conferencing features.
              /// </summary>
              /// <param name="conferenceName"></param>
              /// <param name="localEndpoint"></param>
              /// <param name="mcuType"></param>
              /// <param name="participants"></param>
              /// <returns></returns>
              Conference CreateAdHocConference(String conferenceName, LocalEndpoint localEndpoint, String mcuType, IList<ConferenceParticipantInformation> participants)
              {
                  Conference conference = null;

                  // Set up the conference properties
                  ConferenceScheduleInformation conferenceScheduleInformation = new ConferenceScheduleInformation(); 
                  conferenceScheduleInformation.AdmissionPolicy = ConferenceAdmissionPolicy.OpenAuthenticated; 
                  conferenceScheduleInformation.IsPasscodeOptional = true; 
                  conferenceScheduleInformation.Passcode = ConferenceServices.GeneratePasscode();
                  conferenceScheduleInformation.Description = conferenceName; // The verbose description of the conference.
                  conferenceScheduleInformation.ExpiryTime = System.DateTime.Now.AddHours(2); // delete time
                  conferenceScheduleInformation.ConferenceId = ConferenceServices.GenerateConferenceId();

                  // add our participants
                  foreach (ConferenceParticipantInformation participantInfo in participants)
                  {
                      conferenceScheduleInformation.Participants.Add(participantInfo);
                  }

                  // These two lines assign a set of modalities from the available MCUs to the conference.
                  // Custom modalities (and their corresponding MCUs) may be added at this time.
                  ConferenceMcuInformation conferenceMCU = new ConferenceMcuInformation(mcuType);
                  conferenceScheduleInformation.Mcus.Add(conferenceMCU);

                  // Now that the setup object is complete, schedule the conference using the conference services
                  // Note: the conference organizer is considered a leader of the conference by default.
                  conference = localEndpoint.ConferenceServices.EndScheduleConference(localEndpoint.ConferenceServices.BeginScheduleConference(conferenceScheduleInformation, null, null));

                  return conference;
              }

  4. Add the following two callback methods to determine when the new audio/video flow is configured and active.

    // Called when flow configured

    void conferenceAVCall_AudioVideoFlowConfigurationRequested(object sender, AudioVideoFlowConfigurationRequestedEventArgs args)
    {
        // flow is configured
        // wait for the flow state to go to active.
        args.Flow.StateChanged += new EventHandler<MediaFlowStateChangedEventArgs>(Flow_StateChanged);

        _waitForFlowActive.Set();   // go ahead and set now
    }

    // Called when flow state changes

    void Flow_StateChanged(object sender, MediaFlowStateChangedEventArgs args)
    {
        if (args.State == MediaFlowState.Active)
        {
            // flow has gone active
            _waitForFlowActive.Set();
        }
    }

  5. Replace the EndJoinConference() method with this method:

            private void EndJoinConference(IAsyncResult ar)
            {
                ConferenceSession confSession = ar.AsyncState as ConferenceSession;
                try
                {
                   confSession.EndJoin(ar);

                }
                catch (OperationFailureException opFailEx)
                {
                    // OperationFailureException: Indicates failure to connect the call to the remote party.
                    // It is left to the application to perform real error handling here.
                    Console.WriteLine(opFailEx.ToString());
                }
                catch (RealTimeException realTimeEx)
                {
                    // RealTimeException may be thrown on media or link-layer failures, or call rejection (FailureResponseException)
                    // It is left to the application to perform real error handling here.
                    Console.WriteLine(realTimeEx.ToString());
                }


                // Method which is NOT working for audio calls.
    //            confSession.Conversation.BeginEscalateToConference(EndEscalateConference, confSession.Conversation);

                // START OF MODIFIED CODE.

                // Call into the conference ourselves.
                AudioVideoCall conferenceAVCall = new AudioVideoCall(confSession.Conversation);

                // Register for call backs.
                conferenceAVCall.AudioVideoFlowConfigurationRequested += new EventHandler<AudioVideoFlowConfigurationRequestedEventArgs>(conferenceAVCall_AudioVideoFlowConfigurationRequested);
                conferenceAVCall.StateChanged += _call_StateChanged;

                Console.WriteLine("");
                Console.WriteLine("Establishing call with audio/video MCU");

                // Call the audio/video MCU.
                // Do this sync just to reduce lines of code for this sample
                conferenceAVCall.EndEstablish(conferenceAVCall.BeginEstablish(null, null, null));

                // We would wait for the flow to be configured for real code.
                _waitForFlowActive.WaitOne();

                Console.WriteLine("");
                Console.WriteLine("Transfering existing call to Audio Video MCU");

                // We have now joined the conference on this conversation.
                // Add our other endpoint to the conference by transfering
                // their call to the conference audio video MCU.
                // Note that the conference audio video MCU is the third-party
                // that all conference attendees communicate with.
                // The MCU "mixes" the participants speech and multiplexes it to
                // all attendees.
                // Make this synchronous just to reduce sample lines of code.
                confSession.AudioVideoMcuSession.EndTransfer(confSession.AudioVideoMcuSession.BeginTransfer(audioVideoCall, null, null, null));

                // Note that the flow becomes active after we transfer the call

                Console.WriteLine("");
                Console.WriteLine("Call has been successfully transfered");

                // set the event for both the caller and the callee,
                // since the callee is automatically transfered
                _waitForConferenceEscalation.Set();

                // END OF MODIFIED CODE.
            }

 

Have fun!

Katy Lynn McCullough-Leonard

 


May 12, 2009 09:30 by Katyle Permalink | Comments (20) | Comment RSS RSS Button Image


Comments

Comments are closed

Privacy  |  Contact  |  Terms of Use  |  www.unifysquare.com | Copyright © 2009 Unify2  -  All rights reserved.
Microsoft and the Microsoft Logos are trademarks of Microsoft, Inc.