Giter VIP home page Giter VIP logo

neventsocket's Introduction

NEventSocket

NuGet Badge MyGet Badge Join the chat at https://gitter.im/danbarua/NEventSocket

Windows / .NET Linux / Mono
Build status Build Status

NEventSocket is a FreeSwitch event socket client/server library for .Net 4.5.

Since I no longer work in the telecoms industry, this library is no longer maintained. There is a DotNetCore 3 fork of the project here: https://github.com/iamkinetic/NEventSocket

Installing Release builds

Package Manager Console: Install-Package NEventSocket

CommandLine: nuget install NEventSocket

Installing Pre-Release builds

NuGet v3 (VS2015): nuget install NEventSocket -PreRelease -Source "https://www.myget.org/F/neventsocket-prerelease/api/v3/index.json"

NuGet v2 (VS2012): nuget install NEventSocket -PreRelease -Source "https://www.myget.org/F/neventsocket-prerelease/api/v2"

Inbound Socket Client

An InboundSocket connects and authenticates to a FreeSwitch server (inbound from the point of view of FreeSwitch) and can listen for all events going on in the system and issue commands to control calls. You can use ReactiveExtensions to filter events using LINQ queries and extension methods. All methods are async and awaitable.

using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

using NEventSocket;
using NEventSocket.FreeSwitch;

using (var socket = await InboundSocket.Connect("localhost", 8021, "ClueCon"))
{
  var apiResponse = await socket.SendApi("status");
  Console.WriteLine(apiResponse.BodyText);

  //Tell FreeSwitch which events we are interested in
  await socket.SubscribeEvents(EventName.ChannelAnswer);

  //Handle events as they come in using Rx
  socket.ChannelEvents.Where(x => x.EventName == EventName.ChannelAnswer)
        .Subscribe(async x =>
            {
                Console.WriteLine("Channel Answer Event " +  x.UUID);

                //we have a channel id, now we can control it
                await socket.Play(x.UUID, "misc/8000/misc-freeswitch_is_state_of_the_art.wav");
            });

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();
}

Outbound Socket Server

An OutboundListener listens on a TCP port for socket connections (outbound from the point of view of FreeSwitch) when the FreeSwitch dialplan is setup to route calls to the EventSocket. An OutboundSocket receives events for one particular channel, the API is the same as for an InboundSocket, so you will need to pass in the channel UUID to issue commands for it.

Don't forget to use the async and full flags in your dialplan. async means that applications will not block (e.g. a bridge will block until the channel hangs up and completes the call) and full gives the socket access to the full EventSocket api (without this you will see -ERR Unknown Command responses)

<action application="socket" data="127.0.0.1:8084 async full"/>
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

using NEventSocket;
using NEventSocket.FreeSwitch;

using (var listener = new OutboundListener(8084))
{
  listener.Connections.Subscribe(
    async socket => {
      await socket.Connect();

      //after calling .Connect(), socket.ChannelData
      //is populated with all the headers and variables of the channel

      var uuid = socket.ChannelData.Headers[HeaderNames.UniqueId];
      Console.WriteLine("OutboundSocket connected for channel " + uuid);

      await socket.SubscribeEvents(EventName.ChannelHangup);

      socket.ChannelEvents
            .Where(x => x.EventName == EventName.ChannelHangup && x.UUID == uuid)
            .Take(1)
            .Subscribe(async x => {
                  Console.WriteLine("Hangup Detected on " + x.UUID);
                  await socket.Exit();
            });
      
      
      //if we use 'full' in our FS dialplan, we'll get events for ALL channels in FreeSwitch
      //this is not desirable here - so we'll filter in for our unique id only
      //cases where this is desirable is in the channel api where we want to catch other channels bridging to us
      await socket.Filter(HeaderNames.UniqueId, uuid);
      
      //tell FreeSwitch not to end the socket on hangup, we'll catch the hangup event and .Exit() ourselves
      await socket.Linger();
      
      await socket.ExecuteApplication(uuid, "answer");
      await socket.Play(uuid, "misc/8000/misc-freeswitch_is_state_of_the_art.wav");
      await socket.Hangup(uuid, HangupCause.NormalClearing);
    });

  listener.Start();

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();
}

Error Handling

NEventSocket makes a best effort to handle errors gracefully, there is one scenario that you do need to handle in your application code. In a realtime async application, there may be a situation where we are trying to write to a socket when FreeSwitch has already hung up and disconnected the socket. In this case, NEventSocket will throw a TaskCanceledException (Note incorrect spelling of Cancelled) which you can catch in order to do any clean up.

It's a good idea to wrap any IObservable.Subscribe(() => {}) callbacks in a try/catch block.

try {
  await socket.Connect();

  var uuid = socket.ChannelData.Headers[HeaderNames.UniqueId];
  Console.WriteLine("OutboundSocket connected for channel " + uuid);

  await socket.SubscribeEvents(EventName.Dtmf);

  socket.ChannelEvents.Where(x => x.UUID == uuid && x.EventName == EventName.Dtmf)
        .Subscribe(async e => {
          try {
            Console.WriteLine(e.Headers[HeaderNames.DtmfDigit]);
           //speak the number to the caller
            await client.Say(
                  uuid,
                  new SayOptions()
                  {
                    Text = e.Headers[HeaderNames.DtmfDigit],
                    Type = SayType.Number,
                    Method = SayMethod.Iterated
                    });
           }
           catch(TaskCanceledException ex){
            //channel hungup
           }
      ));
}
catch (TaskCanceledException ex) {
  //FreeSwitch disconnected, do any clean up here.
}

Channel API

Whilst the InboundSocket and OutboundSocket give you a close-to-the-metal experience with the EventSocket interface, the Channel API is a high level abstraction built on top of these. A Channel object maintains its own state by subscribing to events from FreeSwitch and allows us to control calls in a more object oriented manner without having to pass channel UUIDs around as strings.

Although the InboundSocket and OutboundSocket APIs are reasonably stable, the Channel API is a work in progress with the goal of providing a pleasant, easy to use, strongly-typed API on top of the EventSocket.

There is an in-depth example in the examples/Channels folder.

using System;
using System.Reactive.Linq;
using System.Threading.Tasks;

using NEventSocket;
using NEventSocket.Channels;
using NEventSocket.FreeSwitch;
using NEventSocket.Util;

using (var listener = new OutboundListener(8084))
{
  listener.Channels.Subscribe(
    async channel => {
      try
      {
          await channel.Answer();
          await channel.PlayFile("ivr/8000/ivr-call_being_transferred.wav");
          await channel.StartDetectingInbandDtmf();

          var bridgeOptions = 
                  new BridgeOptions()
                      {
                        IgnoreEarlyMedia = true,
                        RingBack =
                            "tone_stream://%(400,200,400,450);%(400,2000,400,450);loops=-1",
                        ContinueOnFail = true,
                        HangupAfterBridge = true,
                        TimeoutSeconds = 60,
                        //can get variables from a channel using the indexer
                        CallerIdName = channel["effective_caller_id_name"], 
                        CallerIdNumber = channel["effective_caller_id_number"],
                      };

          //attempt a bridge to user/1001, write to the console when it starts ringing
          await channel.BridgeTo("user/1001", 
                                  bridgeOptions,
                                  (evt) => Console.WriteLine("B-Leg is ringing..."))

          //channel.Bridge represents the bridge status
          if (!channel.Bridge.IsBridged)
          {
              //we can inspect the HangupCause to determine why it failed
              Console.WriteLine("Bridge Failed - {0}".Fmt(channel.Bridge.HangupCause));
              await channel.PlayFile("ivr/8000/ivr-call_rejected.wav");
              await channel.Hangup(HangupCause.NormalTemporaryFailure);
              return;
          }
              
          Console.WriteLine("Bridge success - {0}".Fmt(channel.Bridge.ResponseText));

          //the bridged channel maintains its own state
          //and handles a subset of full Channel operations
          channel.Bridge.Channel.HangupCallBack = 
            (e) => ColorConsole.WriteLine("Hangup Detected on B-Leg {0} {1}"
                  .Fmt(e.Headers[HeaderNames.CallerUniqueId],
                    e.Headers[HeaderNames.HangupCause]));

          //we'll listen out for the feature code #9
          //on the b-leg to do an attended transfer
          channel.Bridge.Channel.FeatureCodes("#")
            .Subscribe(async x =>
            {
              switch (x)
              {
                case "#9":
                  Console.WriteLine("Getting the extension to do an attended transfer to...");

                  //play a dial tone to the b-leg and get 4 digits to dial
                  var digits = await channel.Bridge.Channel.Read(
                                    new ReadOptions {
                                        MinDigits = 3,
                                        MaxDigits = 4, 
                                        Prompt = "tone_stream://%(10000,0,350,440)",
                                        TimeoutMs = 30000,
                                        Terminators = "#" });

                  if (digits.Result == ReadResultStatus.Success && digits.Digits.Length == 4)
                  {
                    await channel.Bridge.Channel
                      .PlayFile("ivr/8000/ivr-please_hold_while_party_contacted.wav");
                    
                    var xfer = await channel.Bridge.Channel
                      .AttendedTransfer("user/{0}".Fmt(digits));

                    //attended transfers are a work-in-progress at the moment
                    if (xfer.Status == AttendedTransferResultStatus.Failed)
                    {
                      if (xfer.HangupCause == HangupCause.CallRejected)
                      {
                          //we can play audio into the b-leg via the a-leg channel
                          await channel
                            .PlayFile("ivr/8000/ivr-call-rejected.wav", Leg.BLeg);
                      }
                      else if (xfer.HangupCause == HangupCause.NoUserResponse 
                                || xfer.HangupCause == HangupCause.NoAnswer)
                      {
                          //or we can play audio on the b-leg channel object
                          await channel.Bridge.Channel
                            .PlayFile("ivr/8000/ivr-no_user_response.wav");
                      }
                      else if (xfer.HangupCause == HangupCause.UserBusy)
                      {
                          await channel.Bridge.Channel
                            .PlayFile("ivr/8000/ivr-user_busy.wav");
                      }
                    }
                    else
                    {
                      //otherwise the a-leg is now connected to either
                      // 1) the c-leg
                      //    in this case, channel.Bridge.Channel is now the c-leg channel
                      // 2) the b-leg and the c-leg in a 3-way chat
                      //    in this case, if the b-leg hangs up, then channel.Bridge.Channel
                      //    will become the c-leg
                      await channel
                      .PlayFile("ivr/8000/ivr-call_being_transferred.wav", Leg.ALeg);
                    }
                  }
                break;
              }
            });
      }
      catch(TaskCanceledException)
      {
          Console.WriteLine("Channel {0} hungup".Fmt(channel.UUID));
      }
    }
    });

  listener.Start();

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();
}

neventsocket's People

Contributors

anber avatar danbarua avatar gitter-badger avatar pragmatrix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

neventsocket's Issues

How do I send uuid_transfer command?

I don't see a Transfer method on InboundSocket. I'm basically trying to convert an ongoing call into a conference, so that I can 'conference in' another person. I plan on doing it by transfering both legs into a conference room first. I'm assuming the specific 'Transfer()' function has not been implemented, but the workaround can be running a transfer application via another command? Thanks.

Also - how do I label this issue as a Question (since it's not a bug)?

ApiResponse: Is "-ERR no reply" an error?

-ERR no reply should probably not be treated as an error in ApiResponse. I'd suggest to set Success to true and add another property IsNoReply that indicates this specific response state.

Error on initialize socket

Hi
When user call FS and it open socket to manage the call , if call close befor init and calling Channels.Subscribe , it throw exception .
I figure out problem and trace source .
The errors occur in following classes :

  1. OutboundListener.Channels property . It Throw exception on "c.GetChannel().Result" .
    I put it in try cache and return null in exception and log the error, and check for null channel in callback . It fixed .
  2. Channel constructor . in Disposables subscribe . on "await eventSocket.Exit();" .
    I put it again in try cache . and log exception .
  3. In ObservableSocket . Observable.Defer on line "stream.ReadAsync(buffer, 0, buffer.Length);" .
    it throw error when socket closes before it . I put it on try cache and log error and also return null .

Now tell me , my works is OK or should change . I'm afraid of unexpected behaviors . So follow me in correct way , and also I use it in running application and the errors i mentioned closed the socket , and i should restart app . I'm also looking for exception handling which if socket close and dispose , it restart it self again .

sorry for my poor english

Hangup detection

I am having an issue where hang up is not being detected. I have added a class to your Examples project, based on your VoiceBlaster sample, which simply dials an internal station (VOIP softphone), plays a tone on answer, and then simply looks for a hang up. Hanging up the softphone seems to do nothing and I am at a loss. I don't know if I am doing something wrong, or if there is some issue detecting the hang up. If I just dial a number via the softphone and answer that call, then hang up the softphone, FreeSWITCH detects the hang up and ends the call, so I know that at least my basic setup seems ok. Any help would be greatly appreciated. Below is the class I added to the examples:

namespace NEventSocket.Examples.Examples
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using ColoredConsole;
    using Net.CommandLine;
    using NEventSocket.FreeSwitch;

    public class MyTest : ICommandLineTask, IDisposable
    {
        private int currentCallCount = 0;

        public async Task Run(CancellationToken cancellationToken)
        {
            var ourCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            using (var client = await InboundSocket.Connect())
            {
                Console.WriteLine("Authenticated!");

                await client.SubscribeEvents(EventName.ChannelHangup, EventName.BackgroundJob);

                client.Events.Where(x => x.EventName == EventName.ChannelHangup)
                    .Subscribe(x => { Console.WriteLine($"Hangup Detected : {x.GetVariable("ccs_station_id")} {x.HangupCause}"); });

                using (var listener = new OutboundListener(8084))
                {
                    listener.Connections.Subscribe(
                        async socket =>
                            {
                                try
                                {
                                    await socket.Connect();

                                    var uuid = socket.ChannelData.UUID;

                                    await socket.Filter(HeaderNames.UniqueId, uuid);

                                    Console.WriteLine($"OutboundSocket connected for channel {uuid} {socket.ChannelData.GetVariable("ccs_station_id")}");

                                    await socket.ExecuteApplication(uuid, "pre_answer");
                                    await socket.Play(uuid, "{loops=2}tone_stream://%(300,25,440)");
                                    await socket.ExecuteApplication(uuid, "answer");
                                }
                                catch (OperationCanceledException)
                                {
                                    //hangup - freeswitch disconnected from us
                                }
                            });

                    listener.Start();

                    var checkCallCount = new Task(
                        async () =>
                            {
                                try
                                {
                                    while (!ourCancellationToken.IsCancellationRequested)
                                    {
                                        var res = await client.SendApi("show calls count");
                                        Console.WriteLine("Current Calls Count " + Convert.ToInt32(res.BodyText.Split(' ')[0]));
                                        currentCallCount = Convert.ToInt32(res.BodyText.Split(' ')[0]);
                                        await Task.Delay(5000);
                                    }
                                }
                                catch (OperationCanceledException)
                                {
                                    //shutdown
                                }
                            });

                    checkCallCount.Start();

                    try
                    {
                        await RegisterAgent(client, "1001");
                    }
                    catch (OperationCanceledException)
                    {
                        //shutdown
                    }

                    ColorConsole.WriteLine("Press [Enter] to exit.".Green());
                    await Util.WaitForEnterKeyPress(cancellationToken);
                    ourCancellationToken.Cancel();

                    listener.Dispose();
                }
            }
        }

        private async Task RegisterAgent(InboundSocket client, string stationId)
        {
            Console.WriteLine("Registering agent : " + stationId);

            var originateOptions = new OriginateOptions
            {
                CallerIdNumber = "8560000000",
                CallerIdName = "My CID",
                HangupAfterBridge = false,
                IgnoreEarlyMedia = true,
                TimeoutSeconds = 20
            };

            originateOptions.ChannelVariables["ccs_station_id"] = stationId;

            var originateResult =
                                    await
                                    client.Originate(
                                        $"user/{stationId}",
                                        originateOptions,
                                        "socket",
                                        "127.0.0.1:8084 async full");

            if (!originateResult.Success)
            {
                Console.WriteLine($"Registration failed to initiate : {stationId} {originateResult.ResponseText}");
            }
            else
            {
                Console.WriteLine($"Agent successfully registered : {stationId}");
            }
        }

        public void Dispose()
        {
        }
    }
}

ArgumentNullException("logger") when disposing of ObservableSocket

I have a windows service that is constantly connecting to FreeSwitch and monitoring the servers. Unfortunately, when the FreeSwitch boxes fail over or are rebooted I keep hitting an ArgumentNullException in the GuardAgainstNullLogger method. I am getting logs out of log4net. It is only when it calls the dispose method that it fails and throws this error. Any ideas?

System.Reactive.Core

It looks like MS updated reactive library nuget package to 3.0. With this reference neventsocket project cannot be build. Any plans to support new version of reactive library?

No FILE NOT FOUND exception?

When I send a playback command to FreeSWITCH with a non-existing file as a parameter, I will see an error in the console (logging), but the task object has Exception = NULL after awaiting it and no exception is thrown.
I think it should throw an exception when trying to playback a file that does not exist.

OutboundListener event-lock

I'm trying to bridge a channel using an OutboundListener but am having, so far, not much luck. As soon as I pick up the ringing phone the channel is gone. While looking for a solution I came across this:

Note about async mode
If you are using async, you need to pay attention to potential race condition. The commands you send may not execute in sequential order. You may force the command to wait:

sendmsg 
call-command: set
execute-app-name: foo=bar\n\n
event-lock:true

Source

And:

Q: Can I bridge a call with an Outbound socket?
Yes you can, simple execute a 'Bridge'

sendmsg 
call-command: execute
execute-app-name: bridge
execute-app-arg: {ignore_early_media=true}sofia/gateway/myGW/177808
event-lock: true

The event-lock is key here, if you don't have it set, other events you might have sent can terminate the call, even if you've sent them before the bridge command. Remember that if you want to hangup the call when the bridge completes, either look for the event [CHANNEL_UNBRIDGE], or send a hangup event after the bridge.

Source

Looking at the bridge implementation it seems to me that the eventlock argument is not passed (e.g. defaults to false) to the ExecuteApplication method.

Could it be that this is my problem? If not, could you maybe show a simple example on how to bridge a channel with an OutboundListener?

I'll post my code as soon as I have created a stripped-down version to easily reproduce; may take a bit though 😉

Consider retiring SocketResponseTimeout

Related to #8
If the socket closes down or errors, then any message observables should complete. It's likely that this will happen before FreeSwitch fails to send a message in due time.
Will keep this ticket open and wait for feedback from users.

multiple concurrent Inbound calls

I am developing IVR application. I want to make multiple concurrent calls through gsm gateway. should i create multiple inbound socket instances to make concurrent calls or can i make concurrent calls through single socket instance and play audio files based on call id? could you please give any sample code?

Here is my sample code for make one call at time

using (var socket = await InboundSocket.Connect("localhost", 8021, "ClueCon"))
            {
                await socket.SubscribeEvents(EventName.ChannelHangupComplete, EventName.ChannelAnswer, EventName.Dtmf);

                List<String> mobileNos = new List<String>();
                mobileNos.Add("8940703144");
                mobileNos.Add("8754006462");

                foreach (var item in mobileNos)
                {
                    var originate = await socket.Originate("sofia/internal/" + item + "@192.168.1.10",
                    new OriginateOptions
                    {
                        CallerIdName = "HSS",
                        IgnoreEarlyMedia = true
                    });

                    if (originate.Success)
                    {
                        var uuid = originate.ChannelData.UUID;
                        var playOptions = new PlayGetDigitsOptions()
                        {
                            MinDigits = 1,
                            MaxDigits = 1,
                            MaxTries = 3,
                            TimeoutMs = 6000,
                            PromptAudioFile = IvrMenuFilePath,
                            ValidDigits = "1,2,3"
                        };
                        playOptions.BadInputAudioFile = InvalidPhraseFilePath;
                        var playGetDigitsResult = await socket.PlayGetDigits(uuid, playOptions);
                    }
                }
            }

BridgeTo blocking events until channel hangup.

DEBUG 15/02/03 20:00:54 079 [7] - NEventSocket.Channels.Channel:Channel 576fd745-b4af-49ef-8048-4c677f0864d2 is attempting a bridge to user/oper_1
DEBUG 15/02/03 20:00:54 104 [7] - NEventSocket.OutboundSocket:Sending [sendmsg 576fd745-b4af-49ef-8048-4c677f0864d2
Event-UUID: 659bb4bd-316f-4094-8667-b901fe02cb88
call-command: execute
execute-app-name: bridge
content-type: text/plain
content-length: 68

{origination_uuid='d126f06d-9bc7-4b80-b1f4-b88c0910d293'}user/oper_1
]

A-leg hangup here

DEBUG 15/02/03 20:01:08 763 [26] - NEventSocket.OutboundSocket:Messages Received [command/reply].
DEBUG 15/02/03 20:01:08 775 [26] - NEventSocket.OutboundSocket:CommandReply received [+OK] for [sendmsg 576fd745-b4af-49ef-8048-4c677f0864d2
Event-UUID: 659bb4bd-316f-4094-8667-b901fe02cb88
call-command: execute
execute-app-name: bridge
content-type: text/plain
content-length: 68

{origination_uuid='d126f06d-9bc7-4b80-b1f4-b88c0910d293'}user/oper_1
]
DEBUG 15/02/03 20:01:08 805 [26] - NEventSocket.OutboundSocket:Messages Received [text/event-plain].
DEBUG 15/02/03 20:01:08 811 [26] - NEventSocket.OutboundSocket:Events Received [d126f06d-9bc7-4b80-b1f4-b88c0910d293] [ChannelCreate]

FreeSWITCH version: 1.4.15~64bit ( 64bit)

Serialization instances of NEventSocket.FreeSwitch classes

I need to send instances of classes like OriginateOptions and BridgeOptions over Akka, but their don't serialize completely. I see two way:

  1. implement ISerializable;
  2. make parameters public like ChannelVariables.

P.S. Sorry for my english :)

First connection fails if a logger (in my case NLog) is found

I took this code from your repo, just added NLog package. When Connect method is called, an exception is thrown (System.TimeoutException, Message = "No Auth Request received within the specified timeout of 00:00:05.")
If I remove NLog it works just fine.
Sometimes if I call any log method befor calling Connect it also works.

I tested this behavior on 2 computers.
VS 2015, .NET 4.5 / 4.5.1 / 4.5.2

public class Program
{
    static void Main(string[] args)
    {
        ApiTest();
        Console.ReadLine();
    }

    private static async void ApiTest()
    {
        using (var client = await InboundSocket.Connect("192.168.21.100", 8021, "mypassword"))
        {
            Console.WriteLine((await client.SendApi("status")));
            Console.WriteLine((await client.SendApi("blah")));
            Console.WriteLine((await client.SendApi("status")));
        }
    }

I thought it might be something wrong with the time needed to initialize the logger for the first time, that is why I created dummy logger with static ctor with Thread.Sleep(2000) inside, but with this one it works just fine.

I hope this explanation is enough and maybe you can help me fixing this ;)

Dispose issue

Using the DtmfTest() merthod as an example, if I add

await Task.Delay(1000);

right after the InboundSocket.Connect() call, then when the Task.Deplay call begins, the log shows

2016-02-08 04:16:22 TID[21] TRACE Buffer Read Observable Completed 0 chunks left.

and any additional calls fail because "received" is disposed and null.

Is this not a supported scenario? The application I am trying to use the library with involves opening an inbound socket and subscribing to events then just sleeping until events are received on the socket or an external trigger requires an origination call to be made. In this scenario, the socket opens and authenticates, then immediately dies (which I am simulating with the Task.Delay() call in the sample application.)

TRACEOBJECTPOOLLEAKS_BEGIN log message?

I'm slowly getting up to speed using the library. During some initial testing, I've come across the following error message, which happens to pop up quite often:

TRACEOBJECTPOOLLEAKS_BEGIN
Pool detected potential leaking of System.Text.StringBuilder.

Is it a known issue?
I was basically hanging up a SIP call using the following socket message:

await _socket.Hangup(_callUuid);

The hangup worked, but I did get those potential leak warnings. Here's the complete log:

NLOG: EventSocket initialized
NLOG: NEventSocket.InboundSocket Worker Thread 1 started
NLOG: Messages Received [auth/request].
NLOG: Received Auth Request
NLOG: Sending [auth ClueCon]
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK accepted] for [auth ClueCon]
NLOG: InboundSocket authentication succeeded.
NLOG: Sending [api status]
NLOG: Messages Received [api/response].
NLOG: ApiResponse received [UP 0 years, 0 days, 4 hours, 30 minutes, 13 seconds, 314 milliseconds, 867 microsecondsFreeSWITCH (Version 1.7.0  64bit) is ready23 session(s) since startup2 session(s) - peak 4, last 5min 2 0 session(s) per Sec out of max 30, peak 2, last 5min 0 1000 session(s) maxmin idle cpu 0.00/96.26] for [status]
UP 0 years, 0 days, 4 hours, 30 minutes, 13 seconds, 314 milliseconds, 867 microseconds
FreeSWITCH (Version 1.7.0  64bit) is ready
23 session(s) since startup
2 session(s) - peak 4, last 5min 2 
0 session(s) per Sec out of max 30, peak 2, last 5min 0 
1000 session(s) max
min idle cpu 0.00/96.26

NLOG: Sending [event plain CHANNEL_ANSWER]
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK event listener enabled plain] for [event plain CHANNEL_ANSWER]
NLOG: Messages Received [text/event-plain].
Channel Answer Event 667c1091-c7fe-4657-83a7-9c82c2c6f490
NLOG: Messages Received [text/event-plain].
Channel Answer Event 44b1b7c2-ccb9-4d50-8955-f63a04cd39e1
NLOG: Sending [sendmsg 44b1b7c2-ccb9-4d50-8955-f63a04cd39e1
call-command: hangup
hangup-cause: NORMAL_CLEARING]
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK] for [sendmsg 44b1b7c2-ccb9-4d50-8955-f63a04cd39e1
call-command: hangup
hangup-cause: NORMAL_CLEARING]
TRACEOBJECTPOOLLEAKS_BEGIN
Pool detected potential leaking of System.Text.StringBuilder. 
 Location of the leak: 
    at NEventSocket.Util.ObjectPooling.ObjectPool`1.Allocate()
   at NEventSocket.Util.ObjectPooling.SharedPoolExtensions.AllocateAndClear(ObjectPool`1 pool)
   at NEventSocket.Util.ObjectPooling.StringBuilderPool.Allocate()
   at NEventSocket.Sockets.Parser..ctor()
   at NEventSocket.Sockets.EventSocket.<>c.<.ctor>b__7_1()
   at NEventSocket.Util.ObservableExtensions.<>c__DisplayClass0_1`2.<AggregateUntil>b__1(TSource value)
   at System.Reactive.AnonymousSafeObserver`1.OnNext(T value)
   at System.Reactive.Linq.ObservableImpl.SelectMany`2.NoSelectorImpl.OnNext(TSource value)
   at System.Reactive.Linq.ObservableImpl.Defer`1._.OnNext(TValue value)
   at System.Reactive.Linq.ObservableImpl.AsObservable`1._.OnNext(TSource value)
   at System.Reactive.Subjects.Subject`1.OnNext(T value)
   at NEventSocket.Sockets.ObservableSocket.<>c__DisplayClass10_0.<<-ctor>b__1>d.MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(Object stateMachine)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(Action action, Boolean allowInlining, Task& currentTask)
   at System.Threading.Tasks.Task.FinishContinuations()
   at System.Threading.Tasks.Task.FinishStageThree()
   at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.CompleteFromAsyncResult(IAsyncResult asyncResult)
   at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
   at System.Net.ContextAwareResult.CompleteCallback(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.ContextAwareResult.Complete(IntPtr userToken)
   at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
   at System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
 TRACEOBJECTPOOLLEAKS_END
TRACEOBJECTPOOLLEAKS_BEGIN
Pool detected potential leaking of System.Text.StringBuilder. 
 Location of the leak: 
    at NEventSocket.Util.ObjectPooling.ObjectPool`1.Allocate()
   at NEventSocket.Util.ObjectPooling.SharedPoolExtensions.AllocateAndClear(ObjectPool`1 pool)
   at NEventSocket.Util.ObjectPooling.StringBuilderPool.Allocate()
   at NEventSocket.Sockets.Parser..ctor()
   at NEventSocket.Sockets.EventSocket.<>c.<.ctor>b__7_1()
   at NEventSocket.Util.ObservableExtensions.<>c__DisplayClass0_1`2.<AggregateUntil>b__1(TSource value)
   at System.Reactive.AnonymousSafeObserver`1.OnNext(T value)
   at System.Reactive.Linq.ObservableImpl.SelectMany`2.NoSelectorImpl.OnNext(TSource value)
   at System.Reactive.Linq.ObservableImpl.Defer`1._.OnNext(TValue value)
   at System.Reactive.Linq.ObservableImpl.AsObservable`1._.OnNext(TSource value)
   at System.Reactive.Subjects.Subject`1.OnNext(T value)
   at NEventSocket.Sockets.ObservableSocket.<>c__DisplayClass10_0.<<-ctor>b__1>d.MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(Object stateMachine)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
   at System.Threading.Tasks.AwaitTaskContinuation.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
 TRACEOBJECTPOOLLEAKS_END
TRACEOBJECTPOOLLEAKS_BEGIN
Pool detected potential leaking of System.Text.StringBuilder. 
 Location of the leak: 
    at NEventSocket.Util.ObjectPooling.ObjectPool`1.Allocate()
   at NEventSocket.Util.ObjectPooling.SharedPoolExtensions.AllocateAndClear(ObjectPool`1 pool)
   at NEventSocket.Util.ObjectPooling.StringBuilderPool.Allocate()
   at NEventSocket.Sockets.Parser..ctor()
   at NEventSocket.Sockets.EventSocket.<>c.<.ctor>b__7_1()
   at NEventSocket.Util.ObservableExtensions.<>c__DisplayClass0_1`2.<AggregateUntil>b__1(TSource value)
   at System.Reactive.AnonymousSafeObserver`1.OnNext(T value)
   at System.Reactive.Linq.ObservableImpl.SelectMany`2.NoSelectorImpl.OnNext(TSource value)
   at System.Reactive.Linq.ObservableImpl.Defer`1._.OnNext(TValue value)
   at System.Reactive.Linq.ObservableImpl.AsObservable`1._.OnNext(TSource value)
   at System.Reactive.Subjects.Subject`1.OnNext(T value)
   at NEventSocket.Sockets.ObservableSocket.<>c__DisplayClass10_0.<<-ctor>b__1>d.MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(Object stateMachine)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(Action action, Boolean allowInlining, Task& currentTask)
   at System.Threading.Tasks.Task.FinishContinuations()
   at System.Threading.Tasks.Task.FinishStageThree()
   at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.CompleteFromAsyncResult(IAsyncResult asyncResult)
   at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
   at System.Net.ContextAwareResult.CompleteCallback(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.ContextAwareResult.Complete(IntPtr userToken)
   at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
   at System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
 TRACEOBJECTPOOLLEAKS_END
The thread 0x1af0 has exited with code 0 (0x0).
NLOG: Messages Received [text/event-plain].
Channel Answer Event 32aad671-f9d3-447e-b705-4542bf2d85bb
NLOG: Messages Received [text/event-plain].
Channel Answer Event 9fb3b24a-acfa-4df5-9312-a6f2bbb77bfc
NLOG: Sending [sendmsg 9fb3b24a-acfa-4df5-9312-a6f2bbb77bfc
call-command: hangup
hangup-cause: NORMAL_CLEARING]
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK] for [sendmsg 9fb3b24a-acfa-4df5-9312-a6f2bbb77bfc
call-command: hangup
hangup-cause: NORMAL_CLEARING]
NLOG: Messages Received [text/event-plain].
NLOG: Messages Received [text/event-plain].
Channel Answer Event 3c2fa28a-1986-493e-94da-89b93b9f596d
Channel Answer Event f36f7863-bee6-4aa6-ae2f-cf3a4cb84c8c
NLOG: Sending [sendmsg f36f7863-bee6-4aa6-ae2f-cf3a4cb84c8c
call-command: hangup
hangup-cause: NORMAL_CLEARING]
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK] for [sendmsg f36f7863-bee6-4aa6-ae2f-cf3a4cb84c8c
call-command: hangup
hangup-cause: NORMAL_CLEARING]
The program '[12788] FsSockets.Gui.Wpf.vshost.exe' has exited with code -1 (0xffffffff).

Correction(s) for README?

The Outbound Socket Server example in the README has the following code:

socket.Events.Where(x => x.EventName == EventName.ChannelHangup)
                    .Take(1)
                    .Subscribe(x => {
                          Console.WriteLine("Hangup Detected on " + x.UUID);
                          socket.Exit();
                      });

It took me a while to figure out why, when I set up two calls, both hung up if I hung up either of them. In the end the solution couldn't be simpler but, as said, it took me a while to figure out what it was. I think the code should read:

socket.Events.Where(x => x.EventName == EventName.ChannelHangup && x.UUID == uuid)
                    .Take(1)
                    .Subscribe(x => {
                          Console.WriteLine("Hangup Detected on " + x.UUID);
                          socket.Exit();
                      });

Notice the Where clause where I added && x.UUID == uuid. (Maybe use a string.CompareOrdinal or equivalent, but you get the idea).

Also: the "await await" in await await socket.SubscribeEvents(); should, I guess, read: await Task.Run(() => socket.SubscribeEvents());. If I use "await await" the compiler says: Cannot await void on that particular line and also says Cannot convert lambda expression to type 'System.IObserver<NEventSocket.OutboundSocket> because it is not a delegate type*" on the async socket => line.

Please correct me if I'm wrong.

using InBoundSocket auto outcall problem.

here is the main code.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'
// using static socket.
public static InboundSocket socket;

static void Main(string[] args)
{

socket = await InboundSocket.Connect("127.0.0.1", 8021, "ClueCon", 0);

//subscribe events......
socket.Events.Where(x => x.EventName == EventName.ChannelAnswer).Subscribe(async y =>
            {
            });

}

//in thread auto out call
while (true)
{
.....

InBoundAutoCall(list_ky[j], m_num.Tele, m_num.Id, op);

Thread.Sleep(50);
.....

}

public static async void InBoundAutoCall(string callerno, string tele, int pool_id, OriginateOptions op)
{
try
{
op.CallerIdNumber = callerno;
op.CallerIdName = callerno;
op.HangupAfterBridge = true;
op.TimeoutSeconds = 20;
op.ReturnRingReady = true;
op.ChannelVariables.Add("callerno", callerno);
op.ChannelVariables.Add("calledno", tele);

    var result = await socket.Originate("sofia/gateway/" + callerno + "/" + tele, op);
    if (result.Success)
    {

    }
    else
    {

    }

}
catch (Exception ex)
{

}

}`

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

When the number of concurrent calls, at around 100, there will be a socket problem. Originate returns false. Then I look at the number of freeswitch channels, but the number of channels has been 0. After we close the program, reopen the socket connection, socket can work for a while, but once the amount reached 100 or so, it will appear the above situation.Is something error with us?

regards.

Async hanging

Running the KitchenSink demo, with the DtmfTest() method, my socket connects fine and the "await originate" call triggers a dial/park on my extension, but the originate call never returns- it hangs on the await. Any thoughts as to why this would be? I am running against the most recent dev build of FreeSWITCH (1.7) on Windows.

Any help would be greatly appreciated.

No response received from Play method EventSocket class

When Play method called just after hang up occurs then there is no response from method.

I've found the following log for this call.

2015-03-07 15:40:26.9263|DEBUG|NEventSocket.InboundSocket|CommandReply received
[-ERR invalid session id [c1cdaeae-ebb0-4f3f-8f75-0f673bfbc046]] for [sendmsg c1
cdaeae-ebb0-4f3f-8f75-0f673bfbc046
Event-UUID: 0cbc2a67-8591-49bc-8f64-abe414744c4f
call-command: execute
execute-app-name: playback
content-type: text/plain
content-length: 29

I think there is a problem in ExecuteApplication method. it only returns response after receiving ChannelExecuteComplete event.

NuGet Package

Todo: throw it up on NuGet
Consider breaking Channels out into a separate package.

ChannelState Enum

Can you please add Reporting to ChannelState enum in Channelstate.cs

Best regards, Gregor

Long-running applications block new Outbound connections

In scriptcs, where async/await is not available, calling .Wait() on a long-running dial plan application such as conference or refer in the connections observable subscription blocks new connections.

Example:

using (var listener = new OutboundListener(8084))
{
  var sub = listener.Connections.SubscribeOn(TaskPoolScheduler.Default).Subscribe(
    socket => {
      socket.Connect().Wait();
      var uuid = socket.ChannelData.Headers[HeaderNames.UniqueId];

      socket.SubscribeEvents().Wait();
      socket.ExecuteApplication(uuid, "answer").Wait();
      socket.ExecuteApplication(uuid, "conference", "test+1234").Wait(); //blocks
      //socket.ExecuteApplication(uuid, "conference", "test+1234"); //does not block
    });

  listener.Start();

  Console.WriteLine("Press [Enter] to exit.");
  Console.ReadLine();

  sub.Dispose();
}

Event Subscriptions

Currently an EventSocket subscribes to a few events for convenience:

private readonly HashSet<EventName> subscribedEvents = new HashSet<EventName>()
{
 EventName.ChannelExecuteComplete, 
 EventName.BackgroundJob, 
 EventName.ChannelHangup, 
 EventName.ChannelAnswer, 
 EventName.ChannelProgress, 
 EventName.ChannelProgressMedia, 
 EventName.ChannelBridge, 
 EventName.ChannelUnbridge
};

In high-traffic/high-performance scenarios, users may wish to subscribe to only the minimum events.

Requirement: an EventSocket should subscribe to events lazily as and when they are required, for example, when I do an Originate it should, if necessary, subscribe to the BackgroundJob and ChannelExecuteComplete events if not already done so.

Please update LibLog?

Hi!

I would kindly like to request you update LibLog so that I can silcence logging from NEventSocket.

I took a stab at it myself but the ColouredConsoleLogger has since been removed; I tried adding it back in but all sorts of other problems arose and, unfortunately, I don't have the time right now (not that I expect you to fix this anytime soon!). I will try again later and submit a PR if I get around to it.

Ship v2

Runnning in production at $dayJob for some time now, seems stable enough.

Inbound

Hi!
Tried NEventSocket and I am amazed. It is just that on Inbound connection I get no subscribe events. Tried with simple example on read me, but I cannot get any event. Otherwise sending commands works ok.

Can you please tell me how should I found out what is wrong?

BridgeTo hangs when called multiple times

I am able to reproduce this issue against the stable and latest codebases. Open an inbound socket and originate to a phone. Using Channel API, get channel for successfully originated call. Wait 10 seconds and call BridgeTo to bridge to a different phone number. Bridge is successful. Hangup bridged phone, and detect the hangup in Channel API. Wait 10 seconds and call BridgeTo to bridge to the phone number again. Bridge is successful. Wait 10 seconds and call BridgeTo a 3rd time. BridgeTo hangs and never returns, fs_cli shows no indication that it received the bridge command. Channel remains hung until you hangup the originally originated call, then BridgeTo returns and fs_cli shows the bridge command failing (obviously) because of the hangup. I have tried this countless times and it always hangs on the 3rd BridgeTo. I also tried setting the event-lock option on the bridge (as recommended by the FS docs for an outbound socket bridge, but it made no difference. Also, my original version of the code used only the inbound socket and no Channel API, but I had the same problem (which prompted me to try outbound sockets/Channel API).

This is a showstopper for me and I am at a loss, not being very fluent in the reactive extensions (I can only debug so far before I get in over my head). Any help is greatly appreciated.

Below is a console app to reproduce (.NET Core console app)- obviously substitute your own FS config and phone numbers.

Dial Plan:

<extension name="bridge_test">
  <condition field="destination_number" expression="^5557$">
      <action application="set" data="park_after_bridge=true" />
      <action application="pre_answer" />
      <action application="playback" data="{loops=2}tone_stream://%(300,25,440)" />
      <action application="answer" />
      <action application="socket" data="127.0.0.1:8084 async full" />
  </condition>
</extension>

project.json:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "NETStandard.Library": "1.6.0",
    "NEventSocket": "1.1.0",
    "Rx-Main": "2.2.5"
  },

  "frameworks": {
    "net452": {
    }
  }
}

Program.cs:

using NEventSocket;
using NEventSocket.Channels;
using NEventSocket.FreeSwitch;
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FSBridgeTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                var cancellationTokenSource = new CancellationTokenSource();
                Console.WriteLine("Starting bridge test- hit any key abort...");
                var task = RunBridgeTest(cancellationTokenSource.Token);
                Console.ReadKey();
                Console.WriteLine("Shutting down...");
                cancellationTokenSource.Cancel();
                Console.WriteLine("Shutdown complete.");
                task.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                Console.WriteLine("Hit any key exit...");
                Console.ReadKey();
            }
        }

        static async Task<bool> BridgeToPhone(Channel station, string phone)
        {
            var lineUUID = Guid.NewGuid().ToString("N");

            var bridgeOptions = new BridgeOptions
            {
                CallerIdNumber = "1003",
                CallerIdName = "Bridge Test",
                HangupAfterBridge = true,
                IgnoreEarlyMedia = false,
                TimeoutSeconds = 30,
                UUID = lineUUID
            };

            string endpoint = $"sofia/gateway/my-gateway/{phone}";

            Console.WriteLine($"Bridging {phone} to station channel {station.UUID}...");

            await station.BridgeTo(
                endpoint,
                bridgeOptions);

            if (!station.IsBridged)
            {
                Console.WriteLine($"Bridge failed.");
                return false;
            }
            else
            {
                Console.WriteLine($"Bridge successful.");
                return true;
            }
        }

        static Task RunBridgeTest(CancellationToken token)
        {
            var bridgeTestCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);

            return Task.Run(async () =>
            {
                try
                {
                    Console.WriteLine("Bridge test starting...");

                    using (var client = await InboundSocket.Connect())
                    {
                        using (var listener = new OutboundListener(8084))
                        {
                            listener.Channels.Subscribe(
                                async station =>
                                {
                                    try
                                    {
                                        Console.WriteLine($"Outbound socket connected for station channel {station?.UUID}.");

                                        station.HangupCallBack = (evt) =>
                                        {
                                            var hangupCause = evt.HangupCause?.ToString() ?? "Unknown";
                                            Console.WriteLine($"Station hangup detected for station channel {station?.UUID}- {hangupCause}.");
                                        };

                                        station.BridgedChannels.Subscribe(
                                            line =>
                                            {
                                                try
                                                {
                                                    Console.WriteLine($"Outbound socket connected for line channel {line?.UUID}.");

                                                    // Handle line hangup.
                                                    line.HangupCallBack = async (evt) =>
                                                        {
                                                            var hangupCause = evt.HangupCause?.ToString() ?? "Unknown";
                                                            Console.WriteLine($"Line hangup detected for line channel {line?.UUID}- {hangupCause}.");

                                                            await Task.Delay(10000);
                                                            await BridgeToPhone(station, "12155551212"); // Your cellphone here
                                                        };
                                                }
                                                catch (OperationCanceledException)
                                                {
                                                    Console.WriteLine($"Outbound socket disconnected for line channel {line?.UUID}.");
                                                }
                                            });

                                        await Task.Delay(10000);
                                        await BridgeToPhone(station, "12155551212"); // Your cellphone here.
                                    }
                                    catch (OperationCanceledException)
                                    {
                                        Console.WriteLine($"Outbound socket disconnected for station channel {station?.UUID}.");
                                    }
                                });

                            listener.Start();

                            var stationUUID = Guid.NewGuid().ToString("N");
                            var originateOptions = new OriginateOptions
                            {
                                CallerIdNumber = "1003",
                                CallerIdName = $"Station 1003",
                                HangupAfterBridge = false,
                                TimeoutSeconds = 20,
                                UUID = stationUUID
                            };

                            Console.WriteLine($"Originating to station channel {stationUUID}.");
                            var originate = await client.Originate(
                                "user/1003",
                                "5557",
                                "XML",
                                "default",
                                originateOptions);

                            if (!originate.Success)
                            {
                                var hangupCause = originate.HangupCause?.ToString() ?? "Unknown";
                                throw new Exception($"Originate failed: {hangupCause}");
                            }

                            Console.WriteLine($"Origination to station channel {stationUUID} complete.");

                            await Task.Delay(Timeout.Infinite, bridgeTestCancellationTokenSource.Token).ConfigureAwait(false);
                        }
                    }
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                Console.WriteLine("Bridge test complete.");
            });
        }
    }
}

Not all CHANNEL_HANGUP_COMPLETE are received

This is probably more of a FS question but might be related to NEventSocket. Is it possible not to receive all the CHANNEL_HANGUP_COMPLETE events when subscribing using goSocket.Events.Where(Function(x) x.EventName = EventName.ChannelHangupComplete)? Basically we have a windows service, which continually dials records from a DB as they come in. It updates them when we receive CHANNEL_HANGUP_COMPLETE by checking for the variable_uuid, which is a record id that we are sending via bgapi string.

So we send a bunch of bgapi strings using socket.SendCommand() up to specified limit as to not overload FS. We store the record ID in the list and it gets removed when we receive a response from FS. Some of those variable_uuid do not come in and we have been trying a bunch of solutions, including NEventSocket.

Make PlayGetDigitsOptions.digitsRegex public

I would like to set a regex for the accepted dtmf input, so I can set a certain logic, for instance where the code has to begin with either a 0 or a 1 and then some numbers and then ends with a 9 or a 0.
I have now used reflection to set the private field digitsRegex, which works as expected, but it would be nicer if the field was just public to begin with.

Referenced NuGet packages

This is just a suggestion and it would make it a bit easier (for me) to contribute. As I can see, the CI scripts run an explicit NuGet restore and Visual Studio 2015 is now also able to restore the NuGet packages automatically, so I would suggest to remove them from the repository. I am happy to provide a pull request, if this is acceptable.

no auth request received - if delay in socket connection

"No Auth Request received within the specified timeout of 00:00:05" exception thrown when using NLog with database logging. If i remove NLog then everything works fine.

var socket = new InboundSocket(host, port, timeout);

await
socket.Messages.Where(x => x.ContentType == ContentTypes.AuthRequest)
.Take(1)
.Timeout(
socket.ResponseTimeOut,
Observable.Throw(
new TimeoutException(
"No Auth Request received within the specified timeout of {0}.".Fmt(socket.ResponseTimeOut))))
.Do(_ => Log.Trace(() => "Received Auth Request"), ex => Log.ErrorException("Error waiting for AuthRequest.", ex))
.ToTask();

In these statements, If there is no delay in execution between these two statements it just works fine. and if there is a delay executing the second statement then it always fails.

for example, if we add the "System.Threading.Thread.Sleep(50);" in between the above two statements then second statement always fails and throws "No Auth Request received within the specified timeout of 00:00:05" exception.

Channels: Make bridging more reactive

The bridging api is a bit clunky at the moment:

await channel.BridgeTo(
  "user/1003", //destination
  bridgeOptions, //options
  (e) => ColorConsole.WriteLine("Bridge Progress Ringing...".DarkGreen()) // optionally notify on ringing
);

if (!channel.Bridge.IsBridged)
{
  // handle failure
}
else
{
  channel.Bridge.Channel.DoStuff();
}

In particular, we need a better way to handle externally initiated bridges, possibly via attended transfer, or maybe via a command from another EventSocket client.

For example, my use case is this:

When a channel is bridged to another channel,
I need to subscribe to Feature Codes on the b-leg (agent side) channel
So that I can pause/resume recording and initiate attended transfers

Give I have transferred the caller to another agent
When the caller is connected
Then the Feature Codes should be enabled on that agent's channel

The use of the word When in the above example implies that we can and should be handling this in a more functionally reactive manner. Instead of interrogating the Channel.Bridge status object, we can follow Tell Don't Ask by letting the Channel tell us when we have been bridged.

The proposed API could look like this:

channel.Bridge.Subscribe(async bridgedChannel =>
{
  bridgedChannel.DoStuff();
});

await channel.BridgeTo("destination");

So that's the happy path, nice and clean.

We need some way to notify of failure, and there are a few options for this.

  1. Produce an exception in the Channel.Bridge observable
channel.Bridge.Subscribe(
  async bridgedChannel => { bridgedChannel.DoStuff(); },
  ex => { //handle ex.HangupCause etc }
);
  1. Throw an exception in the Channel.BridgeTo Task:

csharp
try
{
await channel.BridgeTo("destination");
}
catch(BridgeFailedException ex)
{
//handle ex.HangupCause etc
}


I am not such a fan of (2) and (3) as currently, the only error handling that consumers of the library need to be aware of is `OperationCanceledException`, and the aim of this library is to allow people to write telephony applications with nice clean C# code. I do not want to be littering my code with ``try/catch`  any more than the bare minimum.

3) Expose `LastBridgeHangupCause` on the  Channel for interrogation, so the proposed api would look like this:

``` csharp
channel.Bridge.Subscribe(
  async bridgedChannel => { bridgedChannel.DoStuff(); }
);

await channel.BridgeTo("destination");
if (!channel.IsBridged)
{
   Console.WriteLine(channel.LastBridgeHangupCause);
  //otherwise success is handled in the Subscription callback
}

socket disconnect

hi:
when I use this command"sofia status profile internal reg", socket will be disconnect.

var socket = await InboundSocket.Connect(freeswitchAddr, freeswitchESPort, freeswitchESPassword);
socket .Disposed += socket _Disposed;
socket .SendApi("sofia status profile internal reg") //when run finish this ,socket _Disposed will be trigger.
sorry,my english is not good。…^~^

ApiResponse.ErrorMessage returns a trailing "\n"

I think the returned ErrorMessage should be either trimmed, or this behavior should be documented.

For example uuid_break [channel] returns -ERR no reply\n which gets converted to no reply\n.

ChannelState

CchannelState variable can be null. Header doesn't have value in CUSTOM events
Can you plese set ChannelState property to nullable like AnswerState.

Thank you!

All calls return HangupCause.UnallocatedNumber

We have another issue. We managed to run our service on another freeswitch server and we are getting HangupCause.UnallocatedNumber for all calls. This is probably freeswitch setup issue but was curious if guys have seen this before.

Thanks,

BridgedChannels and OtherLeg

Is there a reason why BridgedChannels/OtherLeg are not updated appropriately (i.e. remove channel from BridgedChannels/set OtherLeg to null) when a channel is unbridged? In my application, I have a channel generated from an origination to a user, and this channel remains connected indefinitely. While connected, the channel will periodically call BridgeTo to initiate a bridge with an external phone number allowing the user to handle the call. When the call ends (i.e. the external phone hangs up), BridgeTo is called again to connect with a different external phone. This goes on indefinitely without the original user channel hanging up. In this scenario, BridgedChannels will just grow continually, and OtherLeg remains populated even when there is no longer an active bridge. I am really only running into an issue bevcause I am throw an exception if OtherLeg is not null when the user attempts to start a new bridge (since I incorrectly assume one is already in progress). I could probably work around this, but the perpetually growing BridgedChannels concerns me. Wont this also prevent those previously bridged channels from getting garbage collected?

BridgeTo does not return BridgeResult

Is there a reason that the BridgeTo method swallows the result of the bridge instead of returning it? This makes getting the results of the bridge attempt challenging.

ChannelHangupComplete event

I would like to propose that a HangupCompleteCallback handler be added to the Channel API, moving the Dispose() call here from the HangupCallback() handler. The reason being, in my particular application I need to log CDR data from within the app, and this data is presented during the ChannelHangupComplete event. Unfortunately, as currently coded, the channel is disposed when the ChannelHangup event is detected which causes the channel to be disposed before the ChannelHangupComplete event can be handled.

Public member 'Where' on type 'Select(Of BasicMessage,EventMessage)' not found.

Hello,

I am trying to use this library for the windows service project at my work, but getting the following error:
Public member 'Where' on type 'Select(Of BasicMessage,EventMessage)' not found.

I am using an example for the Inbound Socket Client from the main page. The code looks like this:

   Private Async Function SocketSetup() As Task

        goSocket = Await InboundSocket.Connect(ConfigurationManager.AppSettings("Phone_Server_IpAddress"), System.Configuration.ConfigurationManager.AppSettings("Phone_Port"), "ClueCon")

        Dim ApiResponse = Await goSocket.SendApi("status")
        WriteTraceFile(ApiResponse.BodyText, 0)

        Await goSocket.SubscribeEvents(EventName.ChannelHangupComplete)

        goSocket.Events.Where(Function(x) x.EventName = EventName.ChannelHangupComplete).Subscribe(Async Sub(x)

                                                                                                       Await ProcessCall(x.UUID, x.HangupCause)

                                                                                                   End Sub)

    End Function

goSocket is a global object. The error happens where it subscribes to events. Am I missing something? I tried in .NET 4.5 and 4.6.1. I got the latest version of System.Reactive.Linq. Can't figure it out.

'Failed to parse body of event' exception

I'm trying to test the library with FreeSWITCH. I'm using the sample code, just subscribing to EventName.All, and checking if I can see the events. I noticed that I would only get an event or two, and then nothing (despite me placing SIP calls across FreeSWITCH, which should generate plenty).

To see what's going on, I added NLog to my test application, which resulted in the following output (pasted below).

It looks like the library throws 'Failed to parse body of event' exception and closes the socket (relevant class is here:
https://github.com/danbarua/NEventSocket/blob/38bcd41c3ae6443f73067967b226488112ee01a1/src/NEventSocket/FreeSwitch/EventMessage.cs)

Any ideas?

`NLOG: TEST CLICKED!!!
'FsSockets.Gui.Wpf.vshost.exe' (CLR v4.0.30319: FsSockets.Gui.Wpf.vshost.exe): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Dynamic\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Dynamic.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'FsSockets.Gui.Wpf.vshost.exe' (CLR v4.0.30319: FsSockets.Gui.Wpf.vshost.exe): Loaded 'Anonymously Hosted DynamicMethods Assembly'. 
NLOG: EventSocket initialized
NLOG: NEventSocket.InboundSocket Worker Thread 1 started
NLOG: Messages Received [auth/request].
NLOG: Received Auth Request
NLOG: Sending [auth ClueCon]
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK accepted] for [auth ClueCon]
NLOG: InboundSocket authentication succeeded.
NLOG: Sending [api status]
NLOG: NEventSocket.InboundSocket Worker Thread 1 started
NLOG: Messages Received [api/response].
NLOG: ApiResponse received [UP 0 years, 0 days, 0 hours, 56 minutes, 8 seconds, 345 milliseconds, 987 microsecondsFreeSWITCH (Version 1.7.0  64bit) is ready18 session(s) since startup2 session(s) - peak 3, last 5min 2 0 session(s) per Sec out of max 30, peak 2, last 5min 0 1000 session(s) maxmin idle cpu 0.00/96.94] for [status]
UP 0 years, 0 days, 0 hours, 56 minutes, 8 seconds, 345 milliseconds, 987 microseconds
FreeSWITCH (Version 1.7.0  64bit) is ready
18 session(s) since startup
2 session(s) - peak 3, last 5min 2 
0 session(s) per Sec out of max 30, peak 2, last 5min 0 
1000 session(s) max
min idle cpu 0.00/96.94

NLOG: Sending [event plain ALL]
NLOG: NEventSocket.InboundSocket Worker Thread 1 started
NLOG: Messages Received [command/reply].
NLOG: CommandReply received [+OK event listener enabled plain] for [event plain ALL]
NLOG: NEventSocket.InboundSocket Worker Thread 1 started
NLOG: Messages Received [text/event-plain].
NLOG: EVENT: RecvRtcpMessage
NLOG: Messages Received [text/event-plain].
NLOG: EVENT: RecvRtcpMessage
NLOG: Messages Received [text/event-plain].
NLOG: EVENT: RecvRtcpMessage
NLOG: Messages Received [text/event-plain].
NLOG: Messages Received [].
NLOG: Messages Received [text/event-plain].
NLOG: Failed to parse body of event
NLOG: Unexpected Error reading from stream
NLOG: NEventSocket.InboundSocket Worker Thread 1 completed
NLOG: Disposing NEventSocket.InboundSocket (disposing:True)
NLOG: TcpClient closed
NLOG: NEventSocket.InboundSocket Worker Thread 1 completed
NLOG: NEventSocket.InboundSocket Worker Thread 1 completed
NLOG: NEventSocket.InboundSocket (1) Disposed
NLOG: 
NLOG: Unexpected Error reading from stream
NLOG: NEventSocket.InboundSocket Worker Thread 1 completed`

NEventSocket.Channels

As discussed in #41 one of the next steps for NEventSocket is to separate the Channels API. The main reasons are:

  • Keep an abstraction separation between low-level (Sockets) and high-level (Channels) functionality and evolve the Channels API without compromising the core functionality.
  • Keep the core small and stable and independently testable.

So I've tried to separate them and tinkered around until the project was buildable again, it went straight forward:

  • There is one dependency to the internal type InterlockedBoolean, which I suggest to either make them public or temporarily add an InternalsVisibleTo to AssemblyInfo.cs in NEventSocket.dll.
  • In addition to the files below the Channels folder, the files OutputListener.cs and OutboundSocketExtensions.cs clearly belong to the Channels API. I'd suggest to keep them in the NEventSocket namespace. It's quite common to add new classes to namespaces that were introduced in dependent assemblies.

Then there is the packaging. it's probably best to introduce a second NuGet package named NEventSocket.Channels. To make that work, I guess that the Rx dependencies should be referred instead of ilmerged into NEventSocket.dll. I am not sure what was the initial reason to use ilmerge, but I am pretty sure that it will open a can of wormes if it'll be left that way.

Regarding tests, I have no idea yet :( I hope Dan has some ideas.

What do you think? Can we proceed with the separation, or is it just too early? Is another branch a good way to figure out if we can resolve all these issues, or is it better to just tag what's there with v2 and continue to work on the master branch.

Schedule Hangup

Hi Dan,
I added this to your code for scheduled hangup and I thought maybe you'd like to add something similar to your version as I find it handy:

--in apiextension.cs

        public static Task<ApiResponse> ScheduleHangup(
this EventSocket eventSocket, 
string uuid, 
string value, 
HangupCause hangupCause = HangupCause.NormalClearing)
        {
            return eventSocket.SendApi("sched_hangup +{0} {1} {2}".Fmt(
value, uuid, hangupCause.ToString().ToUpperWithUnderscores()));
        }

--in basicchannel.cs

        public Task ScheduleHangup(HangupCause hangupCause = FreeSwitch.HangupCause.NormalClearing)
        {
            return
                RunIfAnswered(
                    () =>
                    eventSocket.SendApi("sched_hangup +10 {0} {1}".Fmt(UUID, hangupCause.ToString().ToUpperWithUnderscores())),
                    true);
        }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.