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