Course 2 / Lecture 9

How to build a VoIP PBX in C#

How to create call queue with more functionalities

With the help of this example, you can add other functionalities to your already created call queue. One such functionality is that the customers waiting on the line can be told how many other customers are before them and how much longer should they wait until they can talk to an agent.

What knowledges would you need?

To fully understand this guide, you might need to study the following chapter first:

  • Call queue: you can learn how to start to develop a basic call queue.
    Learn more...

Advanced Call queue source code analysis in C#

For the sake of simplicity, we still have one agent and more than one incoming call. The first call will be answered by the agent the rest will be in call queue. To the call queued customers the Text-to-Speech engine will read how many customers are before them in every 15 seconds and what is the expected waiting time.

Compared to the previous call queue example, this extension consists 2 new classes: the CallQueueHandlerContainer and the CallHistory classes.

using System;
using System.Collections.Generic;

namespace MyPBX.CallQueue
{
    class CallQueueHandlerContainer
    {
        List<CallQueueCallHandler> callQueueCallHandlers;

        public CallQueueHandlerContainer()
        {
            callQueueCallHandlers = new List<CallQueueCallHandler>();   
        }

        public void Add(CallQueueCallHandler callHandler)
        {
            callQueueCallHandlers.Add(callHandler);
        }

        public int Count()
        {
            return callQueueCallHandlers.Count;
        }

        public int GetIndex(CallQueueCallHandler callHandler)
        {
            return callQueueCallHandlers.IndexOf(callHandler);
        }

        public void Remove(CallQueueCallHandler callHandler)
        {
            callQueueCallHandlers.Remove(callHandler);
        }

        public CallQueueCallHandler Next()
        {
            if (callQueueCallHandlers.Count == 0)
                return null;
            return callQueueCallHandlers[0];
        }
    }
}
using System.Collections.Generic;
using Ozeki.VoIP.PBX.Services;
using Ozeki.VoIP.PBX.PhoneCalls.Session;

namespace MyPBX.CallQueue
{
    class CallHistory
    {
        static Dictionary<string, List<ISession>> callHistoryStatistics = new Dictionary<string, List<ISession>>();
 
        public static void Init(ICallManager callManager)
        {
            callManager.SessionClosed += callManager_SessionClosed;
        }

        public static List<ISession> GetCallHistory(string userId)
        {
            if(!callHistoryStatistics.ContainsKey(userId))
                return new List<ISession>();

            return callHistoryStatistics[userId];
        }

        static void callManager_SessionClosed(object sender, VoIPEventArgs<ISession> e)
        {
            var callee = e.Item.CalleeInfo.Owner.ExtensionID;

            UpdateCallStatistics(callee, e.Item);

        }

        private static void UpdateCallStatistics(string party, ISession session)
        {
            if (!callHistoryStatistics.ContainsKey(party))
                callHistoryStatistics[party] = new List<ISession>();

            callHistoryStatistics[party].Add(session);
        }
    }
}

The CallQueueHandlerContainer class stores callQueueCallHandler objects (actually the calls themselves) in a list, furthermore masks all the operations can be performed, such as Add, Count, GetIndex, Remove and Next. With it's help the rank of the call in the call queue can be easily identified, you just need to call the GetIndex method.

The CallHistory class handles the built in system calls, and by it's help these data can be accessed, you just need to call the GetCallHistrory method.

Within the CallQueueCallHandler class you need to create a textToSpeech object. By it's help, information will be given to the client about his/her position in the line, and about the waiting period. Because between the notifications the mp3 music - that was used in the basic example - will be heard during the calls, here you need to use the AudioMixerMediaHandler to link these with the calls. (You can find more information about it's use here.)

The expected waiting time will be calculated in the timer_Elapsed method. (Beside this, the method works in a way that was seen at the basic call queue.)

void timer_Elapsed(object sender, ElapsedEventArgs e)
{
	if (!call.CallState.IsInCall())
    	return;

    var memb = new List<string>(members);
            
	foreach (var activeSession in callManager.ActiveSessions)
    {
		var callee = activeSession.CalleeInfo.Owner.ExtensionID;
		var caller = activeSession.CallerInfo.Owner.ExtensionID;

		memb.Remove(callee);
		memb.Remove(caller);

		double sum = 0;
		var callCounter = 0;
                
		foreach (var m in memb)
		{
			List<ISession> historyResult = CallHistory.GetCallHistory(m);

			foreach (var session in historyResult)
			{
				++callCounter;
				sum += session.TalkDuration.TotalSeconds;
			}
		}

		if (sum != 0)
		{
			waitingTime = sum / callCounter;
        }
	}

	if(!blindTransferEnabled)
		return;

	foreach (var member in memb)
	{
		if (extensionContainer.GetExtension(member) != null)
		{
			call.BlindTransfer(member);
		}
	}
}

For this, it is needed to be go through the agent's call history, and to calculate the average duration of a call.

To continuously give new information (position in the queue, waiting time) to the clients, creating a new timer was necessary. It runs out in every fifteen seconds, and calls the timerAgent_Elapsed method. This method pauses the mp3 music until the texToSpeech method reads the client's position, and his/her expected waiting time (if the agent haven't had any calls yet, the waiting time can't be calculate, so in this case this information doesn't need to be read to the client). It is important, that the texToSpeech need to be cleared by using the Clear method.

void timerAgent_Elapsed(object sender, ElapsedEventArgs e)
        {
            mp3Player.Pause();
            textToSpeech.Clear();

            if (waitingTime == 0)
            {
                textToSpeech.AddAndStartText(string.Format("{0} more customers are waiting in line before you. Please be patient.",
                                                           callQueueHandlerContainer.GetIndex(this)));
            }
            else
            {
                textToSpeech.AddAndStartText(string.Format("{0} more customers are waiting in line before you. " +
                                                           "Your estimated wait time is approximately {1} seconds. Please be patient.",
                                                           callQueueHandlerContainer.GetIndex(this),
                                                           (int)waitingTime));
            }
        }

When the texToSpeech ends, because it is signed up to the TextToSpeechCompleted method, it will be carried out. Here you can reset the streaming of the mp3.

void TextToSpeechCompleted(object sender, EventArgs e)
{
	mp3Player.Start();
	textToSpeech.Clear();
}

If you have any questions or need assistance, please contact us at info@voip-sip-sdk.com

You can select a suitable Ozeki VoIP SIP SDK license for PBX development on Pricing and licensing information page

Related Pages

More information