Quantcast
Channel: Microsoft Unified Communications Managed API SDK forum
Viewing all articles
Browse latest Browse all 889

Inbound IVR and VoiceXML the right way?

$
0
0

Hi everybody, i'm totally new to Lync and UCMA so please be patient ;-)
I just installed the evaluation version and now i'm struggling with UCMA sdk to build a simple (but stable and reliable) application to handle a VoiceXml IVR for incoming calls.

After a while spent on googoling and channel 9 i started my own application.

My Application consists of:

1) a class called BrowserManager which inherits Microsoft.Rtc.Collaboration.AudioVideo.VoiceXml.Browser
2) a class called CallManager
3) a windows service which creates a callManager instance and starts that

As i supposed in the title, the topic is: "it's my idea correct?" 
I did some tests and everithing works fine with 3 or 4 concurrenting calls, and no other applications running but the real question is it shall be reliable in a production context with multiple applications running at the same time and 50 concurrenting incoming calls.

BrowserManager code:

using Microsoft.Rtc.Collaboration.AudioVideo;
using Microsoft.Rtc.Collaboration.AudioVideo.VoiceXml;
using Microsoft.Speech.VoiceXml.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FirstBot
{
    public class BrowserManager : Browser
    {
        public AudioVideoCall CurrentCall { get; private set; }
        public Uri VoiceXmlUri { get; private set; }

        public BrowserManager(AudioVideoCall currentCall, String voiceXmlUri)
            : base()
        {
            CurrentCall = currentCall;
            VoiceXmlUri = new Uri(voiceXmlUri);
            InitializeVoiceXmlBrowser();
        }

        public VoiceXmlResult Run()
        {
            this.SetAudioVideoCall(CurrentCall);

            return this.Run(VoiceXmlUri, null);
        }

        private void InitializeVoiceXmlBrowser()
        {
            this.Transferring += EmailDictation_Transferring;
            this.Transferred += EmailDictation_Transferred;
            this.Disconnecting += EmailDictation_Disconnecting;
            this.Disconnected += EmailDictation_Disconnected;
            this.SessionCompleted += EmailDictation_SessionCompleted;

            CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, 
                "New Browser created for call: " + CurrentCall.CallId);
        }

        private void EmailDictation_SessionCompleted(object sender, SessionCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, 
                    String.Format("Error on return: {0}", e.Error.Message));
            }
            else if (e.Cancelled)
            {
                CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, "Run was cancelled by application. Call Status: "+ this.State);
            }
            else
            {

                CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser,
                    "Run completed. Reason: " + e.Result.Reason);

                if (e.Result != null && e.Result.Namelist != null)
                {
                    CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser,"Namelist variables from VoiceXml page processing: " + e.Result.Namelist.Count);
                }
            }
        }

        private void EmailDictation_Disconnected(object sender, DisconnectedEventArgs e)
        {
            CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, "Disconnect Started. Browser state: " + e.State);
        }

        private void EmailDictation_Disconnecting(object sender, DisconnectingEventArgs e)
        {
            CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, "Disconnect Started. Browser state: " + e.SessionState);
        }

        private void EmailDictation_Transferred(object sender, TransferredEventArgs e)
        {
            CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, "Transfer completed to " + e.TargetSip);
        }

        private void EmailDictation_Transferring(Object sender, TransferringEventArgs e)
        {
            CallManager.AppendLog(CallManager.LogPrefixName.VoiceXMLBrowser, "Transfer started to " + e.TargetSip);
        }

    }
}

The idea behind it's simple. For each new incoming call an instance of BrowserManager will be created and consumed.

CallManager code:

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

namespace FirstBot
{
    public class CallManager
    {
        public String ApplicationUserAgent { get; private set; }
        public String ApplicationUrn { get; private set; }
        public String ApplicationEndPointUri { get; private set; }
        public String VoiceXmlUri { get; private set; }

        public CollaborationPlatform CollPlatform { get; private set; }
        public ApplicationEndpoint AppEndPoint { get; private set; }

        public Boolean IsAlive
        {
            get
            {
                if (AppEndPoint.State != LocalEndpointState.Terminating &&
                    AppEndPoint.State != LocalEndpointState.Terminated)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }


        public enum LogPrefixName
        {
            CollaborationPlatform = 0x0,
            ApplicationEndPoint,
            CurrentCall,
            VoiceXMLBrowser
        }


        public CallManager(String applicationUserAgent, String applicationUrn,
            String applicationEndPointUri, String voiceXmlUri)
        {
            ApplicationUserAgent = applicationUserAgent;
            ApplicationUrn = applicationUrn;
            ApplicationEndPointUri = applicationEndPointUri;
            VoiceXmlUri = voiceXmlUri;
        }

        public void Run()
        {
            InitializeCollaBorationPlatform();
            InitializeApplicationEndPoint();
        }

        private void InitializeCollaBorationPlatform()
        {
            ProvisionedApplicationPlatformSettings paps =
                new ProvisionedApplicationPlatformSettings(ApplicationUserAgent, ApplicationUrn);
            CollPlatform = new CollaborationPlatform(paps);

            CollPlatform.EndStartup(CollPlatform.BeginStartup(null, null));
            AppendLog(LogPrefixName.CollaborationPlatform, "Collaboration Platform Startup completed");        
        }

        private void InitializeApplicationEndPoint()
        {
            ApplicationEndpointSettings apes = new ApplicationEndpointSettings(ApplicationEndPointUri);
            AppEndPoint = new ApplicationEndpoint(CollPlatform, apes);
            AppendLog(LogPrefixName.ApplicationEndPoint, "ApplicationEndPoint created: " + AppEndPoint.EndpointUri);
            AppEndPoint.RegisterForIncomingCall<AudioVideoCall>(AudioVideoCallReceived);
            AppendLog(LogPrefixName.ApplicationEndPoint, "ApplicationEndPoint registered for incoming audiovideo calls");
            AppEndPoint.EndEstablish(AppEndPoint.BeginEstablish(null, AppEndPoint));
            AppendLog(LogPrefixName.ApplicationEndPoint, "ApplicationEndPoint established");
        }

        private void AudioVideoCallReceived(Object sender, CallReceivedEventArgs<AudioVideoCall> e)
        {
            AudioVideoCall currentCall = e.Call;
            AppendLog(LogPrefixName.CurrentCall, "Call received: " + currentCall.CallId);

            currentCall.AudioVideoFlowConfigurationRequested += Call_AudioVideoFlowConfigurationRequested;

            currentCall.StateChanged += HandleCallStateChanged;

            currentCall.EndAccept(currentCall.BeginAccept(null, null));
            AppendLog(LogPrefixName.CurrentCall, "Call accepted: " + currentCall.CallId);
        }

        private void Call_AudioVideoFlowConfigurationRequested(Object sender, AudioVideoFlowConfigurationRequestedEventArgs e)
        {
            if (sender.GetType() == typeof(AudioVideoCall))
            {
                AudioVideoCall currentCall = (AudioVideoCall)sender;
                currentCall.Flow.StateChanged += new EventHandler<MediaFlowStateChangedEventArgs>(Flow_StateChanged);
            }
        }

        private void HandleCallStateChanged(object sender, CallStateChangedEventArgs e)
        {
            if (sender.GetType() == typeof(AudioVideoCall))
            {
                AudioVideoCall currentCall = (AudioVideoCall)sender;

                AppendLog(LogPrefixName.CurrentCall, "CallID: " +
                    currentCall.CallId +" Underlying Call state changed. From: " + e.PreviousState.ToString()+ " To: " + e.State.ToString());
                AppendLog(LogPrefixName.CurrentCall, "CallID: " +
                    currentCall.CallId +" Reason for state change: " + e.TransitionReason.ToString());
            }
        }

        private void Flow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
        {
            if (e.PreviousState == MediaFlowState.Idle && e.State == MediaFlowState.Active)
            {
                if (sender.GetType() == typeof(AudioVideoFlow))
                {
                    AudioVideoFlow avf = (AudioVideoFlow)sender;
                    AppendLog(LogPrefixName.CurrentCall, "A new VoiceXML Browser will be instantiated and run for call: " + avf.Call.CallId);
                    using (BrowserManager ed = new BrowserManager(avf.Call, VoiceXmlUri))
                    {
                        Microsoft.Speech.VoiceXml.Common.VoiceXmlResult result = ed.Run();
                        AppendLog(LogPrefixName.CurrentCall, "VoiceXml Browsing ended with: " +
                            result.Reason.ToString() + " for call: " + avf.Call.CallId);
                    }
                }
            }
        }
        public static void AppendLog(LogPrefixName log, String message)
        {
            switch (log)
            {
                case LogPrefixName.CollaborationPlatform:
                    Voneso.SmartLibrary.Useful.AppendLog("CollaborationPlatform", message);
                    break;
                case LogPrefixName.ApplicationEndPoint:
                    Voneso.SmartLibrary.Useful.AppendLog("ApplicationEndPoint", message);
                    break;
                case LogPrefixName.CurrentCall:
                    Voneso.SmartLibrary.Useful.AppendLog("CurrentCall", message);
                    break;
                case LogPrefixName.VoiceXMLBrowser:
                    Voneso.SmartLibrary.Useful.AppendLog("VoiceXMLBrowser", message);
                    break;
            }
        }

    }
}

Here i have 1 CollaborationPlatform Object and 1 ApplicationEndPoint object, so my application waits till the end of the world for new incoming calls.

MainService code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace FirstBot.Service
{
    public partial class MainService : ServiceBase
    {
        private const String APPUSERAGENT = "firstbot.testivr";
        private const String APPURN = "urn:application:firstbot.testivr";
        private const String APPURI = "sip:firstbot.testivr@intranet.local";
        private const String VXMLURI = "http://worker02.intranet.local/voicexml/test.vxml";

        private BackgroundWorker bgw { get; set; }
        private CallManager callManager { get; set; }

        public MainService()
        {
            InitializeComponent();
            InitBgw();
        }

        private void InitBgw()
        {
            bgw = new BackgroundWorker();
            bgw.DoWork += bgw_DoWork;
            bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
        }

        private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bgw.Dispose();
            GC.SuppressFinalize(bgw);
            InitBgw();

            bgw.RunWorkerAsync();
        }

        private void bgw_DoWork(object sender, DoWorkEventArgs e)
        {
                callManager = new CallManager(APPUSERAGENT, APPURN, APPURI, VXMLURI);
                callManager.Run();     
        }

        protected override void OnStart(string[] args)
        {
            bgw.RunWorkerAsync();
        }
    }
}

mmm... this is the part i like less
My intent it's to prevent service from closing in case of errors. I'm not sure it's can be accomplished by the Background worker. 

Basically i think if an error occurs (i'm not talking about untrapped runtime errors that is another issue i will focus next) and CollaborationPlatform and ApplicationEndPoint need to be reinitialized i think the BackGround worker should never fires the runworker completed event, and so my service will stay alive without working properly.

I had a second idea but i think it's like a workaround so don't like it neither. I can change the service to instantiate and Run CallManager.. if error occurs the service will end 

Somebody has a better ideas? (perhaps just a different approach could be already better :-D )

Thanks to all who would like to spend some time on that.

Cecco


Viewing all articles
Browse latest Browse all 889

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>