Exemple #1
0
        public void can_serialize_and_deserialize_BridgeOptions()
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();

                var options = new BridgeOptions()
                {
                    UUID = "985cea12-4e70-4c03-8a2c-2c4b4502bbbb",
                    TimeoutSeconds = 20,
                    CallerIdName = "Dan B Leg",
                    CallerIdNumber = "987654321",
                    HangupAfterBridge = false,
                    IgnoreEarlyMedia = true,
                    ContinueOnFail = true,
                    RingBack = "${uk-ring}"
                };

                options.ChannelVariables.Add("foo", "bar");
                options.ChannelVariables.Add("baz", "widgets");

                formatter.Serialize(ms, options);

                ms.Seek(0, SeekOrigin.Begin);

                var fromStream = formatter.Deserialize(ms) as BridgeOptions;
                Assert.Equal(options, fromStream);
            }
        }
Exemple #2
0
        public void can_format_BridgeOptions()
        {
            var options = new BridgeOptions()
            {
                UUID = "985cea12-4e70-4c03-8a2c-2c4b4502bbbb",
                TimeoutSeconds = 20,
                CallerIdName = "Dan B Leg",
                CallerIdNumber = "987654321",
                HangupAfterBridge = false,
                IgnoreEarlyMedia = true,
                ContinueOnFail = true,
                RingBack = "${uk-ring}"
            };

            // channel variables have no effect on ToString(), they're set on the a-leg of the call before initiating the bridge.
            // todo: allow exporting variables?
            options.ChannelVariables.Add("foo", "bar");
            options.ChannelVariables.Add("baz", "widgets");

            var toString = options.ToString();
            const string Expected = "{origination_uuid='985cea12-4e70-4c03-8a2c-2c4b4502bbbb',call_timeout='20',origination_caller_id_name='Dan B Leg',origination_caller_id_number='987654321',ignore_early_media='true'}";
            Assert.Equal(Expected, toString);
        }
Exemple #3
0
        /// <summary>
        /// Bridge a new channel to the existing one. Generally used to route an incoming call to one or more endpoints.
        /// </summary>
        /// <remarks>
        /// See https://freeswitch.org/confluence/display/FREESWITCH/bridge
        /// </remarks>
        /// <param name="uuid">The UUID of the channel to bridge (the A-Leg).</param>
        /// <param name="endpoint">The destination to dial.</param>
        /// <param name="options">(Optional) Any <seealso cref="BridgeOptions"/> to configure the bridge.</param>
        /// <returns>A Task of <seealso cref="BridgeResult"/>.</returns>
        public async Task<BridgeResult> Bridge(string uuid, string endpoint, BridgeOptions options = null)
        {
            if (options == null)
            {
                options = new BridgeOptions();
            }

            if (string.IsNullOrEmpty(options.UUID))
            {
                options.UUID = Guid.NewGuid().ToString();
            }

            var bridgeString = string.Format("{0}{1}", options, endpoint);

            // some bridge options need to be set in channel vars
            if (options.ChannelVariables.Any())
            {
                await
                    this.SetMultipleChannelVariables(
                        uuid, options.ChannelVariables.Select(kvp => kvp.Key + "='" + kvp.Value + "'").ToArray()).ConfigureAwait(false);
            }

            /* If the bridge fails to connect we'll get a CHANNEL_EXECUTE_COMPLETE event with a failure message and the Execute task will complete.
             * If the bridge succeeds, that event won't arrive until after the bridged leg hangs up and completes the call.
             * In this case, we want to return a result as soon as the b-leg picks up and connects so we'll merge with the CHANNEL_BRIDGE event
             * observable.Amb(otherObservable) will propogate the first sequence to produce a result. */


            var bridgedOrHungupEvent =
                Events.FirstOrDefaultAsync(x => x.UUID == uuid && (x.EventName == EventName.ChannelBridge || x.EventName == EventName.ChannelHangup))
                    .Do(
                        e =>
                            {
                                if (e != null)
                                {
                                    switch (e.EventName)
                                    {
                                        case EventName.ChannelBridge:
                                            Log.Debug(() => "Bridge [{0} - {1}] complete - {2}".Fmt(uuid, options.UUID, e.Headers[HeaderNames.OtherLegUniqueId]));
                                            break;
                                        case EventName.ChannelHangup:
                                            Log.Debug(() => "Bridge [{0} - {1}]  aborted, channel hangup [{2}]".Fmt(uuid, options.UUID, e.Headers[HeaderNames.HangupCause]));
                                            break;
                                    }
                                }
                            });

            return
                await
                ExecuteApplication(uuid, "bridge", bridgeString)
                    .ToObservable()
                    .Amb(bridgedOrHungupEvent)
                    .Select(x => new BridgeResult(x))
                    .ToTask()
                    .ConfigureAwait(false);
        }
Exemple #4
0
        public async Task BridgeTo(string destination, BridgeOptions options, Action<EventMessage> onProgress = null)
        {
            if (!IsAnswered && !IsPreAnswered)
            {
                return;
            }

            Log.Debug(() => "Channel {0} is attempting a bridge to {1}".Fmt(UUID, destination));

            if (string.IsNullOrEmpty(options.UUID))
            {
                options.UUID = Guid.NewGuid().ToString();
            }

            var subscriptions = new CompositeDisposable();

            subscriptions.Add(
                eventSocket.Events.Where(x => x.UUID == options.UUID)
                    .Take(1)
                    .Subscribe(x => Bridge = new BridgeStatus(false, "In Progress", new BridgedChannel(x, eventSocket))));

            if (onProgress != null)
            {
                subscriptions.Add(
                    eventSocket.Events.Where(x => x.UUID == options.UUID && x.EventName == EventName.ChannelProgress)
                        .Take(1)
                        .Subscribe(onProgress));
            }

            var result = await eventSocket.Bridge(UUID, destination, options).ConfigureAwait(false);

            Log.Debug(() => "Channel {0} bridge complete {1} {2}".Fmt(UUID, result.Success, result.ResponseText));
            subscriptions.Dispose();

            Bridge = new BridgeStatus(result.Success, result.ResponseText, Bridge.Channel);
        }
        public void Run()
        {
            var listener = new OutboundListener(8084);

            listener.Channels.Subscribe(
                async channel =>
                    {
                        try
                        {
                            channel.HangupCallBack = (e) =>
                                {
                                    ColorConsole.WriteLine("Hangup Detected on A-Leg {0} {1}".Fmt(e.Headers[HeaderNames.CallerUniqueId], e.Headers[HeaderNames.HangupCause]).Red());
                                    ColorConsole.WriteLine("Aleg bridge {0}".Fmt(channel.Bridge.HangupCause).OnRed());

                                    ColorConsole.WriteLine(e.ToString().DarkGreen());
                                };

                            await channel.Answer();
                            await channel.StartDetectingInbandDtmf();

                            var bridgeOptions = new BridgeOptions()
                                                    {
                                                        UUID = Guid.NewGuid().ToString(),
                                                        IgnoreEarlyMedia = true,
                                                        RingBack =
                                                            "tone_stream://%(400,200,400,450);%(400,2000,400,450);loops=-1",
                                                        ContinueOnFail = true,
                                                        HangupAfterBridge = true,
                                                        TimeoutSeconds = 60,
                                                        CallerIdName = channel.Advanced.GetVariable("effective_caller_id_name"),
                                                        CallerIdNumber = channel.Advanced.GetVariable("effective_caller_id_number"),
                                                    };

                            bridgeOptions.ChannelVariables.Add("bridge_filter_dtmf", "true");

                            await channel.BridgeTo("user/1003", bridgeOptions, (e) => ColorConsole.WriteLine("Bridge Progress Ringing...".DarkGreen()));

                            if (!channel.Bridge.IsBridged)
                            {
                                ColorConsole.WriteLine("Bridge Failed - {0}".Fmt(channel.Bridge.HangupCause).Red());
                                await channel.PlayFile("ivr/8000/ivr-call_rejected.wav");
                                await channel.Hangup(HangupCause.NormalTemporaryFailure);
                            }
                            else
                            {
                                ColorConsole.WriteLine("Bridge success - {0}".Fmt(channel.Bridge.ResponseText).DarkGreen());

                                channel.Bridge.Channel.HangupCallBack = (e) => ColorConsole.WriteLine("Hangup Detected on B-Leg {0} {1}".Fmt(e.Headers[HeaderNames.CallerUniqueId], e.Headers[HeaderNames.HangupCause]).Red());

                                ColorConsole.WriteLine("Enabling feature codes on the B-Leg: ".DarkGreen());
                                ColorConsole.WriteLine("Press ".DarkGreen(), "#7".Yellow(), " to Start Recording".DarkGreen());
                                ColorConsole.WriteLine("Press ".DarkGreen(), "#8".Yellow(), " to Stop Recording".DarkGreen());
                                ColorConsole.WriteLine("Press ".DarkGreen(), "#4".Yellow(), " to Pause Recording".DarkGreen());
                                ColorConsole.WriteLine("Press ".DarkGreen(), "#5".Yellow(), " to Resume Recording".DarkGreen());
                                ColorConsole.WriteLine("Press ".DarkGreen(), "#9".Yellow(), " for attended transfer".DarkGreen());

                                await channel.SetChannelVariable("RECORD_STEREO", "true");
                                var recordingPath = "{0}.wav".Fmt(channel.UUID);

                                channel.Bridge.Channel.FeatureCodes("#").Subscribe(
                                    async x =>
                                        {
                                            try
                                            {
                                                ColorConsole.WriteLine("Detected Feature Code: ".DarkYellow(), x);
                                                switch (x)
                                                {
                                                    case "#4":
                                                        ColorConsole.WriteLine("Mask recording".Yellow());
                                                        await channel.MaskRecording();
                                                        await channel.PlayFile("ivr/8000/ivr-recording_paused.wav", Leg.BLeg);
                                                        break;
                                                    case "#5":
                                                        ColorConsole.WriteLine("Unmask recording".Yellow());
                                                        await channel.UnmaskRecording();
                                                        await channel.PlayFile("ivr/8000/ivr-begin_recording.wav", Leg.BLeg);
                                                        break;
                                                    case "#8":
                                                        ColorConsole.WriteLine("Stop recording".Yellow());
                                                        await channel.StopRecording();
                                                        await channel.PlayFile(
                                                            "ivr/8000/ivr-recording_stopped.wav", Leg.Both);
                                                        break;
                                                    case "#7":
                                                        ColorConsole.WriteLine("Start recording".Yellow());
                                                        await channel.StartRecording(recordingPath);
                                                        await channel.PlayFile("ivr/8000/ivr-begin_recording.wav", Leg.Both);
                                                        break;
                                                    case "#9":
                                                        ColorConsole.WriteLine("Attended x-fer".Yellow());
                                                        await
                                                            Task.WhenAll(
                                                                channel.PlayFile("ivr/8000/ivr-call_being_transferred.wav"),
                                                                channel.Bridge.Channel.PlayFile("misc/8000/transfer1.wav"));

                                                        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.SetChannelVariable("recording_follow_attxfer", "true");
                                                            await channel.Bridge.Channel.SetChannelVariable("origination_cancel_key", "#");
                                                            await channel.Bridge.Channel.SetChannelVariable("transfer_ringback", "tone_stream://%(400,200,400,450);%(400,2000,400,450);loops=-1");

                                                            await channel.Bridge.Channel.PlayFile("ivr/8000/ivr-please_hold_while_party_contacted.wav");

                                                            //todo: push this logic into the channel itself?

                                                            channel.ExitOnHangup = false; //we might want to notify b+c parties if the transfer failed
                                                            var xfer = await channel.Bridge.Channel.AttendedTransfer("user/{0}".Fmt(digits));
                                                            channel.ExitOnHangup = true; //re enable exit on hangup

                                                            ColorConsole.WriteLine("XFER: {0} {1}".Fmt(xfer.Status, xfer.HangupCause).Yellow());

                                                            if (xfer.Status != AttendedTransferResultStatus.Failed)
                                                            {
                                                                await channel.PlayFile("misc/8000/transfer2.wav", Leg.Both);
                                                            }
                                                            else
                                                            {
                                                                if (!channel.IsAnswered && channel.Bridge.Channel.IsAnswered)
                                                                {
                                                                    await channel.Bridge.Channel.PlayFile("ivr/8000/ivr-call_attempt_aborted.wav", Leg.Both);

                                                                    //as a-leg has disconnected, we'll close the socket when b-leg hangs up
                                                                    //todo: what if it's a three-way?!
                                                                    channel.Bridge.Channel.HangupCallBack = async _ => await channel.Exit();

                                                                    return;
                                                                }

                                                                if (xfer.HangupCause == HangupCause.CallRejected)
                                                                {
                                                                    await channel.Bridge.Channel.PlayFile("ivr/8000/ivr-call_rejected.wav");
                                                                }
                                                                else if (xfer.HangupCause == HangupCause.NoUserResponse
                                                                         || xfer.HangupCause == HangupCause.NoAnswer)
                                                                {
                                                                    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
                                                                {
                                                                    await
                                                                        channel.Bridge.Channel.PlayFile(
                                                                            "ivr/8000/ivr-call_cannot_be_completed_as_dialed.wav");
                                                                }
                                                            }
                                                        }

                                                        break;
                                                }
                                            }
                                            catch (OperationCanceledException ex)
                                            {
                                                ColorConsole.WriteLine("TaskCancelled - shutting down\r\n{0}".Fmt(ex.ToString()).OnRed());
                                                ColorConsole.WriteLine("Channel {0} is {1}".Fmt(channel.UUID, channel.Answered).OnRed());
                                            }
                                        });
                            }
                        }
                        catch (OperationCanceledException ex)
                        {
                            ColorConsole.WriteLine("TaskCancelled - shutting down\r\n{0}".Fmt(ex.ToString()).OnRed());
                            ColorConsole.WriteLine("Channel {0} is {1}".Fmt(channel.UUID, channel.Answered).OnRed());
                        }
                    });

            listener.Start();
        }
 protected bool Equals(BridgeOptions other)
 {
     return this.parameters.SequenceEqual(other.parameters) && this.ChannelVariables.SequenceEqual(other.ChannelVariables);
 }