diff --git a/CHANGES b/CHANGES index 2600c05eba95d55594663863a6a3f0a8aef9b5cb..6e78fb02d5fee936f3c783114b269aee283bb575 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,15 @@ AMI (Asterisk Manager Interface) 'Manager Show Command' now displays the privileges needed for using a given manager command instead. + * Added new action "ControlPlayback". The ControlPlayback action allows an AMI + client to manipulate audio currently being played back on a channel. The + supported operations depend on the application being used to send audio to + the channel. When the audio playback was initiated using the ControlPlayback + application or CONTROL STREAM FILE AGI command, the audio can be paused, + stopped, restarted, reversed, or skipped forward. When initiated by other + mechanisms (such as the Playback application), the audio can be stopped, + reversed, or skipped forward. + Channel Drivers ------------------ diff --git a/apps/app_controlplayback.c b/apps/app_controlplayback.c index 1e2e6fbc20f31dee31bcfac8dcb6ac445b79e9bd..c27fd1c52c56f58af8c279cd94f0aac533884610 100644 --- a/apps/app_controlplayback.c +++ b/apps/app_controlplayback.c @@ -36,6 +36,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/pbx.h" #include "asterisk/app.h" #include "asterisk/module.h" +#include "asterisk/manager.h" +#include "asterisk/utils.h" +#include "asterisk/astobj2.h" /*** DOCUMENTATION <application name="ControlPlayback" language="en_US"> @@ -82,6 +85,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Contains the status of the attempt as a text string</para> <value name="SUCCESS" /> <value name="USERSTOPPED" /> + <value name="REMOTESTOPPED" /> <value name="ERROR" /> </variable> <variable name="CPLAYBACKOFFSET"> @@ -95,6 +99,69 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </variablelist> </description> </application> + <manager name="ControlPlayback" language="en_US"> + <synopsis> + Control the playback of a file being played to a channel. + </synopsis> + <syntax> + <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> + <parameter name="Channel" required="true"> + <para>The name of the channel that currently has a file being played back to it.</para> + </parameter> + <parameter name="Control" required="true"> + <enumlist> + <enum name="stop"> + <para>Stop the playback operation.</para> + </enum> + <enum name="forward"> + <para>Move the current position in the media forward. The amount + of time that the stream moves forward is determined by the + <replaceable>skipms</replaceable> value passed to the application + that initiated the playback.</para> + <note> + <para>The default skipms value is <literal>3000</literal> ms.</para> + </note> + </enum> + <enum name="reverse"> + <para>Move the current position in the media backward. The amount + of time that the stream moves backward is determined by the + <replaceable>skipms</replaceable> value passed to the application + that initiated the playback.</para> + <note> + <para>The default skipms value is <literal>3000</literal> ms.</para> + </note> + </enum> + <enum name="pause"> + <para>Pause/unpause the playback operation, if supported. + If not supported, stop the playback.</para> + </enum> + <enum name="restart"> + <para>Restart the playback operation, if supported. + If not supported, stop the playback.</para> + </enum> + </enumlist> + </parameter> + </syntax> + <description> + <para>Control the operation of a media file being played back to a channel. + Note that this AMI action does not initiate playback of media to channel, but + rather controls the operation of a media operation that was already initiated + on the channel.</para> + <note> + <para>The <literal>pause</literal> and <literal>restart</literal> + <replaceable>Control</replaceable> options will stop a playback + operation if that operation was not initiated from the + <replaceable>ControlPlayback</replaceable> application or the + <replaceable>control stream file</replaceable> AGI command.</para> + </note> + </description> + <see-also> + <ref type="application">Playback</ref> + <ref type="application">ControlPlayback</ref> + <ref type="agi">stream file</ref> + <ref type="agi">control stream file</ref> + </see-also> + </manager> ***/ static const char app[] = "ControlPlayback"; @@ -201,6 +268,9 @@ static int controlplayback_exec(struct ast_channel *chan, const char *data) snprintf(stopkeybuf, sizeof(stopkeybuf), "%c", res); pbx_builtin_setvar_helper(chan, "CPLAYBACKSTOPKEY", stopkeybuf); res = 0; + } else if (res > 0 && res == AST_CONTROL_STREAM_STOP) { + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "REMOTESTOPPED"); + res = 0; } else { if (res < 0) { res = 0; @@ -215,16 +285,67 @@ static int controlplayback_exec(struct ast_channel *chan, const char *data) return res; } +static int controlplayback_manager(struct mansession *s, const struct message *m) +{ + const char *channel_name = astman_get_header(m, "Channel"); + const char *control_type = astman_get_header(m, "Control"); + struct ast_channel *chan; + + if (ast_strlen_zero(channel_name)) { + astman_send_error(s, m, "Channel not specified"); + return 0; + } + + if (ast_strlen_zero(control_type)) { + astman_send_error(s, m, "Control not specified"); + return 0; + } + + chan = ast_channel_get_by_name(channel_name); + if (!chan) { + astman_send_error(s, m, "No such channel"); + return 0; + } + + if (!strcasecmp(control_type, "stop")) { + ast_queue_control(chan, AST_CONTROL_STREAM_STOP); + } else if (!strcasecmp(control_type, "forward")) { + ast_queue_control(chan, AST_CONTROL_STREAM_FORWARD); + } else if (!strcasecmp(control_type, "reverse")) { + ast_queue_control(chan, AST_CONTROL_STREAM_REVERSE); + } else if (!strcasecmp(control_type, "pause")) { + ast_queue_control(chan, AST_CONTROL_STREAM_SUSPEND); + } else if (!strcasecmp(control_type, "restart")) { + ast_queue_control(chan, AST_CONTROL_STREAM_RESTART); + } else { + astman_send_error(s, m, "Unknown control type"); + chan = ast_channel_unref(chan); + return 0; + } + + chan = ast_channel_unref(chan); + astman_send_ack(s, m, NULL); + return 0; +} + static int unload_module(void) { - int res; - res = ast_unregister_application(app); + int res = 0; + + res |= ast_unregister_application(app); + res |= ast_manager_unregister("ControlPlayback"); + return res; } static int load_module(void) { - return ast_register_application_xml(app, controlplayback_exec); + int res = 0; + + res |= ast_register_application_xml(app, controlplayback_exec); + res |= ast_manager_register_xml("ControlPlayback", EVENT_FLAG_CALL, controlplayback_manager); + + return res; } AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application"); diff --git a/apps/app_playback.c b/apps/app_playback.c index 18d4c8eb55d0e0f94fa54fb45a7ea7b302cc800b..12b1ff6981fb99f8e33a3860164146d0c1e48675 100644 --- a/apps/app_playback.c +++ b/apps/app_playback.c @@ -82,6 +82,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>See Also: Background (application) -- for playing sound files that are interruptible</para> <para>WaitExten (application) -- wait for digits from caller, optionally play music on hold</para> </description> + <see-also> + <ref type="application">Background</ref> + <ref type="application">WaitExten</ref> + <ref type="application">ControlPlayback</ref> + <ref type="agi">stream file</ref> + <ref type="agi">control stream file</ref> + <ref type="manager">ControlPlayback</ref> + </see-also> </application> ***/ @@ -473,11 +481,12 @@ static int playback_exec(struct ast_channel *chan, const char *data) res = say_full(chan, front, "", ast_channel_language(chan), NULL, -1, -1); else res = ast_streamfile(chan, front, ast_channel_language(chan)); - if (!res) { - res = ast_waitstream(chan, ""); + if (!res) { + res = ast_waitstream(chan, ""); ast_stopstream(chan); - } else { - ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char *)data); + } + if (res) { + ast_log(LOG_WARNING, "Playback failed on %s for %s\n", ast_channel_name(chan), (char *)data); res = 0; mres = 1; } diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c index 8e12aafb3547e609359d1fde41d85b074383d536..45da9691a635eba60e135bb5b1f1e979e0cb58bb 100644 --- a/funcs/func_frame_trace.c +++ b/funcs/func_frame_trace.c @@ -327,8 +327,23 @@ static void print_frame(struct ast_frame *frame) case AST_CONTROL_PVT_CAUSE_CODE: ast_verbose("SubClass: PVT_CAUSE_CODE\n"); break; + case AST_CONTROL_STREAM_STOP: + ast_verbose("SubClass: STREAM_STOP\n"); + break; + case AST_CONTROL_STREAM_SUSPEND: + ast_verbose("SubClass: STREAM_SUSPEND\n"); + break; + case AST_CONTROL_STREAM_RESTART: + ast_verbose("SubClass: STREAM_RESTART\n"); + break; + case AST_CONTROL_STREAM_REVERSE: + ast_verbose("SubClass: STREAM_REVERSE\n"); + break; + case AST_CONTROL_STREAM_FORWARD: + ast_verbose("SubClass: STREAM_FORWARD\n"); + break; } - + if (frame->subclass.integer == -1) { ast_verbose("SubClass: %d\n", frame->subclass.integer); } diff --git a/include/asterisk/file.h b/include/asterisk/file.h index ec2a38e1f70979dbbdbbd02a338080b4077fd5c7..0b2f913ad8f0c2a28cc402501bd1d6c871d2a972 100644 --- a/include/asterisk/file.h +++ b/include/asterisk/file.h @@ -137,46 +137,47 @@ int ast_filedelete(const char *filename, const char *fmt); */ int ast_filecopy(const char *oldname, const char *newname, const char *fmt); -/*! +/*! * \brief Waits for a stream to stop or digit to be pressed * \param c channel to waitstream on * \param breakon string of DTMF digits to break upon * Begins playback of a stream... - * Wait for a stream to stop or for any one of a given digit to arrive, + * Wait for a stream to stop or for any one of a given digit to arrive, * \retval 0 if the stream finishes - * \retval the character if it was interrupted, - * \retval -1 on error + * \retval the character if it was interrupted by the channel. + * \retval -1 on error */ int ast_waitstream(struct ast_channel *c, const char *breakon); -/*! - * \brief Waits for a stream to stop or digit matching a valid one digit exten to be pressed +/*! + * \brief Waits for a stream to stop or digit matching a valid one digit exten to be pressed * \param c channel to waitstream on * \param context string of context to match digits to break upon * Begins playback of a stream... - * Wait for a stream to stop or for any one of a valid extension digit to arrive, + * Wait for a stream to stop or for any one of a valid extension digit to arrive, * \retval 0 if the stream finishes. * \retval the character if it was interrupted. * \retval -1 on error. */ int ast_waitstream_exten(struct ast_channel *c, const char *context); -/*! - * \brief Same as waitstream but allows stream to be forwarded or rewound +/*! + * \brief Same as waitstream but allows stream to be forwarded or rewound * \param c channel to waitstream on * \param breakon string of DTMF digits to break upon * \param forward DTMF digit to fast forward upon * \param rewind DTMF digit to rewind upon * \param ms How many miliseconds to skip forward/back * Begins playback of a stream... - * Wait for a stream to stop or for any one of a given digit to arrive, + * Wait for a stream to stop or for any one of a given digit to arrive, * \retval 0 if the stream finishes. - * \retval the character if it was interrupted. + * \retval the character if it was interrupted, + * \retval the value of the control frame if it was interrupted by some other party, * \retval -1 on error. */ int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms); -/*! +/*! * \brief Same as waitstream_fr but allows a callback to be alerted when a user * fastforwards or rewinds the file. * \param c channel to waitstream on @@ -184,11 +185,12 @@ int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *fo * \param forward DTMF digit to fast forward upon * \param rewind DTMF digit to rewind upon * \param ms How many milliseconds to skip forward/back - * \param cb to call when rewind or fastfoward occurs. + * \param cb to call when rewind or fastfoward occurs. * Begins playback of a stream... - * Wait for a stream to stop or for any one of a given digit to arrive, + * Wait for a stream to stop or for any one of a given digit to arrive, * \retval 0 if the stream finishes. - * \retval the character if it was interrupted. + * \retval the character if it was interrupted, + * \retval the value of the control frame if it was interrupted by some other party, * \retval -1 on error. */ int ast_waitstream_fr_w_cb(struct ast_channel *c, diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 01aa27b616aa7c49c172bfc5923803bed9d85058..5e81b4e18c6593d5d4e5ac4986692e453333fcb7 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -267,6 +267,16 @@ enum ast_control_frame_type { AST_CONTROL_MCID = 31, /*!< Indicate that the caller is being malicious. */ AST_CONTROL_UPDATE_RTP_PEER = 32, /*!< Interrupt the bridge and have it update the peer */ AST_CONTROL_PVT_CAUSE_CODE = 33, /*!< Contains an update to the protocol-specific cause-code stored for branching dials */ + + /* Control frames used to manipulate a stream on a channel. The values for these + * must be greater than the allowed value for a 8-bit char, so that they avoid + * conflicts with DTMF values. */ + AST_CONTROL_STREAM_STOP = 1000, /*!< Indicate to a channel in playback to stop the stream */ + AST_CONTROL_STREAM_SUSPEND = 1001, /*!< Indicate to a channel in playback to suspend the stream */ + AST_CONTROL_STREAM_RESTART = 1002, /*!< Indicate to a channel in playback to restart the stream */ + AST_CONTROL_STREAM_REVERSE = 1003, /*!< Indicate to a channel in playback to rewind */ + AST_CONTROL_STREAM_FORWARD = 1004, /*!< Indicate to a channel in playback to fast forward */ + }; enum ast_frame_read_action { diff --git a/main/app.c b/main/app.c index 208db4b838eabea5dfae9253717e110822c9d490..6db65f37117fb84ca99cad36d14cf59c51f85459 100644 --- a/main/app.c +++ b/main/app.c @@ -1004,24 +1004,37 @@ static int control_streamfile(struct ast_channel *chan, } /* We go at next loop if we got the restart char */ - if (restart && strchr(restart, res)) { + if ((restart && strchr(restart, res)) || res == AST_CONTROL_STREAM_RESTART) { ast_debug(1, "we'll restart the stream here at next loop\n"); pause_restart_point = 0; + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n", + ast_channel_name(chan), + "Restart"); continue; } - if (suspend && strchr(suspend, res)) { + if ((suspend && strchr(suspend, res)) || res == AST_CONTROL_STREAM_SUSPEND) { pause_restart_point = ast_tellstream(ast_channel_stream(chan)); + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n", + ast_channel_name(chan), + "Pause"); for (;;) { ast_stopstream(chan); if (!(res = ast_waitfordigit(chan, 1000))) { continue; - } else if (res == -1 || strchr(suspend, res) || (stop && strchr(stop, res))) { + } else if (res == -1 || (suspend && strchr(suspend, res)) || (stop && strchr(stop, res)) + || res == AST_CONTROL_STREAM_SUSPEND || res == AST_CONTROL_STREAM_STOP) { break; } } - if (res == *suspend) { + if ((suspend && (res == *suspend)) || res == AST_CONTROL_STREAM_SUSPEND) { res = 0; + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n", + ast_channel_name(chan), + "Unpause"); continue; } } @@ -1031,7 +1044,11 @@ static int control_streamfile(struct ast_channel *chan, } /* if we get one of our stop chars, return it to the calling function */ - if (stop && strchr(stop, res)) { + if ((stop && strchr(stop, res)) || res == AST_CONTROL_STREAM_STOP) { + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n", + ast_channel_name(chan), + "Stop"); break; } } @@ -1050,11 +1067,6 @@ static int control_streamfile(struct ast_channel *chan, *offsetms = offset / 8; /* samples --> ms ... XXX Assumes 8 kHz */ } - /* If we are returning a digit cast it as char */ - if (res > 0 || ast_channel_stream(chan)) { - res = (char)res; - } - ast_stopstream(chan); return res; diff --git a/main/channel.c b/main/channel.c index 048975d1249a7503133955f4be7aadcb609cc014..dee6fe3211f33855055e4eae5f1c753fb3393360 100644 --- a/main/channel.c +++ b/main/channel.c @@ -3700,6 +3700,17 @@ int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, int audiofd, in ast_frfree(f); ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY); return -1; + case AST_CONTROL_STREAM_STOP: + case AST_CONTROL_STREAM_SUSPEND: + case AST_CONTROL_STREAM_RESTART: + case AST_CONTROL_STREAM_REVERSE: + case AST_CONTROL_STREAM_FORWARD: + /* Fall-through and treat as if it were a DTMF signal. Items + * that perform stream control will handle this. */ + res = f->subclass.integer; + ast_frfree(f); + ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY); + return res; case AST_CONTROL_PVT_CAUSE_CODE: case AST_CONTROL_RINGING: case AST_CONTROL_ANSWER: @@ -4454,6 +4465,11 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con case AST_CONTROL_MCID: case AST_CONTROL_UPDATE_RTP_PEER: case AST_CONTROL_PVT_CAUSE_CODE: + case AST_CONTROL_STREAM_STOP: + case AST_CONTROL_STREAM_SUSPEND: + case AST_CONTROL_STREAM_REVERSE: + case AST_CONTROL_STREAM_FORWARD: + case AST_CONTROL_STREAM_RESTART: break; case AST_CONTROL_INCOMPLETE: @@ -4661,6 +4677,11 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, case AST_CONTROL_END_OF_Q: case AST_CONTROL_MCID: case AST_CONTROL_UPDATE_RTP_PEER: + case AST_CONTROL_STREAM_STOP: + case AST_CONTROL_STREAM_SUSPEND: + case AST_CONTROL_STREAM_REVERSE: + case AST_CONTROL_STREAM_FORWARD: + case AST_CONTROL_STREAM_RESTART: /* Nothing left to do for these. */ res = 0; break; diff --git a/main/file.c b/main/file.c index db8fd5c02d2139477bd7bd5c71b511e97789c424..79b4e84869c640b5490cdd807788e33bc758b580 100644 --- a/main/file.c +++ b/main/file.c @@ -1240,6 +1240,45 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con return fs; } +static void waitstream_control(struct ast_channel *c, + enum ast_waitstream_fr_cb_values type, + ast_waitstream_fr_cb cb, + int skip_ms) +{ + switch (type) + { + case AST_WAITSTREAM_CB_FASTFORWARD: + { + int eoftest; + ast_stream_fastforward(ast_channel_stream(c), skip_ms); + eoftest = fgetc(ast_channel_stream(c)->f); + if (feof(ast_channel_stream(c)->f)) { + ast_stream_rewind(ast_channel_stream(c), skip_ms); + } else { + ungetc(eoftest, ast_channel_stream(c)->f); + } + } + break; + case AST_WAITSTREAM_CB_REWIND: + ast_stream_rewind(ast_channel_stream(c), skip_ms); + break; + default: + break; + } + + if (cb) { + long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000); + cb(c, ms_len, type); + } + + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n" + "SkipMs: %d\r\n", + ast_channel_name(c), + (type == AST_WAITSTREAM_CB_FASTFORWARD) ? "FastForward" : "Rewind", + skip_ms); +} + /*! * \brief the core of all waitstream() functions */ @@ -1336,34 +1375,49 @@ static int waitstream_core(struct ast_channel *c, return res; } } else { - enum ast_waitstream_fr_cb_values cb_val = 0; res = fr->subclass.integer; if (strchr(forward, res)) { - int eoftest; - ast_stream_fastforward(ast_channel_stream(c), skip_ms); - eoftest = fgetc(ast_channel_stream(c)->f); - if (feof(ast_channel_stream(c)->f)) { - ast_stream_rewind(ast_channel_stream(c), skip_ms); - } else { - ungetc(eoftest, ast_channel_stream(c)->f); - } - cb_val = AST_WAITSTREAM_CB_FASTFORWARD; + waitstream_control(c, AST_WAITSTREAM_CB_FASTFORWARD, cb, skip_ms); } else if (strchr(reverse, res)) { - ast_stream_rewind(ast_channel_stream(c), skip_ms); - cb_val = AST_WAITSTREAM_CB_REWIND; + waitstream_control(c, AST_WAITSTREAM_CB_REWIND, cb, skip_ms); } else if (strchr(breakon, res)) { + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n", + ast_channel_name(c), + "Break"); + ast_frfree(fr); ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY); return res; } - if (cb_val && cb) { - long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000); - cb(c, ms_len, cb_val); - } } break; case AST_FRAME_CONTROL: switch (fr->subclass.integer) { + case AST_CONTROL_STREAM_STOP: + case AST_CONTROL_STREAM_SUSPEND: + case AST_CONTROL_STREAM_RESTART: + /* Fall-through and break out */ + ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n" + "Control: %s\r\n", + ast_channel_name(c), + "Break"); + res = fr->subclass.integer; + ast_frfree(fr); + ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY); + return res; + case AST_CONTROL_STREAM_REVERSE: + if (!skip_ms) { + skip_ms = 3000; + } + waitstream_control(c, AST_WAITSTREAM_CB_REWIND, cb, skip_ms); + break; + case AST_CONTROL_STREAM_FORWARD: + if (!skip_ms) { + skip_ms = 3000; + } + waitstream_control(c, AST_WAITSTREAM_CB_FASTFORWARD, cb, skip_ms); + break; case AST_CONTROL_HANGUP: case AST_CONTROL_BUSY: case AST_CONTROL_CONGESTION: @@ -1427,26 +1481,62 @@ int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *fo -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, NULL /* no callback */); } +/*! \internal + * \brief Clean up the return value of a waitstream call + * + * It's possible for a control frame to come in from an external source and break the + * playback. From a consumer of most ast_waitstream_* function callers, this should + * appear like normal playback termination, i.e., return 0 and not the value of the + * control frame. + */ +static int sanitize_waitstream_return(int return_value) +{ + switch (return_value) { + case AST_CONTROL_STREAM_STOP: + case AST_CONTROL_STREAM_SUSPEND: + case AST_CONTROL_STREAM_RESTART: + /* Fall through and set return_value to 0 */ + return_value = 0; + break; + default: + /* Do nothing */ + break; + } + + return return_value; +} + int ast_waitstream(struct ast_channel *c, const char *breakon) { - return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL, NULL /* no callback */); + int res; + + res = waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL, NULL /* no callback */); + + return sanitize_waitstream_return(res); } int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd) { - return waitstream_core(c, breakon, NULL, NULL, 0, + int res; + + res = waitstream_core(c, breakon, NULL, NULL, 0, audiofd, cmdfd, NULL /* no context */, NULL /* no callback */); + + return sanitize_waitstream_return(res); } int ast_waitstream_exten(struct ast_channel *c, const char *context) { + int res; + /* Waitstream, with return in the case of a valid 1 digit extension */ /* in the current or specified context being pressed */ - if (!context) context = ast_channel_context(c); - return waitstream_core(c, NULL, NULL, NULL, 0, + res = waitstream_core(c, NULL, NULL, NULL, 0, -1, -1, context, NULL /* no callback */); + + return sanitize_waitstream_return(res); } /* diff --git a/res/res_agi.c b/res/res_agi.c index b92ccdbdc2c3eab1a69146cdd77a4f77c8b8aa33..0a20bbdf954ddabba79bc72b704235eb4337ee90 100644 --- a/res/res_agi.c +++ b/res/res_agi.c @@ -160,6 +160,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") permitted. Returns <literal>0</literal> if playback completes without a digit being pressed, or the ASCII numerical value of the digit if one was pressed, or <literal>-1</literal> on error or if the channel was disconnected.</para> + <para>It sets the following channel variables upon completion:</para> + <variablelist> + <variable name="CPLAYBACKSTATUS"> + <para>Contains the status of the attempt as a text string</para> + <value name="SUCCESS" /> + <value name="USERSTOPPED" /> + <value name="REMOTESTOPPED" /> + <value name="ERROR" /> + </variable> + <variable name="CPLAYBACKOFFSET"> + <para>Contains the offset in ms into the file where playback + was at when it stopped. <literal>-1</literal> is end of file.</para> + </variable> + <variable name="CPLAYBACKSTOPKEY"> + <para>If the playback is stopped by the user this variable contains + the key that was pressed.</para> + </variable> + </variablelist> </description> </agi> <agi name="database del" language="en_US"> @@ -652,6 +670,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") or <literal>-1</literal> on error or if the channel was disconnected. If musiconhold is playing before calling stream file it will be automatically stopped and will not be restarted after completion.</para> + <para>It sets the following channel variables upon completion:</para> + <variablelist> + <variable name="PLAYBACKSTATUS"> + <para>The status of the playback attempt as a text string.</para> + <value name="SUCCESS"/> + <value name="FAILED"/> + </variable> + </variablelist> </description> <see-also> <ref type="agi">control stream file</ref> @@ -1984,6 +2010,9 @@ static int handle_controlstreamfile(struct ast_channel *chan, AGI *agi, int argc { int res = 0, skipms = 3000; const char *fwd = "#", *rev = "*", *suspend = NULL, *stop = NULL; /* Default values */ + char stopkeybuf[2]; + long offsetms = 0; + char offsetbuf[20]; if (argc < 5 || argc > 9) { return RESULT_SHOWUSAGE; @@ -2011,6 +2040,25 @@ static int handle_controlstreamfile(struct ast_channel *chan, AGI *agi, int argc res = ast_control_streamfile(chan, argv[3], fwd, rev, stop, suspend, NULL, skipms, NULL); + /* If we stopped on one of our stop keys, return 0 */ + if (res > 0 && stop && strchr(stop, res)) { + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED"); + snprintf(stopkeybuf, sizeof(stopkeybuf), "%c", res); + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTOPKEY", stopkeybuf); + } else if (res > 0 && res == AST_CONTROL_STREAM_STOP) { + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "REMOTESTOPPED"); + res = 0; + } else { + if (res < 0) { + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR"); + } else { + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS"); + } + } + + snprintf(offsetbuf, sizeof(offsetbuf), "%ld", offsetms); + pbx_builtin_setvar_helper(chan, "CPLAYBACKOFFSET", offsetbuf); + ast_agi_send(agi->fd, chan, "200 result=%d\n", res); return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; @@ -2068,6 +2116,8 @@ static int handle_streamfile(struct ast_channel *chan, AGI *agi, int argc, const return RESULT_SUCCESS; } ast_agi_send(agi->fd, chan, "200 result=%d endpos=%ld\n", res, sample_offset); + pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", res ? "FAILED" : "SUCCESS"); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; }