From c9dc911fa69168f9dafdda527506d3cef4ffb861 Mon Sep 17 00:00:00 2001 From: Noah Mehl Date: Tue, 4 Jun 2019 13:49:13 -0400 Subject: [PATCH] Added voicemail fast forward and rewind (#5662) --- .../callflow/src/module/cf_voicemail.erl | 78 +++++++++++++++++-- applications/crossbar/doc/ref/vmboxes.md | 2 + applications/crossbar/doc/voicemail.md | 2 + applications/crossbar/priv/api/swagger.json | 75 ++++++++++++++++++ .../schemas/kapi.dialplan.playseek.json | 47 +++++++++++ .../schemas/system_config.callflow.json | 11 +++ .../priv/couchdb/schemas/vmboxes.json | 11 +++ .../priv/couchdb/schemas/voicemail_keys.json | 8 ++ .../src/call_cmd/ecallmgr_call_command.erl | 16 ++++ applications/ecallmgr/src/ecallmgr_util.erl | 6 ++ core/kazoo_amqp/src/api/kapi_dialplan.erl | 19 +++++ core/kazoo_amqp/src/api/kapi_dialplan.hrl | 10 +++ core/kazoo_call/src/kapps_call_command.erl | 62 ++++++++++++++- core/kazoo_documents/src/kzd_vmboxes.erl | 26 +++++++ core/kazoo_documents/src/kzd_vmboxes.erl.src | 26 +++++++ .../kazoo_documents/src/kzd_voicemail_box.erl | 3 + .../src/kzd_voicemail_keys.erl | 26 +++++++ .../src/kzd_voicemail_keys.erl.src | 26 +++++++ 18 files changed, 444 insertions(+), 10 deletions(-) create mode 100644 applications/crossbar/priv/couchdb/schemas/kapi.dialplan.playseek.json diff --git a/applications/callflow/src/module/cf_voicemail.erl b/applications/callflow/src/module/cf_voicemail.erl index f73657273e5..c84886b6697 100644 --- a/applications/callflow/src/module/cf_voicemail.erl +++ b/applications/callflow/src/module/cf_voicemail.erl @@ -41,7 +41,10 @@ -define(KEY_DELETE_AFTER_NOTIFY, <<"delete_after_notify">>). -define(KEY_SAVE_AFTER_NOTIFY, <<"save_after_notify">>). -define(KEY_FORCE_REQUIRE_PIN, <<"force_require_pin">>). +-define(KEY_ALLOW_FF_RW, <<"is_voicemail_ff_rw_enabled">>). +-define(KEY_SEEK_DURATION, <<"seek_duration_ms">>). -define(MAX_INVALID_PIN_LOOPS, 3). +-define(DEFAULT_SEEK_DURATION, 10 * ?MILLISECONDS_IN_SECOND). -define(MAILBOX_DEFAULT_SIZE ,kapps_config:get_integer(?CF_CONFIG_CAT @@ -88,6 +91,17 @@ ,'false' )). +-define(IS_FF_RW_ENABLED + ,kapps_config:get_is_true(?CF_CONFIG_CAT + ,[?KEY_VOICEMAIL, ?KEY_ALLOW_FF_RW] + ,'false' + )). + +-define(MAILBOX_SEEK_DURATION + ,kapps_config:get_non_neg_integer(?CF_CONFIG_CAT + ,[?KEY_VOICEMAIL, ?KEY_SEEK_DURATION] + ,?DEFAULT_SEEK_DURATION + )). -define(DEFAULT_FORWARD_TYPE ,kapps_config:get_ne_binary(?CF_CONFIG_CAT ,[?KEY_VOICEMAIL, <<"vm_message_forward_type">>] @@ -133,8 +147,10 @@ ,prev = <<"4">> :: kz_term:ne_binary() ,next = <<"6">> :: kz_term:ne_binary() ,delete = <<"7">> :: kz_term:ne_binary() + ,rewind = <<"5">> :: kz_term:ne_binary() + ,fastforward = <<"8">> :: kz_term:ne_binary() - %% Greeting or instructions + %% Greeting or instructions ,continue = 'undefined' :: kz_term:api_ne_binary() }). -type vm_keys() :: #keys{}. @@ -165,6 +181,8 @@ ,transcribe_voicemail = 'false' :: boolean() ,notifications :: kz_term:api_object() ,after_notify_action = 'nothing' :: 'nothing' | 'delete' | 'save' + ,is_ff_rw_enabled = 'false' :: boolean() + ,seek_duration = ?DEFAULT_SEEK_DURATION :: non_neg_integer() ,interdigit_timeout = kapps_call_command:default_interdigit_timeout() :: pos_integer() ,play_greeting_intro = 'false' :: boolean() ,use_person_not_available = 'false' :: boolean() @@ -805,22 +823,30 @@ message_count_prompts(New, Saved) -> kapps_call_command:audio_macro_prompts(). message_prompt([H|_]=Messages, Message, Count, #mailbox{timezone=Timezone ,skip_envelope='false' + ,keys=Keys + ,is_ff_rw_enabled=AllowFfRw }) -> [{'prompt', <<"vm-message_number">>} ,{'say', kz_term:to_binary(Count - length(Messages) + 1), <<"number">>} - ,{'play', Message} + ,play_prompt(Message, AllowFfRw, Keys) ,{'prompt', <<"vm-received">>} ,{'say', get_unix_epoch(kz_json:get_integer_value(<<"timestamp">>, H), Timezone), <<"current_date_time">>} ,{'prompt', <<"vm-message_menu">>} ]; -message_prompt(Messages, Message, Count, #mailbox{skip_envelope='true'}) -> +message_prompt(Messages, Message, Count, #mailbox{is_ff_rw_enabled=AllowFfRw + ,keys=Keys + ,skip_envelope='true'}) -> lager:debug("mailbox is set to skip playing message envelope"), [{'prompt', <<"vm-message_number">>} ,{'say', kz_term:to_binary(Count - length(Messages) + 1), <<"number">>} - ,{'play', Message} + ,play_prompt(Message, AllowFfRw, Keys) ,{'prompt', <<"vm-message_menu">>} ]. +play_prompt(Message, 'true'=_AllowFfRw, #keys{rewind=RW, fastforward=FF}=_Keys) -> + {'play', Message, ?ANY_DIGIT -- [RW, FF]}; +play_prompt(Message, 'false', _Keys) -> + {'play', Message}. %%------------------------------------------------------------------------------ %% @doc Plays back a message then the menu, and continues to loop over the @@ -837,7 +863,7 @@ play_messages(Messages, Count, Box, Call) -> -spec play_messages(kz_json:objects(), kz_json:objects(), non_neg_integer(), mailbox(), kapps_call:call()) -> 'ok' | 'complete'. -play_messages([H|T]=Messages, PrevMessages, Count, Box, Call) -> +play_messages([H|T]=Messages, PrevMessages, Count, #mailbox{seek_duration=SeekDuration}=Box, Call) -> AccountId = kapps_call:account_id(Call), Message = kvm_message:media_url(AccountId, H), lager:info("playing mailbox message ~p (~s)", [Count, Message]), @@ -845,35 +871,51 @@ play_messages([H|T]=Messages, PrevMessages, Count, Box, Call) -> case message_menu(Prompt, Box, Call) of {'ok', 'keep'} -> lager:info("caller chose to save the message"), + _ = kapps_call_command:flush(Call), _ = kapps_call_command:b_prompt(<<"vm-saved">>, Call), {_, NMessage} = kvm_message:set_folder(?VM_FOLDER_SAVED, H, AccountId), play_messages(T, [NMessage|PrevMessages], Count, Box, Call); {'ok', 'prev'} -> lager:info("caller chose to listen to previous message"), + _ = kapps_call_command:flush(Call), play_prev_message(Messages, PrevMessages, Count, Box, Call); {'ok', 'next'} -> lager:info("caller chose to listen to next message"), + _ = kapps_call_command:flush(Call), play_next_message(Messages, PrevMessages, Count, Box, Call); {'ok', 'delete'} -> lager:info("caller chose to delete the message"), + _ = kapps_call_command:flush(Call), _ = kapps_call_command:b_prompt(<<"vm-deleted">>, Call), _ = kvm_message:set_folder({?VM_FOLDER_DELETED, 'false'}, H, AccountId), play_messages(T, PrevMessages, Count, Box, Call); {'ok', 'return'} -> lager:info("caller chose to return to the main menu"), + _ = kapps_call_command:flush(Call), _ = kapps_call_command:b_prompt(<<"vm-saved">>, Call), _ = kvm_message:set_folder(?VM_FOLDER_SAVED, H, AccountId), 'complete'; {'ok', 'replay'} -> lager:info("caller chose to replay"), + _ = kapps_call_command:flush(Call), play_messages(Messages, PrevMessages, Count, Box, Call); {'ok', 'forward'} -> lager:info("caller chose to forward the message"), + _ = kapps_call_command:flush(Call), forward_message(H, Box, Call), {_, NMessage} = kvm_message:set_folder(?VM_FOLDER_SAVED, H, AccountId), _ = kapps_call_command:prompt(<<"vm-saved">>, Call), play_messages(T, [NMessage|PrevMessages], Count, Box, Call); + {'ok', 'rewind'} -> + lager:info("caller chose to rewind part of the message"), + _ = kapps_call_command:seek('rewind', SeekDuration, Call), + play_messages(Messages, PrevMessages, Count, Box, Call); + {'ok', 'fastforward'} -> + lager:info("caller chose to fast forward part of the message"), + _ = kapps_call_command:seek('fastforward', SeekDuration, Call), + play_messages(Messages, PrevMessages, Count, Box, Call); {'error', _} -> + _ = kapps_call_command:flush(Call), lager:info("error during message playback") end; play_messages([], _, _, _, _) -> @@ -1029,7 +1071,7 @@ forward_message(AttachmentName, Length, Message, SrcBoxId, #mailbox{mailbox_numb %% user provides a valid option %% @end %%------------------------------------------------------------------------------ --type message_menu_returns() :: {'ok', 'keep' | 'delete' | 'return' | 'replay' | 'prev' | 'next' | 'forward'}. +-type message_menu_returns() :: {'ok', 'keep' | 'delete' | 'return' | 'replay' | 'prev' | 'next' | 'forward' | 'rewind' | 'fastforward'}. -spec message_menu(mailbox(), kapps_call:call()) -> {'error', 'channel_hungup' | 'channel_unbridge' | kz_json:object()} | @@ -1047,7 +1089,10 @@ message_menu(Prompt, #mailbox{keys=#keys{replay=Replay ,prev=Prev ,next=Next ,return_main=ReturnMain + ,rewind=RW + ,fastforward=FF } + ,is_ff_rw_enabled=AllowFfRw ,interdigit_timeout=Interdigit }=Box, Call) -> lager:info("playing message menu"), @@ -1057,6 +1102,8 @@ message_menu(Prompt, #mailbox{keys=#keys{replay=Replay ,kapps_call_command:default_collect_timeout() ,Interdigit ,NoopId + ,[<<"#">>] + ,'false' ,Call ) of @@ -1067,6 +1114,8 @@ message_menu(Prompt, #mailbox{keys=#keys{replay=Replay {'ok', Replay} -> {'ok', 'replay'}; {'ok', Prev} -> {'ok', 'prev'}; {'ok', Next} -> {'ok', 'next'}; + {'ok', RW} when AllowFfRw -> {'ok', 'rewind'}; + {'ok', FF} when AllowFfRw -> {'ok', 'fastforward'}; {'error', _}=E -> E; _ -> _ = kapps_call_command:b_prompt(<<"menu-invalid_entry">>, Call), @@ -1568,7 +1617,9 @@ get_mailbox_profile(Data, Call) -> ,[MaxMessageCount, MsgCount] ), + SeekDuration = seek_duration(MailboxJObj), AfterNotifyAction = after_notify_action(MailboxJObj), + IsFfRwEnabled = is_ff_rw_enabled(MailboxJObj), #mailbox{mailbox_id = MailboxId ,exists = 'true' @@ -1609,6 +1660,8 @@ get_mailbox_profile(Data, Call) -> ,notifications = kz_json:get_json_value(<<"notifications">>, MailboxJObj) ,after_notify_action = AfterNotifyAction + ,is_ff_rw_enabled = IsFfRwEnabled + ,seek_duration = SeekDuration ,interdigit_timeout = kz_json:find(<<"interdigit_timeout">>, [MailboxJObj, Data], kapps_call_command:default_interdigit_timeout()) ,play_greeting_intro = @@ -1634,6 +1687,17 @@ should_require_pin(MailboxJObj) -> 'false' -> kzd_voicemail_box:pin_required(MailboxJObj) end. +-spec is_ff_rw_enabled(kz_json:object()) -> boolean(). +is_ff_rw_enabled(MailboxJObj) -> + case ?IS_FF_RW_ENABLED of + 'true' -> kzd_vmboxes:is_voicemail_ff_rw_enabled(MailboxJObj); + 'false' -> 'false' + end. + +-spec seek_duration(kz_json:object()) -> non_neg_integer(). +seek_duration(MailboxJObj) -> + kzd_vmboxes:seek_duration_ms(MailboxJObj, ?MAILBOX_SEEK_DURATION). + -spec after_notify_action(kz_json:object()) -> atom(). after_notify_action(MailboxJObj) -> Delete = kz_json:is_true(?KEY_DELETE_AFTER_NOTIFY, MailboxJObj, ?DEFAULT_DELETE_AFTER_NOTIFY), @@ -1702,6 +1766,8 @@ populate_keys(Call) -> ,replay = kz_json:get_binary_value(<<"replay">>, JObj, Default#keys.replay) ,prev = kz_json:get_binary_value(<<"prev">>, JObj, Default#keys.prev) ,next = kz_json:get_binary_value(<<"next">>, JObj, Default#keys.next) + ,fastforward = kz_json:get_binary_value(<<"fastforward">>, JObj, Default#keys.fastforward) + ,rewind = kz_json:get_binary_value(<<"rewind">>, JObj, Default#keys.rewind) ,delete = kz_json:get_binary_value(<<"delete">>, JObj, Default#keys.delete) ,continue = kz_json:get_binary_value(<<"continue">>, JObj, Default#keys.continue) }. diff --git a/applications/crossbar/doc/ref/vmboxes.md b/applications/crossbar/doc/ref/vmboxes.md index 03669ac72e0..a584cd09513 100644 --- a/applications/crossbar/doc/ref/vmboxes.md +++ b/applications/crossbar/doc/ref/vmboxes.md @@ -13,6 +13,7 @@ Key | Description | Type | Default | Required | Support Level `check_if_owner` | Determines if when the user calls their own voicemail they should be prompted to sign in | `boolean()` | `true` | `false` | `supported` `delete_after_notify` | Move the voicemail to delete folder after the notification has been sent | `boolean()` | `false` | `false` | `supported` `is_setup` | Determines if the user has completed the initial configuration | `boolean()` | `false` | `false` | `supported` +`is_voicemail_ff_rw_enabled` | callflow allow fastforward and rewind during voicemail message playback | `boolean()` | `false` | `false` | `mailbox` | The voicemail box number | `string(1..30)` | | `true` | `supported` `media.unavailable` | The ID of a media object that should be used as the unavailable greeting | `string(32)` | | `false` | `supported` `media` | The media (prompt) parameters | `object()` | `{}` | `false` | `supported` @@ -28,6 +29,7 @@ Key | Description | Type | Default | Required | Support Level `pin` | The pin number for the voicemail box | `string(4..15)` | | `false` | `supported` `require_pin` | Determines if a pin is required to check the voicemail from the users devices | `boolean()` | `false` | `false` | `supported` `save_after_notify` | Move the voicemail to save folder after the notification has been sent (This setting will override delete_after_notify) | `boolean()` | `false` | `false` | `supported` +`seek_duration_ms` | callflow fastforward and rewind seek duration | `integer()` | `10000` | `false` | `skip_envelope` | Determines if the envelope should be skipped | `boolean()` | `false` | `false` | `beta` `skip_greeting` | Determines if the greeting should be skipped | `boolean()` | `false` | `false` | `supported` `skip_instructions` | Determines if the instructions after the greeting and prior to composing a message should be played | `boolean()` | `false` | `false` | `supported` diff --git a/applications/crossbar/doc/voicemail.md b/applications/crossbar/doc/voicemail.md index 95490eeb81d..35ee04390cc 100644 --- a/applications/crossbar/doc/voicemail.md +++ b/applications/crossbar/doc/voicemail.md @@ -24,6 +24,7 @@ Key | Description | Type | Default | Required | Support Level `check_if_owner` | Determines if when the user calls their own voicemail they should be prompted to sign in | `boolean()` | `true` | `false` | `supported` `delete_after_notify` | Move the voicemail to delete folder after the notification has been sent | `boolean()` | `false` | `false` | `supported` `is_setup` | Determines if the user has completed the initial configuration | `boolean()` | `false` | `false` | `supported` +`is_voicemail_ff_rw_enabled` | callflow allow fastforward and rewind during voicemail message playback | `boolean()` | `false` | `false` | `mailbox` | The voicemail box number | `string(1..30)` | | `true` | `supported` `media.unavailable` | The ID of a media object that should be used as the unavailable greeting | `string(32)` | | `false` | `supported` `media` | The media (prompt) parameters | `object()` | `{}` | `false` | `supported` @@ -39,6 +40,7 @@ Key | Description | Type | Default | Required | Support Level `pin` | The pin number for the voicemail box | `string(4..15)` | | `false` | `supported` `require_pin` | Determines if a pin is required to check the voicemail from the users devices | `boolean()` | `false` | `false` | `supported` `save_after_notify` | Move the voicemail to save folder after the notification has been sent (This setting will override delete_after_notify) | `boolean()` | `false` | `false` | `supported` +`seek_duration_ms` | callflow fastforward and rewind seek duration | `integer()` | `10000` | `false` | `skip_envelope` | Determines if the envelope should be skipped | `boolean()` | `false` | `false` | `beta` `skip_greeting` | Determines if the greeting should be skipped | `boolean()` | `false` | `false` | `supported` `skip_instructions` | Determines if the instructions after the greeting and prior to composing a message should be played | `boolean()` | `false` | `false` | `supported` diff --git a/applications/crossbar/priv/api/swagger.json b/applications/crossbar/priv/api/swagger.json index f640d360e1b..9419c2ff8ad 100644 --- a/applications/crossbar/priv/api/swagger.json +++ b/applications/crossbar/priv/api/swagger.json @@ -13893,6 +13893,51 @@ ], "type": "object" }, + "kapi.dialplan.playseek": { + "description": "AMQP API for dialplan.playseek", + "properties": { + "Application-Name": { + "enum": [ + "playseek" + ], + "type": "string" + }, + "Call-ID": { + "type": "string" + }, + "Direction": { + "type": "string" + }, + "Duration": { + "type": "string" + }, + "Event-Category": { + "enum": [ + "call" + ], + "type": "string" + }, + "Event-Name": { + "enum": [ + "command" + ], + "type": "string" + }, + "Insert-At": { + "enum": [ + "now" + ], + "type": "string" + } + }, + "required": [ + "Application-Name", + "Call-ID", + "Direction", + "Duration" + ], + "type": "object" + }, "kapi.dialplan.playstop": { "description": "AMQP API for dialplan.playstop", "properties": { @@ -29439,6 +29484,11 @@ "description": "If true, ignore the setting on the vmbox and require all users to enter a pin", "type": "boolean" }, + "is_voicemail_ff_rw_enabled": { + "default": false, + "description": "callflow allow fastforward and rewind during voicemail message playback", + "type": "boolean" + }, "max_box_number_length": { "default": 15, "description": "callflow maximum box number length", @@ -29494,6 +29544,12 @@ "description": "callflow save after notify", "type": "boolean" }, + "seek_duration_ms": { + "default": 10000, + "description": "callflow fastforward and rewind seek duration", + "minimum": 0, + "type": "integer" + }, "vm_message_forward_type": { "default": "only_forward", "description": "Enable or disable the ability to prepend a message when forwarding a voicemail message", @@ -36294,6 +36350,11 @@ "description": "Determines if the user has completed the initial configuration", "type": "boolean" }, + "is_voicemail_ff_rw_enabled": { + "default": false, + "description": "callflow allow fastforward and rewind during voicemail message playback", + "type": "boolean" + }, "mailbox": { "description": "The voicemail box number", "maxLength": 30, @@ -36378,6 +36439,12 @@ "description": "Move the voicemail to save folder after the notification has been sent (This setting will override delete_after_notify)", "type": "boolean" }, + "seek_duration_ms": { + "default": 10000, + "description": "callflow fastforward and rewind seek duration", + "minimum": 0, + "type": "integer" + }, "skip_envelope": { "default": false, "description": "Determines if the envelope should be skipped", @@ -36425,6 +36492,10 @@ "$ref": "#/definitions/voicemail_keys.dtmf_key", "description": "DTMF key which will exit the main menu when pressed" }, + "fastforward": { + "$ref": "#/definitions/voicemail_keys.dtmf_key", + "description": "DTMF key which will seek the playback forward for seek_duration_ms ms" + }, "hear_new": { "$ref": "#/definitions/voicemail_keys.dtmf_key", "description": "DTMF key which will play new voicemail messages when pressed at the main menu" @@ -36477,6 +36548,10 @@ "$ref": "#/definitions/voicemail_keys.dtmf_key", "description": "DTMF key which will return to the main menu when pressed at the config menu" }, + "rewind": { + "$ref": "#/definitions/voicemail_keys.dtmf_key", + "description": "DTMF key which will seek the playback backward for seek_duration_ms ms" + }, "save": { "$ref": "#/definitions/voicemail_keys.dtmf_key", "description": "DTMF key which will save when pressed at the recording review menu" diff --git a/applications/crossbar/priv/couchdb/schemas/kapi.dialplan.playseek.json b/applications/crossbar/priv/couchdb/schemas/kapi.dialplan.playseek.json new file mode 100644 index 00000000000..192da2f7c2f --- /dev/null +++ b/applications/crossbar/priv/couchdb/schemas/kapi.dialplan.playseek.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "_id": "kapi.dialplan.playseek", + "description": "AMQP API for dialplan.playseek", + "properties": { + "Application-Name": { + "enum": [ + "playseek" + ], + "type": "string" + }, + "Call-ID": { + "type": "string" + }, + "Direction": { + "type": "string" + }, + "Duration": { + "type": "string" + }, + "Event-Category": { + "enum": [ + "call" + ], + "type": "string" + }, + "Event-Name": { + "enum": [ + "command" + ], + "type": "string" + }, + "Insert-At": { + "enum": [ + "now" + ], + "type": "string" + } + }, + "required": [ + "Application-Name", + "Call-ID", + "Direction", + "Duration" + ], + "type": "object" +} diff --git a/applications/crossbar/priv/couchdb/schemas/system_config.callflow.json b/applications/crossbar/priv/couchdb/schemas/system_config.callflow.json index ed02f1ab2f9..32a4c8b36bb 100644 --- a/applications/crossbar/priv/couchdb/schemas/system_config.callflow.json +++ b/applications/crossbar/priv/couchdb/schemas/system_config.callflow.json @@ -137,6 +137,11 @@ "description": "If true, ignore the setting on the vmbox and require all users to enter a pin", "type": "boolean" }, + "is_voicemail_ff_rw_enabled": { + "default": false, + "description": "callflow allow fastforward and rewind during voicemail message playback", + "type": "boolean" + }, "max_box_number_length": { "default": 15, "description": "callflow maximum box number length", @@ -192,6 +197,12 @@ "description": "callflow save after notify", "type": "boolean" }, + "seek_duration_ms": { + "default": 10000, + "description": "callflow fastforward and rewind seek duration", + "minimum": 0, + "type": "integer" + }, "vm_message_forward_type": { "default": "only_forward", "description": "Enable or disable the ability to prepend a message when forwarding a voicemail message", diff --git a/applications/crossbar/priv/couchdb/schemas/vmboxes.json b/applications/crossbar/priv/couchdb/schemas/vmboxes.json index 585515913fe..81ef44330cd 100644 --- a/applications/crossbar/priv/couchdb/schemas/vmboxes.json +++ b/applications/crossbar/priv/couchdb/schemas/vmboxes.json @@ -21,6 +21,11 @@ "support_level": "supported", "type": "boolean" }, + "is_voicemail_ff_rw_enabled": { + "default": false, + "description": "callflow allow fastforward and rewind during voicemail message playback", + "type": "boolean" + }, "mailbox": { "description": "The voicemail box number", "maxLength": 30, @@ -118,6 +123,12 @@ "support_level": "supported", "type": "boolean" }, + "seek_duration_ms": { + "default": 10000, + "description": "callflow fastforward and rewind seek duration", + "minimum": 0, + "type": "integer" + }, "skip_envelope": { "default": false, "description": "Determines if the envelope should be skipped", diff --git a/applications/crossbar/priv/couchdb/schemas/voicemail_keys.json b/applications/crossbar/priv/couchdb/schemas/voicemail_keys.json index e966dd474fa..12efe24d332 100644 --- a/applications/crossbar/priv/couchdb/schemas/voicemail_keys.json +++ b/applications/crossbar/priv/couchdb/schemas/voicemail_keys.json @@ -19,6 +19,10 @@ "$ref": "voicemail_keys.dtmf_key", "description": "DTMF key which will exit the main menu when pressed" }, + "fastforward": { + "$ref": "voicemail_keys.dtmf_key", + "description": "DTMF key which will seek the playback forward for seek_duration_ms ms" + }, "hear_new": { "$ref": "voicemail_keys.dtmf_key", "description": "DTMF key which will play new voicemail messages when pressed at the main menu" @@ -71,6 +75,10 @@ "$ref": "voicemail_keys.dtmf_key", "description": "DTMF key which will return to the main menu when pressed at the config menu" }, + "rewind": { + "$ref": "voicemail_keys.dtmf_key", + "description": "DTMF key which will seek the playback backward for seek_duration_ms ms" + }, "save": { "$ref": "voicemail_keys.dtmf_key", "description": "DTMF key which will save when pressed at the recording review menu" diff --git a/applications/ecallmgr/src/call_cmd/ecallmgr_call_command.erl b/applications/ecallmgr/src/call_cmd/ecallmgr_call_command.erl index 99187734101..64a186fca4c 100644 --- a/applications/ecallmgr/src/call_cmd/ecallmgr_call_command.erl +++ b/applications/ecallmgr/src/call_cmd/ecallmgr_call_command.erl @@ -95,6 +95,12 @@ get_fs_app(Node, UUID, JObj, <<"play">>) -> 'true' -> play(Node, UUID, JObj) end; +get_fs_app(Node, UUID, JObj, <<"playseek">>) -> + case kapi_dialplan:playseek_v(JObj) of + 'false' -> {'error', <<"playseek failed to execute as JObj did not validate">>}; + 'true' -> playseek(Node, UUID, JObj) + end; + get_fs_app(_Node, _UUID, JObj, <<"break">>) -> case kapi_dialplan:break_v(JObj) of 'false' -> {'error', <<"break failed to execute as JObj did not validate">>}; @@ -993,6 +999,16 @@ tts(Node, UUID, JObj) -> %% @doc Playback command helpers %% @end %%------------------------------------------------------------------------------ +-spec playseek(atom(), kz_term:ne_binary(), kz_json:object()) -> fs_app(). +playseek(_Node, _UUID, JObj) -> + Duration = kz_json:get_ne_binary_value(<<"Duration">>, JObj), + Args = case kz_json:get_ne_binary_value(<<"Direction">>, JObj) of + <<"fastforward">> -> <<"seek:+", Duration/bytes>>; + <<"rewind">> -> <<"seek:-", Duration/bytes>> + end, + {<<"playseek">>, Args}. + + -spec play(atom(), kz_term:ne_binary(), kz_json:object()) -> fs_apps(). play(Node, UUID, JObj) -> [play_vars(Node, UUID, JObj) diff --git a/applications/ecallmgr/src/ecallmgr_util.erl b/applications/ecallmgr/src/ecallmgr_util.erl index edac989291e..eb821a5348e 100644 --- a/applications/ecallmgr/src/ecallmgr_util.erl +++ b/applications/ecallmgr/src/ecallmgr_util.erl @@ -116,6 +116,12 @@ send_cmd(_Node, _UUID, _App, "kz_multiset_encoded", "^^") -> 'ok'; send_cmd(Node, UUID, App, "playstop", _Args) -> lager:debug("execute on node ~s: ~s uuid_break(~s all)", [Node, App, UUID]), freeswitch:api(Node, 'uuid_break', <>); +send_cmd(Node, UUID, App, "playseek", Cmd) -> + Args = iolist_to_binary([UUID, " ", Cmd]), + lager:debug("execute on node ~s: ~s uuid_fileman(~s)", [Node, App, Args]), + Resp = freeswitch:api(Node, 'uuid_fileman', kz_term:to_list(Args)), + lager:debug("uuid_fileman resulted in: ~p", [Resp]), + Resp; send_cmd(Node, UUID, App, "unbridge", _) -> lager:debug("execute on node ~s: ~s uuid_park(~s)", [Node, App, UUID]), freeswitch:api(Node, 'uuid_park', UUID); diff --git a/core/kazoo_amqp/src/api/kapi_dialplan.erl b/core/kazoo_amqp/src/api/kapi_dialplan.erl index a9d1c3ef890..63a92773283 100644 --- a/core/kazoo_amqp/src/api/kapi_dialplan.erl +++ b/core/kazoo_amqp/src/api/kapi_dialplan.erl @@ -30,6 +30,7 @@ ,execute_extension/1, execute_extension_v/1 ,break/1, break_v/1 ,play/1, play_v/1, playstop/1, playstop_v/1 + ,playseek/1, playseek_v/1 ,tts/1, tts_v/1 ,record/1, record_v/1 ,record_call/1, record_call_v/1 @@ -476,6 +477,24 @@ playstop_v(Prop) when is_list(Prop) -> kz_api:validate(Prop, ?PLAY_STOP_REQ_HEADERS, ?PLAY_STOP_REQ_VALUES, ?PLAY_STOP_REQ_TYPES); playstop_v(JObj) -> playstop_v(kz_json:to_proplist(JObj)). +%%------------------------------------------------------------------------------ +%% @doc Change position in playing media. +%% Takes {@link kz_term:api_term()}, creates JSON string or error. +%% @end +%%------------------------------------------------------------------------------ +-spec playseek(kz_term:api_terms()) -> api_formatter_return(). +playseek(Prop) when is_list(Prop) -> + case playseek_v(Prop) of + 'true' -> kz_api:build_message(Prop, ?PLAY_SEEK_REQ_HEADERS, ?OPTIONAL_PLAY_SEEK_REQ_HEADERS); + 'false' -> {'error', "Proplist failed validation for playseek"} + end; +playseek(JObj) -> playseek(kz_json:to_proplist(JObj)). + +-spec playseek_v(kz_term:api_terms()) -> boolean(). +playseek_v(Prop) when is_list(Prop) -> + kz_api:validate(Prop, ?PLAY_SEEK_REQ_HEADERS, ?PLAY_SEEK_REQ_VALUES, ?PLAY_SEEK_REQ_TYPES); +playseek_v(JObj) -> playseek_v(kz_json:to_proplist(JObj)). + %%------------------------------------------------------------------------------ %% @doc TTS - Text-to-speech. %% Takes {@link kz_term:api_term()}, creates JSON string or error. diff --git a/core/kazoo_amqp/src/api/kapi_dialplan.hrl b/core/kazoo_amqp/src/api/kapi_dialplan.hrl index 464b4baccb1..842514d7bc7 100644 --- a/core/kazoo_amqp/src/api/kapi_dialplan.hrl +++ b/core/kazoo_amqp/src/api/kapi_dialplan.hrl @@ -597,6 +597,16 @@ ]). -define(PLAY_STOP_REQ_TYPES, []). +%% PlaySeek Request +-define(PLAY_SEEK_REQ_HEADERS, [<<"Application-Name">>, <<"Call-ID">>, <<"Duration">>, <<"Direction">>]). +-define(OPTIONAL_PLAY_SEEK_REQ_HEADERS, [<<"Insert-At">>]). +-define(PLAY_SEEK_REQ_VALUES, [{<<"Event-Category">>, <<"call">>} + ,{<<"Event-Name">>, <<"command">>} + ,{<<"Application-Name">>, <<"playseek">>} + ,{<<"Insert-At">>, <<"now">>} + ]). +-define(PLAY_SEEK_REQ_TYPES, [{<<"Duration">>, fun is_integer/1}]). + %% Record Request -define(RECORD_REQ_HEADERS, [<<"Application-Name">>, <<"Call-ID">>, <<"Media-Name">>]). -define(OPTIONAL_RECORD_REQ_HEADERS, [<<"Insert-At">> diff --git a/core/kazoo_call/src/kapps_call_command.erl b/core/kazoo_call/src/kapps_call_command.erl index f49ec5bd0f0..42831496ae2 100644 --- a/core/kazoo_call/src/kapps_call_command.erl +++ b/core/kazoo_call/src/kapps_call_command.erl @@ -76,6 +76,8 @@ ]). -export([prompt/2, prompt/3]). +-export([seek/1, seek/2, seek/3]). + -export([tts/2, tts/3, tts/4, tts/5, tts/6 ,b_tts/2, b_tts/3, b_tts/4, b_tts/5, b_tts/6 ,tts_command/2, tts_command/3, tts_command/4, tts_command/5, tts_command/6 @@ -176,7 +178,7 @@ -export([wait_for_application_or_dtmf/2]). -export([collect_digits/2, collect_digits/3 ,collect_digits/4, collect_digits/5 - ,collect_digits/6 + ,collect_digits/6, collect_digits/7 ]). -export([send_command/2]). @@ -271,6 +273,7 @@ -define(BRIDGE_EXPORT_VARS, kapps_config:get_ne_binaries(?CONFIG_CAT, <<"export_bridge_variables">>, ?BRIDGE_DEFAULT_EXPORT_VARS)). -define(BRIDGE_DEFAULT_EXPORT_VARS, [<<"hold_music">>]). +-define(DEFAULT_SEEK_DURATION, 10000). %%------------------------------------------------------------------------------ %% @doc @@ -1523,6 +1526,40 @@ b_play(Media, Terminators, Leg, Call) -> b_play(Media, Terminators, Leg, Endless, Call) -> wait_for_noop(Call, play(Media, Terminators, Leg, Endless, Call)). +%%------------------------------------------------------------------------------ +%% @doc Produces the low level AMQP request to seek through the playing media. +%% This request will execute immediately. +%% @end +%%------------------------------------------------------------------------------ +-spec seek(kapps_call:call()) -> kapps_api_std_return(). +seek(Call) -> + seek(?DEFAULT_SEEK_DURATION, Call). + +-spec seek(kz_term:api_integer(), kapps_call:call()) -> kapps_api_std_return(). +seek(Duration, Call) when Duration > 0 -> + seek('fastforward', Duration, Call); +seek(Duration, Call) when Duration < 0 -> + seek('rewind', -Duration, Call); +seek(_Duration, _Call) -> + 'ok'. + +-spec seek(atom(), kz_term:api_pos_integer(), kapps_call:call()) -> kapps_api_std_return(). +seek(_Direction, 0, _Call) -> + 'ok'; +seek(Direction, Duration, Call) -> + NoopId = noop_id(), + Command = seek_command(Direction, Duration), + send_command(Command, Call), + NoopId. + +-spec seek_command(atom(), kz_term:api_pos_integer()) -> kz_json:object(). +seek_command(Direction, Duration) -> + kz_json:from_list([{<<"Application-Name">>, <<"playseek">>} + ,{<<"Direction">>, Direction} + ,{<<"Duration">>, Duration} + ,{<<"Insert-At">>, <<"now">>} + ]). + %%------------------------------------------------------------------------------ %% @doc requests the TTS engine to create an audio file to play the desired %% text. @@ -2373,6 +2410,7 @@ b_privacy(Mode, Call) -> ,call :: kapps_call:call() ,digits_collected = <<>> :: binary() ,after_timeout = ?MILLISECONDS_IN_DAY :: pos_integer() + ,flush_on_digit = 'true' }). -type wcc_collect_digits() :: #wcc_collect_digits{}. @@ -2423,15 +2461,28 @@ collect_digits(MaxDigits, Timeout, Interdigit, NoopId, Terminators, Call) -> ,after_timeout=kz_term:to_integer(Timeout) }). +-spec collect_digits(integer(), integer(), integer(), kz_term:api_binary(), list(), boolean(), kapps_call:call()) -> + collect_digits_return(). +collect_digits(MaxDigits, Timeout, Interdigit, NoopId, Terminators, FlushOnDigit, Call) -> + do_collect_digits(#wcc_collect_digits{max_digits=kz_term:to_integer(MaxDigits) + ,timeout=kz_term:to_integer(Timeout) + ,interdigit=kz_term:to_integer(Interdigit) + ,noop_id=NoopId + ,terminators=Terminators + ,call=Call + ,flush_on_digit=FlushOnDigit + }). + -spec do_collect_digits(wcc_collect_digits()) -> collect_digits_return(). do_collect_digits(#wcc_collect_digits{max_digits=MaxDigits ,timeout=Timeout ,interdigit=Interdigit ,noop_id=NoopId - ,terminators=Terminators ,call=Call + ,terminators=Terminators ,digits_collected=Digits ,after_timeout=After + ,flush_on_digit=FlushOnDigit }=Collect) -> Start = os:timestamp(), case receive_event(After) of @@ -2450,8 +2501,7 @@ do_collect_digits(#wcc_collect_digits{max_digits=MaxDigits do_collect_digits(Collect#wcc_collect_digits{after_timeout=kz_time:decr_timeout(After, Start)}); {'dtmf', Digit} -> %% DTMF received, collect and start interdigit timeout - Digits =:= <<>> - andalso flush(Call), + _ = maybe_flash_on_digit(FlushOnDigit, Digits, Call), case lists:member(Digit, Terminators) of 'true' -> @@ -2520,6 +2570,10 @@ handle_collect_digit_event(JObj, _NoopId, {<<"call_event">>, <<"DTMF">>, _}) -> handle_collect_digit_event(_JObj, _NoopId, _EventType) -> {'decrement'}. +-spec maybe_flash_on_digit(boolean(), binary(), kapps_call:call()) -> + kapps_api_std_return(). +maybe_flash_on_digit('true', <<>>, Call) -> flush(Call); +maybe_flash_on_digit(_FlushOnDigit, _Digits, _Call) -> 'ok'. %%------------------------------------------------------------------------------ %% @doc Low level function to consume call events, looping until a specific %% one occurs. If the channel is hungup or no call events are received diff --git a/core/kazoo_documents/src/kzd_vmboxes.erl b/core/kazoo_documents/src/kzd_vmboxes.erl index c221ec3516d..6f6082d5a4e 100644 --- a/core/kazoo_documents/src/kzd_vmboxes.erl +++ b/core/kazoo_documents/src/kzd_vmboxes.erl @@ -9,6 +9,7 @@ -export([check_if_owner/1, check_if_owner/2, set_check_if_owner/2]). -export([delete_after_notify/1, delete_after_notify/2, set_delete_after_notify/2]). -export([is_setup/1, is_setup/2, set_is_setup/2]). +-export([is_voicemail_ff_rw_enabled/1, is_voicemail_ff_rw_enabled/2, set_is_voicemail_ff_rw_enabled/2]). -export([mailbox/1, mailbox/2, set_mailbox/2]). -export([media/1, media/2, set_media/2]). -export([media_unavailable/1, media_unavailable/2, set_media_unavailable/2]). @@ -23,6 +24,7 @@ -export([pin/1, pin/2, set_pin/2]). -export([require_pin/1, require_pin/2, set_require_pin/2]). -export([save_after_notify/1, save_after_notify/2, set_save_after_notify/2]). +-export([seek_duration_ms/1, seek_duration_ms/2, set_seek_duration_ms/2]). -export([skip_envelope/1, skip_envelope/2, set_skip_envelope/2]). -export([skip_greeting/1, skip_greeting/2, set_skip_greeting/2]). -export([skip_instructions/1, skip_instructions/2, set_skip_instructions/2]). @@ -72,6 +74,18 @@ is_setup(Doc) -> is_setup(Doc, Default) -> kz_json:get_boolean_value([<<"is_setup">>], Doc, Default). +-spec is_voicemail_ff_rw_enabled(doc()) -> boolean(). +is_voicemail_ff_rw_enabled(Doc) -> + is_voicemail_ff_rw_enabled(Doc, 'false'). + +-spec is_voicemail_ff_rw_enabled(doc(), Default) -> boolean() | Default. +is_voicemail_ff_rw_enabled(Doc, Default) -> + kz_json:get_boolean_value([<<"is_voicemail_ff_rw_enabled">>], Doc, Default). + +-spec set_is_voicemail_ff_rw_enabled(doc(), boolean()) -> doc(). +set_is_voicemail_ff_rw_enabled(Doc, IsVoicemailFfRwEnabled) -> + kz_json:set_value([<<"is_voicemail_ff_rw_enabled">>], IsVoicemailFfRwEnabled, Doc). + -spec set_is_setup(doc(), boolean()) -> doc(). set_is_setup(Doc, IsSetup) -> kz_json:set_value([<<"is_setup">>], IsSetup, Doc). @@ -244,6 +258,18 @@ save_after_notify(Doc, Default) -> set_save_after_notify(Doc, SaveAfterNotify) -> kz_json:set_value([<<"save_after_notify">>], SaveAfterNotify, Doc). +-spec seek_duration_ms(doc()) -> integer(). +seek_duration_ms(Doc) -> + seek_duration_ms(Doc, 10000). + +-spec seek_duration_ms(doc(), Default) -> integer() | Default. +seek_duration_ms(Doc, Default) -> + kz_json:get_integer_value([<<"seek_duration_ms">>], Doc, Default). + +-spec set_seek_duration_ms(doc(), integer()) -> doc(). +set_seek_duration_ms(Doc, SeekDurationMs) -> + kz_json:set_value([<<"seek_duration_ms">>], SeekDurationMs, Doc). + -spec skip_envelope(doc()) -> boolean(). skip_envelope(Doc) -> skip_envelope(Doc, false). diff --git a/core/kazoo_documents/src/kzd_vmboxes.erl.src b/core/kazoo_documents/src/kzd_vmboxes.erl.src index d233754275b..ccb93790999 100644 --- a/core/kazoo_documents/src/kzd_vmboxes.erl.src +++ b/core/kazoo_documents/src/kzd_vmboxes.erl.src @@ -9,6 +9,7 @@ -export([check_if_owner/1, check_if_owner/2, set_check_if_owner/2]). -export([delete_after_notify/1, delete_after_notify/2, set_delete_after_notify/2]). -export([is_setup/1, is_setup/2, set_is_setup/2]). +-export([is_voicemail_ff_rw_enabled/1, is_voicemail_ff_rw_enabled/2, set_is_voicemail_ff_rw_enabled/2]). -export([mailbox/1, mailbox/2, set_mailbox/2]). -export([media/1, media/2, set_media/2]). -export([media_unavailable/1, media_unavailable/2, set_media_unavailable/2]). @@ -23,6 +24,7 @@ -export([pin/1, pin/2, set_pin/2]). -export([require_pin/1, require_pin/2, set_require_pin/2]). -export([save_after_notify/1, save_after_notify/2, set_save_after_notify/2]). +-export([seek_duration_ms/1, seek_duration_ms/2, set_seek_duration_ms/2]). -export([skip_envelope/1, skip_envelope/2, set_skip_envelope/2]). -export([skip_greeting/1, skip_greeting/2, set_skip_greeting/2]). -export([skip_instructions/1, skip_instructions/2, set_skip_instructions/2]). @@ -76,6 +78,18 @@ is_setup(Doc, Default) -> set_is_setup(Doc, IsSetup) -> kz_json:set_value([<<"is_setup">>], IsSetup, Doc). +-spec is_voicemail_ff_rw_enabled(doc()) -> boolean(). +is_voicemail_ff_rw_enabled(Doc) -> + is_voicemail_ff_rw_enabled(Doc, false). + +-spec is_voicemail_ff_rw_enabled(doc(), Default) -> boolean() | Default. +is_voicemail_ff_rw_enabled(Doc, Default) -> + kz_json:get_boolean_value([<<"is_voicemail_ff_rw_enabled">>], Doc, Default). + +-spec set_is_voicemail_ff_rw_enabled(doc(), boolean()) -> doc(). +set_is_voicemail_ff_rw_enabled(Doc, IsVoicemailFfRwEnabled) -> + kz_json:set_value([<<"is_voicemail_ff_rw_enabled">>], IsVoicemailFfRwEnabled, Doc). + -spec mailbox(doc()) -> kz_term:api_ne_binary(). mailbox(Doc) -> mailbox(Doc, 'undefined'). @@ -244,6 +258,18 @@ save_after_notify(Doc, Default) -> set_save_after_notify(Doc, SaveAfterNotify) -> kz_json:set_value([<<"save_after_notify">>], SaveAfterNotify, Doc). +-spec seek_duration_ms(doc()) -> integer(). +seek_duration_ms(Doc) -> + seek_duration_ms(Doc, 10000). + +-spec seek_duration_ms(doc(), Default) -> integer() | Default. +seek_duration_ms(Doc, Default) -> + kz_json:get_integer_value([<<"seek_duration_ms">>], Doc, Default). + +-spec set_seek_duration_ms(doc(), integer()) -> doc(). +set_seek_duration_ms(Doc, SeekDurationMs) -> + kz_json:set_value([<<"seek_duration_ms">>], SeekDurationMs, Doc). + -spec skip_envelope(doc()) -> boolean(). skip_envelope(Doc) -> skip_envelope(Doc, false). diff --git a/core/kazoo_documents/src/kzd_voicemail_box.erl b/core/kazoo_documents/src/kzd_voicemail_box.erl index c7ebee848f1..d08f8df929a 100644 --- a/core/kazoo_documents/src/kzd_voicemail_box.erl +++ b/core/kazoo_documents/src/kzd_voicemail_box.erl @@ -39,10 +39,13 @@ -define(KEY_PIN, <<"pin">>). -define(KEY_MAILBOX_NUMBER, <<"mailbox">>). -define(KEY_PIN_REQUIRED, <<"require_pin">>). +-define(KEY_IS_FF_RW_ENABLED, <<"is_voicemail_ff_rw_enabled">>). +-define(KEY_SEEK_DURATION, <<"seek_duration_ms">>). -define(KEY_CHECK_IF_OWNER, <<"check_if_owner">>). -define(KEY_IS_SETUP, <<"is_setup">>). -define(PVT_TYPE, <<"vmbox">>). +-define(DEFAULT_SEEK_DURATION, 10000). -define(ACCOUNT_VM_EXTENSION(AccountId), kapps_account_config:get_global(AccountId diff --git a/core/kazoo_documents/src/kzd_voicemail_keys.erl b/core/kazoo_documents/src/kzd_voicemail_keys.erl index 9e96ff91800..ecf2e1da98f 100644 --- a/core/kazoo_documents/src/kzd_voicemail_keys.erl +++ b/core/kazoo_documents/src/kzd_voicemail_keys.erl @@ -10,6 +10,7 @@ -export([continue/1, continue/2, set_continue/2]). -export([delete/1, delete/2, set_delete/2]). -export([exit/1, exit/2, set_exit/2]). +-export([fastforward/1, fastforward/2, set_fastforward/2]). -export([hear_new/1, hear_new/2, set_hear_new/2]). -export([hear_saved/1, hear_saved/2, set_hear_saved/2]). -export([keep/1, keep/2, set_keep/2]). @@ -23,6 +24,7 @@ -export([record/1, record/2, set_record/2]). -export([replay/1, replay/2, set_replay/2]). -export([return_main/1, return_main/2, set_return_main/2]). +-export([rewind/1, rewind/2, set_rewind/2]). -export([save/1, save/2, set_save/2]). -export([set_pin/1, set_pin/2, set_set_pin/2]). @@ -87,6 +89,18 @@ exit(Doc, Default) -> set_exit(Doc, Exit) -> kz_json:set_value([<<"exit">>], Exit, Doc). +-spec fastforward(doc()) -> kz_term:api_ne_binary(). +fastforward(Doc) -> + fastforward(Doc, <<"8">>). + +-spec fastforward(doc(), Default) -> kz_term:ne_binary() | Default. +fastforward(Doc, Default) -> + kz_json:get_ne_binary_value([<<"fastforward">>], Doc, Default). + +-spec set_fastforward(doc(), kz_term:ne_binary()) -> doc(). +set_fastforward(Doc, Fastforward) -> + kz_json:set_value([<<"fastforward">>], Fastforward, Doc). + -spec hear_new(doc()) -> kz_term:ne_binary(). hear_new(Doc) -> hear_new(Doc, <<"1">>). @@ -243,6 +257,18 @@ return_main(Doc, Default) -> set_return_main(Doc, ReturnMain) -> kz_json:set_value([<<"return_main">>], ReturnMain, Doc). +-spec rewind(doc()) -> kz_term:api_ne_binary(). +rewind(Doc) -> + rewind(Doc, <<"5">>). + +-spec rewind(doc(), Default) -> kz_term:ne_binary() | Default. +rewind(Doc, Default) -> + kz_json:get_ne_binary_value([<<"rewind">>], Doc, Default). + +-spec set_rewind(doc(), kz_term:ne_binary()) -> doc(). +set_rewind(Doc, Rewind) -> + kz_json:set_value([<<"rewind">>], Rewind, Doc). + -spec save(doc()) -> kz_term:ne_binary(). save(Doc) -> save(Doc, <<"1">>). diff --git a/core/kazoo_documents/src/kzd_voicemail_keys.erl.src b/core/kazoo_documents/src/kzd_voicemail_keys.erl.src index f499c958e34..af58340aae1 100644 --- a/core/kazoo_documents/src/kzd_voicemail_keys.erl.src +++ b/core/kazoo_documents/src/kzd_voicemail_keys.erl.src @@ -10,6 +10,7 @@ -export([continue/1, continue/2, set_continue/2]). -export([delete/1, delete/2, set_delete/2]). -export([exit/1, exit/2, set_exit/2]). +-export([fastforward/1, fastforward/2, set_fastforward/2]). -export([hear_new/1, hear_new/2, set_hear_new/2]). -export([hear_saved/1, hear_saved/2, set_hear_saved/2]). -export([keep/1, keep/2, set_keep/2]). @@ -23,6 +24,7 @@ -export([record/1, record/2, set_record/2]). -export([replay/1, replay/2, set_replay/2]). -export([return_main/1, return_main/2, set_return_main/2]). +-export([rewind/1, rewind/2, set_rewind/2]). -export([save/1, save/2, set_save/2]). -export([set_pin/1, set_pin/2, set_set_pin/2]). @@ -86,6 +88,18 @@ exit(Doc, Default) -> set_exit(Doc, Exit) -> kz_json:set_value([<<"exit">>], Exit, Doc). +-spec fastforward(doc()) -> kz_term:api_ne_binary(). +fastforward(Doc) -> + fastforward(Doc, 'undefined'). + +-spec fastforward(doc(), Default) -> kz_term:ne_binary() | Default. +fastforward(Doc, Default) -> + kz_json:get_ne_binary_value([<<"fastforward">>], Doc, Default). + +-spec set_fastforward(doc(), kz_term:ne_binary()) -> doc(). +set_fastforward(Doc, Fastforward) -> + kz_json:set_value([<<"fastforward">>], Fastforward, Doc). + -spec hear_new(doc()) -> kz_term:api_ne_binary(). hear_new(Doc) -> hear_new(Doc, 'undefined'). @@ -242,6 +256,18 @@ return_main(Doc, Default) -> set_return_main(Doc, ReturnMain) -> kz_json:set_value([<<"return_main">>], ReturnMain, Doc). +-spec rewind(doc()) -> kz_term:api_ne_binary(). +rewind(Doc) -> + rewind(Doc, 'undefined'). + +-spec rewind(doc(), Default) -> kz_term:ne_binary() | Default. +rewind(Doc, Default) -> + kz_json:get_ne_binary_value([<<"rewind">>], Doc, Default). + +-spec set_rewind(doc(), kz_term:ne_binary()) -> doc(). +set_rewind(Doc, Rewind) -> + kz_json:set_value([<<"rewind">>], Rewind, Doc). + -spec save(doc()) -> kz_term:api_ne_binary(). save(Doc) -> save(Doc, 'undefined').