Add Music On Hold feature to Call Ring Group activity
This commit is contained in:
parent
d1c73d5acd
commit
3d420f2bd8
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -109,6 +110,16 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "The audio file to play while dialing.",
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public Uri? MusicOnHold
|
||||
{
|
||||
get => GetState<Uri>();
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Enables Answering Machine Detection.",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
|
|
@ -130,9 +141,22 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
set => SetState(value);
|
||||
}
|
||||
|
||||
public override void Build(ICompositeActivityBuilder builder) =>
|
||||
public override void Build(ICompositeActivityBuilder builder)
|
||||
{
|
||||
// Root.
|
||||
builder
|
||||
.StartWith<Fork>(fork => fork.WithBranches("Ring", "Queue Timeout"), fork =>
|
||||
.If(() => MusicOnHold != null, @if =>
|
||||
{
|
||||
@if
|
||||
.When(OutcomeNames.True)
|
||||
.Then<PlayAudio>(playAudio => playAudio.WithAudioUrl(() => MusicOnHold).WithLoop("infinity"))
|
||||
.ThenNamed("MainFork");
|
||||
|
||||
@if
|
||||
.When(OutcomeNames.False)
|
||||
.ThenNamed("MainFork");
|
||||
})
|
||||
.Add<Fork>(fork => fork.WithBranches("Ring", "Queue Timeout"), fork =>
|
||||
{
|
||||
fork.When("Ring")
|
||||
.While(true, iterate => iterate
|
||||
|
|
@ -140,16 +164,21 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
{
|
||||
cases.Add(RingGroupStrategy.PrioritizedHunt.ToString(), () => Strategy == RingGroupStrategy.PrioritizedHunt, BuildPrioritizedHuntFlow);
|
||||
cases.Add(RingGroupStrategy.RingAll.ToString(), () => Strategy == RingGroupStrategy.RingAll, BuildRingAllFlow);
|
||||
}));
|
||||
}))
|
||||
.ThenNamed("ExitWithNoResponse");
|
||||
|
||||
fork.When("Queue Timeout")
|
||||
.IfFalse(() => IsNullOrZero(MaxQueueWaitTime), whenTrue =>
|
||||
{
|
||||
whenTrue
|
||||
.Timer(() => MaxQueueWaitTime!.Value)
|
||||
.Finish(TelnyxOutcomeNames.NoResponse);
|
||||
});
|
||||
});
|
||||
.IfFalse(() => IsNullOrZero(MaxQueueWaitTime), ifFalse => ifFalse.StartIn(() => MaxQueueWaitTime!.Value).ThenNamed("ExitWithNoResponse"));
|
||||
}).WithName("MainFork");
|
||||
|
||||
// No Response exit node.
|
||||
builder.Add<If>(@if => @if.WithCondition(() => MusicOnHold != null), @if =>
|
||||
{
|
||||
@if.When(OutcomeNames.True).Then<StopAudioPlayback>(stopAudioPlayback => stopAudioPlayback.When(TelnyxOutcomeNames.CallPlaybackEnded).Finish(TelnyxOutcomeNames.NoResponse));
|
||||
@if.When(OutcomeNames.False).Finish(TelnyxOutcomeNames.NoResponse);
|
||||
})
|
||||
.WithName("ExitWithNoResponse");
|
||||
}
|
||||
|
||||
protected override async ValueTask OnExitAsync(ActivityExecutionContext context, object? output)
|
||||
{
|
||||
|
|
@ -185,13 +214,24 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
{
|
||||
dial
|
||||
.When(TelnyxOutcomeNames.Answered)
|
||||
.Then<BridgeCalls>(bridgeCalls =>
|
||||
.If(() => MusicOnHold != null, @if =>
|
||||
{
|
||||
@if.When(OutcomeNames.True)
|
||||
.Then<StopAudioPlayback>(stopAudioPlayback => stopAudioPlayback
|
||||
.When(TelnyxOutcomeNames.CallPlaybackEnded)
|
||||
.ThenNamed("BridgeCalls1"));
|
||||
|
||||
@if.When(OutcomeNames.False)
|
||||
.ThenNamed("BridgeCalls1");
|
||||
})
|
||||
.Add<BridgeCalls>(branch: bridgeCalls =>
|
||||
bridgeCalls.When(TelnyxOutcomeNames.Bridged)
|
||||
.Finish(activity => activity.WithOutcome(TelnyxOutcomeNames.Connected).WithOutput(context => context.GetInput<BridgedCallsOutput>())));
|
||||
.Finish(activity => activity.WithOutcome(TelnyxOutcomeNames.Connected).WithOutput(context => context.GetInput<BridgedCallsOutput>()))).WithName("BridgeCalls1");
|
||||
}
|
||||
)
|
||||
)
|
||||
.IfTrue(() => IsNullOrZero(MaxQueueWaitTime), whenTrue => whenTrue.Finish(TelnyxOutcomeNames.NoResponse));
|
||||
// If we don't have a queue wait time, then break out of outer loop.
|
||||
.IfTrue(() => IsNullOrZero(MaxQueueWaitTime), @if => @if.Break());
|
||||
|
||||
private void BuildRingAllFlow(IOutcomeBuilder builder) =>
|
||||
builder
|
||||
|
|
@ -210,7 +250,12 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
fork
|
||||
.When("Timeout")
|
||||
.StartIn(() => RingTime)
|
||||
.IfTrue(() => IsNullOrZero(MaxQueueWaitTime), whenTrue => whenTrue.Finish(TelnyxOutcomeNames.NoResponse));
|
||||
|
||||
// If we don't have a queue wait time, then break out of outer loop.
|
||||
.If(@if => @if.WithCondition( () => IsNullOrZero(MaxQueueWaitTime)), @if =>
|
||||
{
|
||||
@if.When(OutcomeNames.True).Break();
|
||||
});
|
||||
|
||||
fork
|
||||
.When("Dial Everyone")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Elsa.Activities.Telnyx.Client.Services;
|
|||
using Elsa.Activities.Telnyx.Extensions;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Builders;
|
||||
using Elsa.Design;
|
||||
using Elsa.Exceptions;
|
||||
using Elsa.Expressions;
|
||||
|
|
@ -109,4 +110,37 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
|
||||
private static string? EmptyToNull(string? value) => value is "" ? null : value;
|
||||
}
|
||||
|
||||
public static class PlayAudioExtensions
|
||||
{
|
||||
public static ISetupActivity<PlayAudio> WithCallControlId(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<PlayAudio> WithCallControlId(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<PlayAudio> WithCallControlId(this ISetupActivity<PlayAudio> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<PlayAudio> WithCallControlId(this ISetupActivity<PlayAudio> setup, Func<string?> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<PlayAudio> WithCallControlId(this ISetupActivity<PlayAudio> setup, string? value) => setup.Set(x => x.CallControlId, value);
|
||||
|
||||
public static ISetupActivity<PlayAudio> WithAudioUrl(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, ValueTask<Uri?>> value) => setup.Set(x => x.AudioUrl, value);
|
||||
public static ISetupActivity<PlayAudio> WithAudioUrl(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, Uri?> value) => setup.Set(x => x.AudioUrl, value);
|
||||
public static ISetupActivity<PlayAudio> WithAudioUrl(this ISetupActivity<PlayAudio> setup, Func<ValueTask<Uri?>> value) => setup.Set(x => x.AudioUrl, value);
|
||||
public static ISetupActivity<PlayAudio> WithAudioUrl(this ISetupActivity<PlayAudio> setup, Func<Uri?> value) => setup.Set(x => x.AudioUrl, value);
|
||||
public static ISetupActivity<PlayAudio> WithAudioUrl(this ISetupActivity<PlayAudio> setup, Uri? value) => setup.Set(x => x.AudioUrl, value);
|
||||
|
||||
public static ISetupActivity<PlayAudio> WithLoop(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.Loop, value);
|
||||
public static ISetupActivity<PlayAudio> WithLoop(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.Loop, value);
|
||||
public static ISetupActivity<PlayAudio> WithLoop(this ISetupActivity<PlayAudio> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.Loop, value);
|
||||
public static ISetupActivity<PlayAudio> WithLoop(this ISetupActivity<PlayAudio> setup, Func<string?> value) => setup.Set(x => x.Loop, value);
|
||||
public static ISetupActivity<PlayAudio> WithLoop(this ISetupActivity<PlayAudio> setup, string? value) => setup.Set(x => x.Loop, value);
|
||||
|
||||
public static ISetupActivity<PlayAudio> WithOverlay(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, ValueTask<bool?>> value) => setup.Set(x => x.Overlay, value);
|
||||
public static ISetupActivity<PlayAudio> WithOverlay(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, bool?> value) => setup.Set(x => x.Overlay, value);
|
||||
public static ISetupActivity<PlayAudio> WithOverlay(this ISetupActivity<PlayAudio> setup, Func<ValueTask<bool?>> value) => setup.Set(x => x.Overlay, value);
|
||||
public static ISetupActivity<PlayAudio> WithOverlay(this ISetupActivity<PlayAudio> setup, Func<bool?> value) => setup.Set(x => x.Overlay, value);
|
||||
public static ISetupActivity<PlayAudio> WithOverlay(this ISetupActivity<PlayAudio> setup, bool? value) => setup.Set(x => x.Overlay, value);
|
||||
|
||||
public static ISetupActivity<PlayAudio> WithTargetLegs(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.TargetLegs, value);
|
||||
public static ISetupActivity<PlayAudio> WithTargetLegs(this ISetupActivity<PlayAudio> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.TargetLegs, value);
|
||||
public static ISetupActivity<PlayAudio> WithTargetLegs(this ISetupActivity<PlayAudio> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.TargetLegs, value);
|
||||
public static ISetupActivity<PlayAudio> WithTargetLegs(this ISetupActivity<PlayAudio> setup, Func<string?> value) => setup.Set(x => x.TargetLegs, value);
|
||||
public static ISetupActivity<PlayAudio> WithTargetLegs(this ISetupActivity<PlayAudio> setup, string? value) => setup.Set(x => x.TargetLegs, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
using Elsa.Activities.Telnyx.Extensions;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Builders;
|
||||
using Elsa.Design;
|
||||
using Elsa.Exceptions;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Activities
|
||||
{
|
||||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Play an audio file on the call.",
|
||||
Outcomes = new[] { TelnyxOutcomeNames.CallPlaybackEnding, TelnyxOutcomeNames.CallIsNoLongerActive, TelnyxOutcomeNames.CallPlaybackEnded },
|
||||
DisplayName = "Play Audio"
|
||||
)]
|
||||
public class StopAudioPlayback : Activity
|
||||
{
|
||||
private readonly ITelnyxClient _telnyxClient;
|
||||
public StopAudioPlayback(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Call Control ID",
|
||||
Hint = "Unique identifier and token for controlling the call",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string? CallControlId { get; set; } = default!;
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string? ClientState { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Command ID",
|
||||
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string? CommandId { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Use 'current' to stop only the current audio or 'all' to stop all audios in the queue.",
|
||||
DefaultValue = "all",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string? Stop { get; set; } = "all";
|
||||
|
||||
[ActivityOutput(Hint = "The received payload when speaking ended.")]
|
||||
public CallPlaybackEndedPayload? PlaybackEndedPayload { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var request = new StopAudioPlaybackRequest(
|
||||
EmptyToNull(ClientState),
|
||||
EmptyToNull(CommandId),
|
||||
Stop
|
||||
);
|
||||
|
||||
var callControlId = context.GetCallControlId(CallControlId);
|
||||
|
||||
try
|
||||
{
|
||||
await _telnyxClient.Calls.StopAudioPlaybackAsync(callControlId, request, context.CancellationToken);
|
||||
return Combine(Outcome(TelnyxOutcomeNames.CallPlaybackEnding), Suspend());
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
if (await e.CallIsNoLongerActiveAsync())
|
||||
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
|
||||
|
||||
throw new WorkflowException(e.Content ?? e.Message, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)
|
||||
{
|
||||
PlaybackEndedPayload = context.GetInput<CallPlaybackEndedPayload>();
|
||||
return Outcome(TelnyxOutcomeNames.CallPlaybackEnded);
|
||||
}
|
||||
|
||||
private static string? EmptyToNull(string? value) => value is "" ? null : value;
|
||||
}
|
||||
|
||||
public static class StopAudioPlaybackExtensions
|
||||
{
|
||||
public static ISetupActivity<StopAudioPlayback> WithCallControlId(this ISetupActivity<StopAudioPlayback> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithCallControlId(this ISetupActivity<StopAudioPlayback> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithCallControlId(this ISetupActivity<StopAudioPlayback> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithCallControlId(this ISetupActivity<StopAudioPlayback> setup, Func<string?> value) => setup.Set(x => x.CallControlId, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithCallControlId(this ISetupActivity<StopAudioPlayback> setup, string? value) => setup.Set(x => x.CallControlId, value);
|
||||
|
||||
public static ISetupActivity<StopAudioPlayback> WithStop(this ISetupActivity<StopAudioPlayback> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.Stop, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithStop(this ISetupActivity<StopAudioPlayback> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.Stop, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithStop(this ISetupActivity<StopAudioPlayback> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.Stop, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithStop(this ISetupActivity<StopAudioPlayback> setup, Func<string?> value) => setup.Set(x => x.Stop, value);
|
||||
public static ISetupActivity<StopAudioPlayback> WithStop(this ISetupActivity<StopAudioPlayback> setup, string? value) => setup.Set(x => x.Stop, value);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +90,12 @@ namespace Elsa.Activities.Telnyx.Client.Models
|
|||
bool Overlay,
|
||||
string? TargetLegs
|
||||
);
|
||||
|
||||
public record StopAudioPlaybackRequest(
|
||||
string? ClientState,
|
||||
string? CommandId,
|
||||
string? Stop
|
||||
);
|
||||
|
||||
public record StartRecordingRequest(
|
||||
string Channels,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ namespace Elsa.Activities.Telnyx.Client.Services
|
|||
[Post("/v2/calls/{callControlId}/actions/playback_start")]
|
||||
Task PlayAudioAsync(string callControlId, [Body]PlayAudioRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
[Post("/v2/calls/{callControlId}/actions/playback_stop")]
|
||||
Task StopAudioPlaybackAsync(string callControlId, [Body]StopAudioPlaybackRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
[Post("/v2/calls/{callControlId}/actions/record_start")]
|
||||
Task StartRecordingAsync(string callControlId, [Body]StartRecordingRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Services;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Handlers
|
||||
{
|
||||
public class ResumeStopAudioPlayback : ResumeWebhookDrivenActivity<StopAudioPlayback, CallPlaybackEndedPayload>
|
||||
{
|
||||
public ResumeStopAudioPlayback(IWorkflowLaunchpad workflowLaunchpad) : base(workflowLaunchpad)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ using MediatR;
|
|||
|
||||
namespace Elsa.Activities.Telnyx.Handlers
|
||||
{
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity, TPayload> : ResumeWebhookDrivenActivity<TActivity> where TPayload : CallPayload
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity, TPayload> : ResumeWebhookDrivenActivity<TActivity> where TPayload : CallPayload where TActivity : IActivity
|
||||
{
|
||||
protected ResumeWebhookDrivenActivity(IWorkflowLaunchpad workflowLaunchpad) : base(workflowLaunchpad)
|
||||
{
|
||||
|
|
@ -22,7 +22,7 @@ namespace Elsa.Activities.Telnyx.Handlers
|
|||
protected override IEnumerable<Type> GetSupportedPayloadTypes() => new[] {typeof(TPayload)};
|
||||
}
|
||||
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity> : INotificationHandler<TelnyxWebhookReceived>
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity> : INotificationHandler<TelnyxWebhookReceived> where TActivity : IActivity
|
||||
{
|
||||
private readonly IWorkflowLaunchpad _workflowLaunchpad;
|
||||
protected ResumeWebhookDrivenActivity(IWorkflowLaunchpad workflowLaunchpad) => _workflowLaunchpad = workflowLaunchpad;
|
||||
|
|
|
|||
|
|
@ -23,5 +23,7 @@
|
|||
public const string Speaking = "Speaking";
|
||||
public const string Recording = "Recording";
|
||||
public const string FinishedRecording = "Finished Recording";
|
||||
public const string CallPlaybackEnding = "Playback Ending";
|
||||
public const string CallPlaybackEnded = "Playback Ended";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Services.Bookmarks;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Providers.Bookmarks
|
||||
{
|
||||
public class StopAudioPlaybackBookmark : IBookmark
|
||||
{
|
||||
}
|
||||
|
||||
public class StopAudioPlaybackBookmarkProvider : DefaultBookmarkProvider<StopAudioPlaybackBookmark, StopAudioPlayback>
|
||||
{
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue