summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES9
-rw-r--r--apps/app_controlplayback.c127
-rw-r--r--apps/app_playback.c17
-rw-r--r--funcs/func_frame_trace.c17
-rw-r--r--include/asterisk/file.h32
-rw-r--r--include/asterisk/frame.h10
-rw-r--r--main/app.c32
-rw-r--r--main/channel.c21
-rw-r--r--main/file.c130
-rw-r--r--res/res_agi.c50
10 files changed, 392 insertions, 53 deletions
diff --git a/CHANGES b/CHANGES
index 2600c05eb..6e78fb02d 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 1e2e6fbc2..c27fd1c52 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 18d4c8eb5..12b1ff698 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 8e12aafb3..45da9691a 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 ec2a38e1f..0b2f913ad 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 01aa27b61..5e81b4e18 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 208db4b83..6db65f371 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 048975d12..dee6fe321 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 db8fd5c02..79b4e8486 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 b92ccdbdc..0a20bbdf9 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;
}