Using UCMA 3.0 BackToBackCall: Application Details (Part 2 of 4)

Summary:   The BackToBackCall class is an important addition to Microsoft Unified Communications Managed API (UCMA) 3.0. Part 2 presents the details of setting up the outgoing call and writing a handler for the incoming call for the BackToBackCall instance.

Applies to:   Microsoft UCMA 3.0 SDK | Microsoft Lync Server 2010

Published:   May 2011 | Provided by:   Mark Parker, Microsoft | About the Author

Contents

Download code   Download code

This article is the second in a four-part series of articles on how to use the BackToBackCall class.

Part 2 discusses the tasks that are specific to an application that uses the BackToBackCall class. For the complete code listing for the application described in this series of articles, see Using UCMA 3.0 BackToBackCall: Code Listing (Part 3 of 4).

There are several steps that must be taken before a BackToBackCall instance can be put to use. The initial steps are devoted to setting up the outgoing call leg for the BackToBackCall instance.

Note Note

The next section in this article describes setting up the incoming call leg when an incoming call arrives.

  1. Create and establish the UserEndpoint instance. This endpoint represents the user of the application.

  2. Register a delegate to be invoked when an incoming call arrives. In the following code example, the name of the delegate is incomingAVCall_CallReceived.

  3. Create a Conversation instance for the outgoing call (the call to the agent). The Conversation instance for the incoming call is created after the incoming call arrives.

  4. Create an AudioVideoCall instance for the outgoing call.

    Important note Important

    The outgoing call must be in the Idle state. Establishing the BackToBackCall instance automatically establishes the outgoing call.

  5. Create settings for the outgoing leg of the BackToBackCall instance, by creating an instance of the BackToBackCallSettings class.

The following example shows an implementation of these steps.

// Declarations of variables used in this and the following examples.
private UCMASampleHelper _helper;
private UserEndpoint _userEndpoint;
private AudioVideoCall _incomingAVCall;
private AudioVideoCall _outgoingAVCall;
private static String _calledParty;
private Conversation _outgoingConversation;
private BackToBackCallSettings _outgoingCallLeg;
private BackToBackCall _b2bCall;
private AutoResetEvent _waitUntilOneUserHangsUp = new AutoResetEvent(false);
private AutoResetEvent _waitForB2BCallToEstablish = new AutoResetEvent(false);
.
.
.
// Initialize and register the endpoint, using the credentials of the user that the application will be acting as.
_helper = new UCMASampleHelper();
_userEndpoint = _helper.CreateEstablishedUserEndpoint("B2BCall Sample User");
_userEndpoint.RegisterForIncomingCall<AudioVideoCall>(incomingAVCall_CallReceived);

// Settings for the outgoing call (to the agent).
ConversationSettings outConvSettings = new ConversationSettings();
outConvSettings.Priority = _conversationPriority;
outConvSettings.Subject = _outConversationSubject;
 
// Create the Conversation instance between the application and the agent.
_outgoingConversation = new Conversation(_userEndpoint, outConvSettings);

// Create the outgoing call between the application and the agent.
_outgoingAVCall = new AudioVideoCall(_outgoingConversation);

// Register for notification of the StateChanged event on the outgoing call. 
_outgoingAVCall.StateChanged += new EventHandler<CallStateChangedEventArgs>(outgoingAVCall_StateChanged);

// Prompt for called party - the agent.
_calledParty = UCMASampleHelper.PromptUser("Enter the URI of the called party, in sip:User@Host form or tel:+1XXXYYYZZZZ form => ", "RemoteUserURI1");

_outgoingCallLeg = new BackToBackCallSettings(_outgoingAVCall, _calledParty);

// Pause the main thread until both calls, the BackToBackCall, both conversations,
// and the platform are shut down.
_waitUntilOneUserHangsUp.WaitOne();

The delegate that was registered in step 2 of the previous section is invoked when an incoming call arrives. In the following example, the CallReceivedEventArgs<TCall> parameter in the delegate can be used to retrieve the AudioVideoCall instance. An AudioVideoCall instance is required when a BackToBackCallSettings instance is created for the incoming call leg.

After the settings objects are created for both call legs, the BackToBackCall instance can be created, by using the class constructor. Next, the BackToBackCall instance must be established, by using a call to the BeginEstablish(AsyncCallback, Object) method. In the following example, the call to the WaitOne method on the _waitForB2BCallToEstablish object pauses the main thread until the callbacks and event handlers that are running in worker threads are complete.

The following example shows the definition of the delegate that is invoked when an incoming call arrives.

private void incomingAVCall_CallReceived(object sender, CallReceivedEventArgs<AudioVideoCall> e)
{
  _incomingAVCall = e.Call;
  // Register for notification of the StateChanged event on the incoming call.
  _incomingAVCall.StateChanged += new EventHandler<CallStateChangedEventArgs>(incomingAVCall_StateChanged);

  // Create a new conversation and settings for the incoming call leg.
  _incomingConversation = new Conversation(_userEndpoint);
  
  _incomingCallLeg = new BackToBackCallSettings(_incomingAVCall);
  
  // Create the back-to-back call instance.
  // A Destination URI is needed only for the outgoing call leg.
  _b2bCall = new BackToBackCall(_incomingCallLeg, _outgoingCallLeg);
  
  // Begin the back-to-back session; provide a destination.
  try
  {
    IAsyncResult result = _b2bCall.BeginEstablish(BeginEstablishCB, _b2bCall);
  }
  catch (InvalidOperationException ioe)
  {
    Console.WriteLine("The BackToBackCall instance must be in the Idle state." + ioe.Message.ToString());
  }
  _waitForB2BCallToEstablish.WaitOne();
}

The following example shows the callback method that is listed as the first parameter in the call to BeginEstablish on the BackToBackCall instance. The second line of code allows the main thread to resume operation.

// Callback for BeginEstablish on the BackToBackCall instance.
private void BeginEstablishCB(IAsyncResult ar)
{
  _b2bCall.EndEstablish(ar);
  _waitForB2BCallToEstablish.Set();
}

After the media path between the incoming call and the outgoing call is established, the application runs until either person hangs up. To be notified of this event, the application registers to be notified when the StateChanged event is raised on each call.

The following example shows how the application registers for notification of the StateChanged event on the incoming call. The code for registering for notification of the same event on the outgoing call is almost the same.

_incomingAVCall.StateChanged += new EventHandler<CallStateChangedEventArgs>(incomingAVCall_StateChanged);

The handler for the StateChanged event on the outgoing call is registered in an almost identical way.

The next example shows the handler that is called when the StateChanged event on the incoming call is raised. If the incoming caller hangs up, the TransitionReason property on the CallStateChangedEventArgs instance is reset to TerminatedLocally, a value of the CallStateTransitionReason enumeration. The application can be improved by making it respond to other transition reasons, such as TransferredRemotely.

In the following example, the handler checks the value of e.TransitionReason. If its value is CallStateTransitionReason.TerminatedRemotely, the handler unregisters for notification of the StateChanged event on both calls, and then calls the BeginTerminate method on the incoming call. If the value of the outgoing call is not null, the handler calls BeginTerminate on the outgoing call.

The following example shows the handler for the StateChanged event on the incoming call.

void incomingAVCall_StateChanged(object sender, CallStateChangedEventArgs e)
{
  Console.WriteLine("Incoming call - state change.\nPrevious state: "
                   + e.PreviousState + "\nCurrent state: " + e.State
                   + "\nTransitionReason: " + e.TransitionReason + "\n"); 
  if (e.TransitionReason == CallStateTransitionReason.TerminatedRemotely)
  {
    // If one call has been terminated remotely, unregister both calls for 
    // notification of the StateChanged event.
    _outgoingAVCall.StateChanged -= outgoingAVCall_StateChanged;
    _incomingAVCall.StateChanged -= incomingAVCall_StateChanged;
    _incomingAVCall.BeginTerminate(TerminateCallCB, _incomingAVCall);
    
    if (_outgoingAVCall != null)
    {
      Console.WriteLine("Terminating the incoming call...");
      _outgoingAVCall.BeginTerminate(TerminateCallCB, _outgoingAVCall);
    }

    FinishShutdown(); 
  }
}

The following example shows the handler for the StateChanged event on the outgoing call. Both handlers for the StateChanged event have the same basic logic.

void outgoingAVCall_StateChanged(object sender, CallStateChangedEventArgs e)
{
  Console.WriteLine("Outgoing call - state change.\nPrevious state: " 
                   + e.PreviousState + "\nCurrent state: " + e.State
                   + "\nTransitionReason: " + e.TransitionReason + "\n");
  if (e.TransitionReason == CallStateTransitionReason.TerminatedRemotely)
  {
    // If one call has been terminated remotely, unregister for 
    // notification of the StateChanged event.
    _incomingAVCall.StateChanged -= incomingAVCall_StateChanged;
    _outgoingAVCall.StateChanged -= outgoingAVCall_StateChanged;
    _outgoingAVCall.BeginTerminate(TerminateCallCB, _outgoingAVCall);
    
    if (_incomingAVCall != null)
    {
      Console.WriteLine("Terminating the incoming call...");
      _incomingAVCall.BeginTerminate(TerminateCallCB, _incomingAVCall);
    }

    FinishShutdown(); 
  }
}

Code used in production might have to account for the possibility of both callers hanging up at the same time.

The following example shows the callback for BeginTerminate on both calls.

private void TerminateCallCB(IAsyncResult ar)
{
  AudioVideoCall audioVideoCall = ar.AsyncState as AudioVideoCall;

  // Finish terminating the call.
  audioVideoCall.EndTerminate(ar);
}

Mark Parker is a programming writer at Microsoft Corporation. His principal responsibility is the UCMA SDK documentation.

Show: