[asterisk-commits] mjordan: trunk r379830 - in /trunk: ./ apps/ funcs/ include/asterisk/ main/ res/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue Jan 22 09:16:27 CST 2013


Author: mjordan
Date: Tue Jan 22 09:16:20 2013
New Revision: 379830

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=379830
Log:
Add ControlPlayback manager action

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


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

Modified: trunk/CHANGES
URL: http://svnview.digium.com/svn/asterisk/trunk/CHANGES?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/CHANGES (original)
+++ trunk/CHANGES Tue Jan 22 09:16:20 2013
@@ -31,6 +31,15 @@
    than 15 characters and no longer shows authorization requirement for commands.
    '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
 ------------------

Modified: trunk/apps/app_controlplayback.c
URL: http://svnview.digium.com/svn/asterisk/trunk/apps/app_controlplayback.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/apps/app_controlplayback.c (original)
+++ trunk/apps/app_controlplayback.c Tue Jan 22 09:16:20 2013
@@ -36,6 +36,9 @@
 #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 @@
 					<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 @@
 			</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 @@
 		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 @@
 	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");

Modified: trunk/apps/app_playback.c
URL: http://svnview.digium.com/svn/asterisk/trunk/apps/app_playback.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/apps/app_playback.c (original)
+++ trunk/apps/app_playback.c Tue Jan 22 09:16:20 2013
@@ -82,6 +82,14 @@
 			<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 @@
 				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;
 			}

Modified: trunk/funcs/func_frame_trace.c
URL: http://svnview.digium.com/svn/asterisk/trunk/funcs/func_frame_trace.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/funcs/func_frame_trace.c (original)
+++ trunk/funcs/func_frame_trace.c Tue Jan 22 09:16:20 2013
@@ -327,8 +327,23 @@
 		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);
 		}

Modified: trunk/include/asterisk/file.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/file.h?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/include/asterisk/file.h (original)
+++ trunk/include/asterisk/file.h Tue Jan 22 09:16:20 2013
@@ -137,46 +137,47 @@
  */
 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 @@
  * \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,

Modified: trunk/include/asterisk/frame.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/frame.h?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/include/asterisk/frame.h (original)
+++ trunk/include/asterisk/frame.h Tue Jan 22 09:16:20 2013
@@ -267,6 +267,16 @@
 	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 {

Modified: trunk/main/app.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/app.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/main/app.c (original)
+++ trunk/main/app.c Tue Jan 22 09:16:20 2013
@@ -1004,24 +1004,37 @@
 		}
 
 		/* 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 @@
 		}
 
 		/* 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;
 		}
 	}
@@ -1048,11 +1065,6 @@
 
 	if (offsetms) {
 		*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);

Modified: trunk/main/channel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/channel.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/main/channel.c (original)
+++ trunk/main/channel.c Tue Jan 22 09:16:20 2013
@@ -3700,6 +3700,17 @@
 					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 @@
 	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 @@
 	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;

Modified: trunk/main/file.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/file.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/main/file.c (original)
+++ trunk/main/file.c Tue Jan 22 09:16:20 2013
@@ -1240,6 +1240,45 @@
 	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 @@
 						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 @@
 		-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);
 }
 
 /*

Modified: trunk/res/res_agi.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_agi.c?view=diff&rev=379830&r1=379829&r2=379830
==============================================================================
--- trunk/res/res_agi.c (original)
+++ trunk/res/res_agi.c Tue Jan 22 09:16:20 2013
@@ -160,6 +160,24 @@
 			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 @@
 			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 @@
 {
 	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;
@@ -2010,6 +2039,25 @@
 	}
 
 	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);
 
@@ -2068,6 +2116,8 @@
 		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;
 }
 




More information about the asterisk-commits mailing list