Cookie CSS

Friday, May 29, 2015

Understanding UCMA Back to Back Calls Part III

This is the final part of this series on the Back2Back call class.  Last week we walked through setting a context and self transferring the call in order to get it in the proper state.  This week we'll continue our look as our ConferenceCallSession class.

Now that call has been self transfered, I want our tech support app to use conferences to handle the rest.  Let's start with the HandleSelfTransfer code.

        internal void HandleSelfTransfer(AudioVideoCall call)
        {
            // The incoming call will serve as the first call leg.
            AudioVideoCall incomingCallLeg = call;

            // Create a second call leg for the call into the conference.
            Conversation conferenceCallLegConversation =
                new Conversation(call.Conversation.Endpoint);

            // Use a fake SIP URI when joining the conference.
            //conferenceCallLegConversation.Impersonate("sip:support@bridgeoc.com", "sip:support@bridgeoc.com", "Bridge OC Support");

            string realcallNum = "";
            string realcallUri = "";
            string realcallName = "";

            try
            {
                realcallUri = incomingCallLeg.Conversation.RemoteParticipants[0].Uri;
                realcallName = incomingCallLeg.Conversation.RemoteParticipants[0].DisplayName;
                realcallNum = incomingCallLeg.Conversation.RemoteParticipants[0].PhoneUri;

                conferenceCallLegConversation.Impersonate(realcallUri, realcallNum, realcallName);

    
            }
            catch { }

            // Join the conference as a normal participant.
            ConferenceJoinOptions joinOptions = new ConferenceJoinOptions()
            {
                JoinMode = JoinMode.Default
            };

            ConferenceJoinOptions trustedOptions = new ConferenceJoinOptions()
            {
                JoinMode = JoinMode.TrustedParticipant
            };

            // Join the ad hoc conference, creating the SIP dialog
            // with the new conference focus.
            try
            {
                conferenceCallLegConversation.ConferenceSession.BeginJoin(
                    joinOptions,
                    ar =>
                    {
                        try
                        {
                            conferenceCallLegConversation.ConferenceSession.EndJoin(ar);

                            ConferenceRecorder crec = new ConferenceRecorder(conferenceCallLegConversation.ConferenceSession.ConferenceUri, _logger,  _appEndpoint, _agentSipUri);
                            crec.Start();

                            // Create an AudioVideoCall for the call to the A/V MCU.
                            AudioVideoCall conferenceCallLeg =
                                new AudioVideoCall(conferenceCallLegConversation);

                            InitiateBackToBackCall(incomingCallLeg, conferenceCallLeg);

                        }
                        catch (RealTimeException ex)
                        {
                            _logger.Log("Failed joining ad hoc conference.", ex);
                        }
                    },
                    null
                );
            }
            catch (InvalidOperationException ex)
            {
                _logger.Log("Failed joining ad hoc conference.", ex);
            }
        }

Let's look at the code for InitateBackToBackCall which does the impersonation of our support alias for the caller to see.

        private void InitiateBackToBackCall(AudioVideoCall incomingCallLeg, AudioVideoCall conferenceCallLeg)
        {
            // Create the settings for the back to back call.
            // There's no need to provide a SIP URI for the outgoing call
            // since the conversation is already associated with the conference.
            BackToBackCallSettings settings1 = new BackToBackCallSettings(incomingCallLeg);
            BackToBackCallSettings settings2 = new BackToBackCallSettings(conferenceCallLeg);

            incomingCallLeg.Conversation.Impersonate("sip:support@bridgeoc.com", "sip:support@bridgeoc.com", "Bridge OC Support");


            
            // Create and establish the back to back call.
            BackToBackCall b2bCall = new BackToBackCall(settings1, settings2);

            incomingCallLeg.StateChanged += conferenceCallLeg_StateChanged;
            conferenceCallLeg.StateChanged += conferenceCallLeg_StateChanged;

            try
            {
                _logger.Log("Establishing back to back call...");

                b2bCall.BeginEstablish(
                    ar =>
                    {
                        try
                        {
                            b2bCall.EndEstablish(ar);

                            _logger.Log("Established back to back call.");

                           
                            try
                            {
                                _logger.Log("Sending Conference Invites");


                                foreach (string u in _agentSipUri.Split(','))
                                {
                                    InviteToConf(conferenceCallLeg.Conversation, u);
                                }

                            }
                            catch { }

                        }
                        catch (RealTimeException ex)
                        {
                            _logger.Log("Failed establishing a back to back call.", ex);
                        }
                    },
                    null);
            }
            catch (InvalidOperationException ioex)
            {
                _logger.Log("Failed initiating a back to back call.", ioex);
            }

            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(15000);
                allAttached = true;
            });
        }

In this example _agentSipUri is a List<string> object containing the URIs of all the users in the tech support group.  They all get invited which seems like overkill, but since the caller won't hear or see them join, they can easily silently drop if someone else is already helping the customer.  The other thing you no doubt say was the call to a ConferenceRecorder class.  We use this class to record the audio of the conference to local file.

Here is the full class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;
using Microsoft.Rtc.Collaboration.AudioVideo;
using System.Threading;

namespace B2B1
{
    internal class ConferenceRecorder
    {
        string _agentSipUri;
        Guid _guid = new Guid();
        Recorder _recorder = new Recorder();
        ApplicationEndpoint _appEndpoint;
        bool allAttached = false;
        string _conferenceURI;
        ILogger _logger;
        AudioVideoCall tAV;
        List<string> localParticipants = new List<string>();

        internal ConferenceRecorder(string confURI, ILogger logger, ApplicationEndpoint _apendpoint, string agenturi)
        {
            _conferenceURI = confURI;
            _logger = logger;
            _appEndpoint = _apendpoint;
            _agentSipUri = agenturi;
        }

        internal void Start()
        {
            _guid = Guid.NewGuid();
            _recorder.SetSink(new WmaFileSink(@"C:\temp\" + _guid.ToString() + ".wma"));

            ConferenceJoinOptions trustedOptions = new ConferenceJoinOptions()
            {
                JoinMode = JoinMode.TrustedParticipant
            };


            try
            {
                Conversation tConv = new Conversation(_appEndpoint);
                tAV = new AudioVideoCall(tConv);
                tConv.ConferenceSession.BeginJoin(_conferenceURI, trustedOptions, myt =>
                {
                    tConv.ConferenceSession.EndJoin(myt);

                    tConv.ConferenceSession.ParticipantEndpointAttendanceChanged += ConferenceSession_ParticipantEndpointAttendanceChanged;
                    tConv.ConferenceSession.StateChanged += ConferenceSession_StateChanged;

                    tAV.BeginEstablish(mytc =>
                    {
                        tAV.EndEstablish(mytc);

                        while (tAV.Flow == null)
                        {
                            Thread.Sleep(10);
                        }

                        _recorder.AttachFlow(tAV.Flow);
                        _recorder.Start();
                        _logger.Log("Attaching Recorder");


                    }, null);
                }, null);
            }
            catch { }
        }

        void ConferenceSession_StateChanged(object sender, StateChangedEventArgs<ConferenceSessionState> e)
        {
            _logger.Log("Conf State was " + e.PreviousState.ToString() + ", Now " + e.State.ToString());
        }

        void ConferenceSession_ParticipantEndpointAttendanceChanged(object sender, ParticipantEndpointAttendanceChangedEventArgs<ConferenceParticipantEndpointProperties> e)
        {
            _logger.Log("Conference Join or Leave");

            try
            {
                foreach (KeyValuePair<ParticipantEndpoint, ConferenceParticipantEndpointProperties> pair in e.Joined)
                {
                    localParticipants.Add(pair.Key.Participant.Uri);

                    if (_agentSipUri.ToUpper().Contains(pair.Key.Participant.Uri.ToUpper()))
                    {
                        allAttached = true;
                    }

                    _logger.Log(string.Format("{0} joined the conference.", pair.Key.Participant.Uri));
                }
            }
            catch { }

            try
            {
                foreach (KeyValuePair<ParticipantEndpoint, ConferenceParticipantEndpointProperties> pair in e.Left)
                {
                    localParticipants.Remove(pair.Key.Participant.Uri);

                    _logger.Log(string.Format("{0} left the conference.", pair.Key.Participant.Uri));

                    if (localParticipants.Count == 1 && !_agentSipUri.ToUpper().Contains(localParticipants[0].ToUpper()))
                    {
                        _logger.Log("Stopping Recorder");
                        _recorder.Stop();
                        _recorder.RemoveSink();

                        tAV.BeginTerminate(myterm =>
                        {
                            tAV.EndTerminate(myterm);
                        }, null);

                    }
                }
            }
            catch { }

        }

    }
}

That will end my 3 part series showing you how to use the back2back class in conjunction with conferences and recording to make a nice mini tech support call center application in UCMA.


Doug Routledge, C# Lync, Skype for Business, SQL, Exchange, UC Developer  BridgeOC
Twitter - @droutledge @ndbridge