Add ControlPlayback manager action
authorMatthew Jordan <mjordan@digium.com>
Tue, 22 Jan 2013 15:16:20 +0000 (15:16 +0000)
committerMatthew Jordan <mjordan@digium.com>
Tue, 22 Jan 2013 15:16:20 +0000 (15:16 +0000)
This patch adds the capability for asynchronous manipulation of audio being
played back to a channel though a new AMI action "ControlPlayback". The
ControlPlayback action supports a number of operations, the availability of
which 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.

Review: https://reviewboard.asterisk.org/r/2265/

(closes issue ASTERISK-20882)
Reported by: mjordan

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@379830 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
apps/app_controlplayback.c
apps/app_playback.c
funcs/func_frame_trace.c
include/asterisk/file.h
include/asterisk/frame.h
main/app.c
main/channel.c
main/file.c
res/res_agi.c

diff --git a/CHANGES b/CHANGES
index 2600c05..6e78fb0 100644 (file)
--- 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
 ------------------
 
index 1e2e6fb..c27fd1c 100644 (file)
@@ -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");
index 18d4c8e..12b1ff6 100644 (file)
@@ -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;
                        }
index 8e12aaf..45da969 100644 (file)
@@ -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);
                }
index ec2a38e..0b2f913 100644 (file)
@@ -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,
index 01aa27b..5e81b4e 100644 (file)
@@ -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 {
index 208db4b..6db65f3 100644 (file)
@@ -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;
index 048975d..dee6fe3 100644 (file)
@@ -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;
index db8fd5c..79b4e84 100644 (file)
@@ -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);
 }
 
 /*
index b92ccdb..0a20bbd 100644 (file)
@@ -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;
 }