info@voip-sip-sdk.com Tel: +36 1 371 0150

How to build a Silverlight SIP softphone for web to SIP calls

Explanation

Prerequisities

Download: 03_Web2SIP_calls.zip

Due to its outstanding flexibility, Ozeki SIP SDK also allows to build a Silverlight SIP softphone for web to SIP calls. This article introduces you the basic concepts of a web page embedded softphone solution with the use of Silverlight technology. After reading through this material you will be able to create your own softphone application that you can put on your web site so as your customers could use it for calling you any time they want only by visiting your site.

Introduction

Webphone technology makes it possible for your customers to be able to call you using your web site as the webphone can a web page embedded solution with all the functions of a fully operable softphone (Figure 1).


Figure 1 - Ozeki VoIP SIP SDK supports web to SIP communication using Silverlight

Assuming that you are familiar with the main terms concerning a webphone and you have created your first own click-to-call webphone solution before, this guide will show you how to implement a fully functional softphone application that can be embedded in your web page.

If you have not read the guides about programming click-to-call webphone applications or you feel that you are not familiar with all the basic terms, please check Web to web voice calls using Silverlight and Web to web video calls using Silverlight before you read further.

This guide will show the basic steps of implementing a fully featured Silverlight softphone client-server application, however, it may not be as detailed as the other two sites mentioned before. So, it is strongly suggested to check those before you start to write the sample program that is described on this page.

The webphone application you will create with this guide is a C# application. You will need to have Visual Studio 2010, Silverlight 4 and Ozeki VoIP SIP SDK in order to be able to get through this guide. If you do not have some of these you will need to download and install them before starting the programming stage.

As the Silverlight environment is not capable of establishing TCP or UDP communication, the webphone application will need the help of a webphone server that will provide the channel between the clients. The solution you will see establishes the connection between a Silverlight client and a SIP gateway that will connect to a SIP telephone network.

At first you will need to write the server-side code and after that you can create the client-side application. This order can be changed, however, you will not be able to test the client without a server.

The server functionality will need some basic changes

If you have checked the webphone server on the above mentioned sites, you will see that you need to make some basic changes to be able to establish a SIP connection. It may be good if you read through the pages about softphone technology in order to see the fundament of this new server.

The basic softphone programming guide can be found on Ozeki VoIP SIP Softphone page. It is strongly recommended to become familiar with the basic terms of softphones if you want to create a Silverlight softphone solution.

If you have read all the above suggested material, you will find it easy to create the softphone application. If you have any doubts, however, about some parts of the program, feel free to recheck the introduction pages to be sure you understand everything.

The MyClient class will define the client-side functions within the server

As you surely know the MyClient class in the previous click-to-call example programs was only a storage class that had an IClient object and a name field defined in it. In this case, the MyClient class will represent a more complex function as it will store the whole SIP support and background for the webphone clients.

Code 1 shows the tools you will use when having a webphone client. You can see a lot of familiar tools as, for example, there is a need for a MediaConnector as always

There are some softphone specific definitions here too like the ISoftPhone or the IPhoneLine objects. You will see how easy is to merge these two sets of tools together in a web site embedded softphone.

IClient client;

ISoftPhone softPhone;
IPhoneLine phoneLine;
IPhoneCall call;

IStreamService streamService;
string publishStreamName;
string playStreamName;

MediaConnector mediaConnector;
PhoneCallAudioSender phoneCallAudioSender;
PhoneCallAudioReceiver phoneCallAudioReceiver;

MediaGatewayAudioReceiver mediaGatewayAudioReceiver;
MediaGatewayAudioSender mediaGatewayAudioSender;

Code 1 - SIP initialization in MyClient class

In order to be able to use the names without labeling them with the namespaces, you will need to use some new using lines in your code like in Code 2.

using Ozeki.Common;
using Ozeki.Media;
using Ozeki.Media.MediaHandlers;
using Ozeki.MediaGateway;
using Ozeki.MediaGateway.Data;
using Ozeki.MediaGateway.Service;
using Ozeki.VoIP;
using Ozeki.VoIP.Media;
using Ozeki.VoIP.SDK;

Code 2 - Some useful using lines will be needed too

Of course, you can only use these tools and namespaces if you have registered your Ozeki VoIP SIP SDK to your server project on the Solution Explorer panel.

When creating a new client, the program initializes the basic tools for the connection. At this time the server itself will provide an available SIP account for the clients.

The constructor of the MyClient class initializes the basic tools and sets the field values according to the parameters (Code 3).

This constructor shows how the two basic concepts of the softphone and the webphone technologies are merged together. You create a softphone but at the same time you need a StreamService object for the webphone functionality too.

The MediaConnector has to register the MediaSender object to the phoneCallMediaSender that makes the transmission between the two solutions. The MediaSender object is a new type that is defined in this project with the name of MyMediaSender.

public MyClient(IClient client, IStreamService streamService, ISoftPhone softPhone, SIPAccount account)
{
    mediaConnector = new MediaConnector();
    phoneCallAudioSender = new PhoneCallAudioSender();
    phoneCallAudioReceiver = new PhoneCallAudioReceiver();

    this.streamService = streamService;
    this.softPhone = softPhone;
    this.client = client;

    Account = account;
}

Code 3 - MyClient class constructor

The MyClient class implements the basic softphone function of registering to and unregistering from the SIP PBX. This is actually made the same way as in case of an ordinary softphone (Code 4).

The phoneLine is created with the SIP account the client gets from the server and it must be registered to the state change event. The softphone object also has to be registered to the incoming call event in order to be able to notice an incoming call. The last step is to register the phone line to the softphone. This establishes the PXB registration.

{
    phoneLine = softPhone.CreatePhoneLine(Account);

    phoneLine.PhoneLineStateChanged += PhoneLine_PhoneLineInformationCanged;
    softPhone.IncomingCall += SoftPhone_IncomingCall;


    softPhone.RegisterPhoneLine(phoneLine);

}

Code 4 - The MyClient class registers the softphone to a SIP PBX

Unregistering the softphone from the PBX has to contain the opposite instructions. The call has to be hung up and the EventHandlers has to be removed, and at last the phone line has to be unregistered from the softphone.

You will need to write the two EventHandler methods of the incoming call and the call state change. These methods are slightly different from the ones you have created for a simple softphone as at this time the server has to make sure that all the clients got the right messages about the events.

The incoming call can be handled with the simple method shown in Code 5. You need to register the call to the basic events and you will need to write those methods too in this class.

The method consists of one conditional instruction that ensures that only in case of the proper phone line will it do anything. In any other cases the incoming call does not belong to this client and there is nothing to do with it.

 if (e.Item.PhoneLine == phoneLine)
    {
        call = e.Item;
        call.CallErrorOccured += Call_ErrorOccured;
        call.CallStateChanged += Call_StateChanged;

        OnCallRequest(call.DialInfo.DisplayName);
    }

Code 5 - In case of an incoming call you will register to some events of the call

The call state change event is one of the most important events to notice. This informs both the server and the client about a change in the call state (Code 6).

The method can be divided into two sections. The first section is a great switch instruction that decides about the type of the change and runs the proper functions. In case of an incoming call the phoneCallMediaSender object has to be attached to the call and the server and the client have to publish the stream names.

The publishStreamName is the name that the client publishes and the playStreamName is the one that the server will publish.

This method also sets the mediaStream object with the information that the server gives (playStreamName) and it also calls the OnInCall method that invokes the proper method on the client's side to inform the client application about the incoming call.

In case of a completed call that means that one of the clients hung up the phone, the method will detach the phoneCallMediaSender from the call, remove the streamService object from the mediaStream and free the call object.

The second section of this method has only one instruction (last line of Code 6) that is for informing the remote client about the state change of the call. This method call will invoke the call state change method of the remote client.

switch (e.Item)
    {
        case CallState.InCall:
            phoneCallAudioSender.AttachToCall(call);
            phoneCallAudioReceiver.AttachToCall(call);

            publishStreamName = Guid.NewGuid().ToString(); // This is the ID the client publishes
            playStreamName = Guid.NewGuid().ToString(); // This is the ID the server publishes

            var mediaStream = streamService.CreateStream(playStreamName);
            mediaGatewayAudioReceiver = new MediaGatewayAudioReceiver(mediaStream);

            mediaConnector.Connect(phoneCallAudioReceiver, mediaGatewayAudioReceiver);

            OnInCall(publishStreamName, playStreamName);
            break;

        case CallState.Completed:

            phoneCallAudioSender.Detach();
            phoneCallAudioReceiver.Detach();

            streamService.RemoveStream(mediaGatewayAudioReceiver.MediaStream);
            mediaConnector.Disconnect(phoneCallAudioReceiver, mediaGatewayAudioReceiver);
            mediaGatewayAudioReceiver.Dispose();

            mediaGatewayAudioReceiver = null;
            call = null;
            break;
    }

    OnCallStateChanged(e.Item);
}

Code 6 - The call state change event has to reach the server and both connected clients

The MyClient class contains the methods that invoke the client-side methods according to the messages from the other clients. These methods are OnCallStateChanged, OnCallErrorOccurred, OnSetReadyStatus, OnCallRequest and OnInCall. The client-side application has to have the proper methods that can be invoked in case of these method calls.

The method invocation code line is shown in Code 7. The server can invoke the client-side method by giving the method's name as the first parameter of the invoke call and then it has to set all the parameters the client-side method needs.

client.InvokeMethod("ClientMethodName", [method parameter(s)]);

Code 7 - The server can invoke the client's proper method with this instruction

The MyClient class also contains the basic methods for making, accepting, rejecting and hanging up the call. These are the basic softphone methods and they can be implemented similar to the ones you did in case of the softphone application.

Code 8 shows the main functionality of the Call method that is for starting a call towards a remote client. The call object has to be set with the proper parameters and it has to be registers to the basic events mentioned before. After all these settings, the server only has to start the call.

if ((phoneLine.RegisteredInfo == PhoneLineState.RegistrationSucceded || phoneLine.RegisteredInfo == PhoneLineState.NoRegNeeded) && call == null)
{
call = softPhone.CreateCallObject(phoneLine, dialNumber);

call.CallErrorOccured += Call_ErrorOccured;
call.CallStateChanged += Call_StateChanged;

call.Start();
}

Code 8 - Starting a call is similar to the softphone method

The MyClient class used a SIP account in several cased and it was mentioned that the server itself gives a SIP account to each clients. For this purpose you will need to define a new class.

You will need to know the usable SIP accounts

The SIPAccountPool class is a helping storage that registers the SIP accounts that the server can give to the clients. The server registers some SIP accounts when started and the SIPAccountPool object will handle these.

When a client connects to the server it gets an unused SIP account from the SIPAccountPool and when it disconnects, the account is put back into the pool to be able to given to another client. The client itself has no doubt about the SIP account it got and it cannot modify it in any ways. With this setting you can ensure that the webphone clients will work properly and they will use a valid and fully handled SIP account.

The SIPAccountPool class only contains two methods and a constructor. It has a property that is a Queue object that contains SIP accounts. The constructor only initializes this object.

The two methods in the SIP AccountPool class are GetSIPAccount and AddSIPAcount. The GetSIPAccount method will provide a SIP account to the connected client. It gets one account from the Queue object and returns with it. The account will be dequeued from the pool.

The AddSIPAcount method will add a SIP account to the pool Queue object. This method is called when the server fills up the pool when it starts and it is also called when a client disconnects and gives back the SIP account it got from the server.

The AddSIPaccount method only puts the parameter SIP account to the pool Queue.

The main server functionality will be implemented in the SIPGateway class

The server functionality is implemented in the SIPGateway class that is to be added to the project. This class looks similar to the one that was defined in case of the click-to-call webphone solution but it contains some new properties to be able to handle the softphone functionalities and the SIP accounts.

This webphone server will need the properties of a softphone and a SIPAccountPool that will store the SIP accounts the server can give to the clients. When the server starts and the OnStart method is called the softphone is initialized and the usable SIP accounts will be set (Code 9).

The server can write some information or notification on the console window then it initializes the basic properties.

In this sample the server will generate 18 different SIP accounts that can be registered with a cycle instruction. Note that you have to set your own proper SIP accounts you got from your PBX provider in order to have your webphone work.

When the softphone is created, the SoftPhoneFactory class sets the main settings of the PBX. Note that you need to set your PBX IP and port information in order to have your webphone work. If you do not know these data, please ask for them from your PBX provider.

Console.WriteLine("SIP Gateway started");

MyClients = new Dictionary<IClient, MyClient>();
softPhone = SoftPhoneFactory.CreateSoftPhone("192.168.113.6", 5060, 5800, 5060);

sipAccountPool = new SIPAccountPool();

for (int i = 80; i < 99; i++)
{
var name = "oz8" + i;
var sipAccount = new SIPAccount(true, name, name, name, name, "192.168.115.1", 5060);

sipAccountPool.AddSIPAcount(sipAccount);
}

streamService = GetService<IStreamService>();

Code 9 - This method runs in case of the server start

When a client connects to the server, the OnClientConnect method gets called (Code 10). The server gives a SIP account to the client or in case of no available SIP accounts it invokes the client's OnRegistrationStateChanged method with the parameter "Connection limit" that indicates that there is no SIP account at the moment.

If there is at least one available SIP account the client is initialized and put in the register Dictionary of the server.

Console.WriteLine("New client connected. Remote address: {0}", client.RemoteAddress);

var sipAccount = sipAccountPool.GetSIPAccount();

if (sipAccount == null)
{
client.InvokeMethod("OnRegistrationStateChanged", "Connection limit");
client.Close();
return;
}

var MyClient = new MyClient(client, streamService, softPhone, sipAccount);
MyClients.Add(client, MyClient);

MyClient.Register();

Code 10 - When a client connects to the server it gets a SIP account to use

When a client disconnects the server gets back the SIP account ant puts it back to the pool of available SIP accounts.

You will need to implement the basic events for the call that are Call, HangUp, Accept, Reject and the stream that are OnStreamPublishStart and OnStreamClose. These methods are really similar to the ones you used for the click-to-call webphone.

After having all these methods, you can start implementing the client-side application with the use of Silverlight.

You will need a new project for the Silverlight client

In order to get all the background support you will need to download and install Ozeki MediaGateway_SLClientSDK. This SDK provides the background support for the Silverlight webphone client applications.

Make sure you are using Silverlight 4 for your application. If you don't you can download and install it to your Visual Studio before starting.

The first step when implementing the webphone client is to create a new Silverlight application. You can use the File->New Project menu and choose Silverlight application from the list on the New Project window.

Your Silverlight project will be divided to two parts. One for the actual application and another for a testing html page where you can run the project. You will not have to do anything with the TestHTLM page though; you will only have to implement the application.

First step should be building a functional GUI

When building a graphical application, the first step is always about the GUI. You can build the exact look you want and after that you can write the code that will make the GUI work.

You can build the GUI on the Design tab using the GUI elements that are provided on the Toolbox floating panel. For a fully featured softphone application that supports voice calls, you will need a keypad and some notification labels and, of course, a textbox that shows the dialed number.

You can set the position and the size of the GUI elements with your mouse or you can use the Properties panel to set all the properties you want to change. After setting all the GUI elements, you can have a Silverlight softphone GUI like the one in Figure 2.


Figure 2 - This is a simple GUI sample for a Silverlight softphone

After having an impressive GUI, it is time to start implementing the functionality of the client-side solution, but at first, do not forget to register the Ozeki MediaGateway_SLClientSDK to your project.

Two brand new classes for state and error definition

The client application will contain three classes, two of what will be Enum definitions for call states and errors. These enums contain the flags that indicate the events that occur during a call.

The CallState enum has the following values:

  • Created - the call object has been created
  • Setup - the call has started
  • Error - an error occurred during the call initialization
  • Ringing - the softphone is ringing (incoming call)
  • RingingWithEarlyMedia - the softphone is ringing with early media
  • LocalHeld - the local call pary does not want to call for a while
  • RemoteHeld - the remot call party does not want to call for a while
  • InCall - the call paries are talking
  • Completed - the phone was hanged up
  • Rejected - the call has been rejected
  • Cancelled - the call has been cancelled
  • Busy - the remote party is busy

The CallError enum can have the following values:

  • NotFound - the called number has not been found
  • Unavailable - the callee is not available
  • UnknownError - some other error occurred

These two enums are used for flaging the messages that are sent to the server from the client and they are for indicating some state or error data that will be transferred to the other client too.

After defining these basic enum values, it is time to implement the client functionality itself. For this purpose you will need to work in the main class that is generated in connection with the GUI.

The softphone functionality is attached to the GUI buttons

The GUI buttons has to have the required functionality that must be attached to the buttons' Click event on the Event panel in Visual Studio. As you can use the same EventHandler method for all keypad buttons, it is recommended to write this method and attach it to all buttons on the GUI.

The numeric keypad buttons (including the * and # buttons) only write the proper character onto the textbox that shows the number to dial. These can have the same EventHandler method shown in Code 11.

private void btnNumpads_Click(object sender, RoutedEventArgs e)
{
    Button btn = sender as Button;
    if (btn != null)
    {
        txtDial.Text += btn.Content;
    }
}

Code 11 - The EventHandler method for all numeric keypad buttons

After writing this single method, you will need to attach it to all keypad buttons' Click event on the Design tab.

The Call and Stop Call buttons have their own well defined functionality that can be written in two separate methods.

The EventHandler for the Call button can be created by double clicking on the button on the Design tab. Code 12 shows how this method can be written.

The method checks the call state according to the CallState enum values and chooses the right functionality. In case of an incoming call, the Call button accepts the call while if there is no incoming call, it tries to start an outgoing one.

If the Silverlight does not have permission for using the microphone, it asks for permission before it can do anything about the call.

if (Microphone.GetPermissionToMicrophone())
{
ReleaseStreams();

switch (phoneState)
{
    case PhoneState.InRinging:
        connection.InvokeOnConnection("Accept");
        setPhoneState(PhoneState.InCall);
        break;
    case PhoneState.Ready:
        connection.InvokeOnConnection("Call", txtDial.Text);
        txtboxInfo.Text = "Outgoing call progress.";
        setPhoneState(PhoneState.OutRinging);
        break;
}
}
else
{
txtboxInfo.Text = "Please, add permission to access microphone.";
}

Code 12 - The default EventHandler for the Call button

The Stop Call button also has more than one functionalities. In case of an incoming call the Stop Call button can reject the call while in case of an established call it can hang up the phone. Code 13 shows the method that does this activity.

This method also decides on the phone states about the proper activity to perform.

switch (phoneState)
{
case PhoneState.InRinging:
    connection.InvokeOnConnection("Reject");
    setPhoneState(PhoneState.Ready);
    break;
case PhoneState.OutRinging:
case PhoneState.InCall:
    txtboxInfo.Text = "Call stop, ready to call.";
    connection.InvokeOnConnection("HangUp");
    setPhoneState(PhoneState.Ready);
    break;
}
txtDial.Text = "";

Code 13 - The EventHandler for the Stop Call button

The client application also has to implement the methods that can be invoked by the server. These are OnSetReadyStatus, OnCallRequest, OnInCall, OnCallErrorOccurred and OnCallStateChanged.

The OnInCall method defines the activity the client performs in case of a call. It has to start the microphone and the audioPlayer and it has to attach the sender and receiver objects to the proper tool (Code 14).

streamSender = new MediaStreamSender(connection);
microphone = Microphone.GetMicrophone();
streamSender.AttachMicrophone(microphone);
streamSender.Publish(publisStreamName);

streamReceiver = new MediaStreamReceiver(connection);
streamReceiver.AttachAudioPlayer(audioPlayer);
streamReceiver.Play(playStreamName);

Code 14 - The method the server invokes in case of a call

The other invoking methods mainly set the call status according to the events that occurred and the set the notification text on the GUI to inform the user about the actual event.

The constructor of the client class only establishes the connection to the server by giving the IP and port data as parameters (Code 15).

Note that you have to set your server IP address, port number and name in order to have your webphone work properly. Make sure this information are set to the same data as in the App.config XML file of the server.

InitializeComponent();
connection = new MediaConnection("localhost:4502/SIPGateway");
connection.ConnectionStateChanged += new EventHandler<GenericEventArgs<ConnectionState>>(connection_ConnectionStateChanged);
connection.Connect();

Code 15 - The client constructor establishes the connection

Now you have a fully featured Silverlight softphone application and if you set the IP addresses, ports, and SIP accounts properly, you can use it right now.

This articles shows how to develop a Silverlight softphone application with the support of Ozeki VoIP SIP SDK. If you followed the guide properly you already have your fully operational webphone solution that you can embed in your web site. Now it is time to take a step forward and explore the other outstanding features provided by Ozeki VoIP SIP SDK.

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 webphone development on licensing page

Related Pages

Operating system: Windows 10 Windows 8, Windows 7, Vista, 200x, XP
Development environment: Visual Studio 2012, Visual Studio 2010, Visual Studio 2008, Visual Studio 2005
Programming language: C#.NET
Supported .NET framework: .NET Framework 4.5, .NET Framework 4.0, .NET Framework 3.5 SP1
Software development kit: OZEKI VoIP SIP SDK (Download)
VoIP connection: 1 SIP account
System memory: 512 MB+
Free disk space: 100 MB+

Ozeki Cookie Policy
Ozeki Informatics Ltd uses cookies to provide you the best experience on this website. The further use of the website will be considered as an agreement to the use of cookies. For more information read this website.

Cookies are enabled You are browsing the optimized version of this website. For more information read this website.