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