Index: apps/app_controlplayback.c
===================================================================
--- apps/app_controlplayback.c	(revision 46117)
+++ apps/app_controlplayback.c	(working copy)
@@ -66,7 +66,32 @@
 "  CPLAYBACKSTATUS -  This variable contains the status of the attempt as a text\n"
 "                     string, one of: SUCCESS | USERSTOPPED | ERROR\n";
 
+static const char *app2 = "ControlPlayback2";
 
+static const char *synopsis2 = "Play a file with fast forward and rewind (multispeed)";
+
+static const char *descrip2 = 
+"  ControlPlayback(file[|b^skipms^ff^rew[|b^skipms^ff^rew][|b^skipms^ff^rew][|stop[|pause[|restart|options]]]]):\n"
+"This application will play back the given filename. By default, the '*' key\n"
+"can be used to rewind, and the '#' key can be used to fast-forward.\n"
+"You can specify up to 3 pairs of keys with different skip time to perfom\n"
+"multispeed fastforwarding and rewinding.\n"
+"Parameters:\n"
+"  b       - This is a start tag of key pair. Each pair of keys should start from 'b'\n"
+"  skipms  - This is number of milliseconds to skip when rewinding or\n"
+"            fast-forwarding.\n"
+"  ff      - Fast-forward when this DTMF digit is received.\n"
+"  rew     - Rewind when this DTMF digit is received.\n"
+"  stop    - Stop playback when this DTMF digit is received.\n"
+"  pause   - Pause playback when this DTMF digit is received.\n"
+"  restart - Restart playback when this DTMF digit is received.\n"
+"Options:\n"
+"  j - Jump to priority n+101 if the requested file is not found.\n"
+"This application sets the following channel variable upon completion:\n"
+"  CPLAYBACKSTATUS -  This variable contains the status of the attempt as a text\n"
+"                     string, one of: SUCCESS | USERSTOPPED | ERROR\n";
+
+
 static int is_on_phonepad(char key)
 {
 	return key == 35 || key == 42 || (key >= 48 && key <= 57);
@@ -153,16 +178,118 @@
 	return res;
 }
 
+static int controlplayback2_exec(struct ast_channel *chan, void *data)
+{
+	int res = 0, priority_jump = 0;
+	int skipms1 = 0, skipms2 = 0, skipms3 = 0;
+	char *fwd1 = "#", *rew1 = "*", *fwd2 = "#", *rew2 = "*", *fwd3 = "#", *rew3 = "*";
+	char *stop=NULL, *pause=NULL, *restart=NULL;
+	struct ast_module_user *u;
+	char *tmp;
+	int argc;
+	char *argv[8];
+	
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "ControlPlayback2 requires an argument (filename)\n");
+		return -1;
+	}
+
+	u = ast_module_user_add(chan);
+	
+	tmp = ast_strdupa(data);
+	memset(argv, 0, sizeof(argv));
+
+	argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
+
+	if (argc < 1) {
+		ast_log(LOG_WARNING, "ControlPlayback2 requires an argument (filename)\n");
+		ast_module_user_remove(u);
+		return -1;
+	}
+
+	int i, keypairs = 0;
+	for(i=1;i<argc;i++) {
+		if(argv[i][0] == 'b' && i <= 3) {
+			char *buttons[4];
+			tmp = ast_strdupa(argv[i]);
+			int vals = ast_app_separate_args(tmp, '^', buttons, sizeof(buttons) / sizeof(buttons[0]));
+			if (vals != 4 ) {
+				ast_log(LOG_WARNING, "parameter '%s' should contain 4 values separated with '^'\n",argv[i]);
+				ast_module_user_remove(u);
+				return -1;
+			}
+			if(keypairs == 0 ) {
+				skipms1 = buttons[1] ? atoi(buttons[1]) : 3000;
+				if (!skipms1)
+					skipms1 = 3000;
+				fwd1 = buttons[2];
+				rew1 = buttons[3];
+			} else if(keypairs == 1 ) {
+				skipms2 = buttons[1] ? atoi(buttons[1]) : 3000;
+				if (!skipms2)
+					skipms2 = 3000;
+				fwd2 = buttons[2];
+				rew2 = buttons[3];
+			} else {
+				skipms3 = buttons[1] ? atoi(buttons[1]) : 3000;
+				if (!skipms3)
+					skipms3 = 3000;
+				fwd3 = buttons[2];
+				rew3 = buttons[3];
+			}
+			keypairs++;
+		} else {
+			if(i - keypairs == 1) { /* stop[|pause[|restart|options */
+				stop = argv[i];
+			} else if(i - keypairs == 2) {
+				pause = argv[i];
+			} else if(i - keypairs == 3) {
+				restart = argv[i];
+			} else {
+				if (strchr(argv[i], 'j'))
+					priority_jump = 1;
+			}
+		}
+	}
+	res = ast_control_streamfile2(chan, argv[0], fwd1, rew1, fwd2, rew2, fwd3, rew3, stop, pause, restart, skipms1, skipms2, skipms3);
+
+	/* If we stopped on one of our stop keys, return 0  */
+	if (stop == res) {
+		res = 0;
+		pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED");
+	} else {
+		if (res < 0) {
+			if (priority_jump || ast_opt_priority_jumping) {
+				if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
+					ast_log(LOG_WARNING, "ControlPlayback2 tried to jump to priority n+101 as requested, but priority didn't exist\n");
+				}
+			}
+			res = 0;
+			pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR");
+		} else
+			pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS");
+	}
+
+	ast_module_user_remove(u);
+
+	return res;
+}
+
 static int unload_module(void)
 {
 	int res;
 	res = ast_unregister_application(app);
+	res |= ast_unregister_application(app2);
 	return res;
 }
 
 static int load_module(void)
 {
-	return ast_register_application(app, controlplayback_exec, synopsis, descrip);
+	int res;
+	res = ast_register_application(app, controlplayback_exec, synopsis, descrip);
+	res |= ast_register_application(app2, controlplayback2_exec, synopsis2, descrip2);
+	return res;
+	
 }
 
 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application");
Index: include/asterisk/file.h
===================================================================
--- include/asterisk/file.h	(revision 46117)
+++ include/asterisk/file.h	(working copy)
@@ -249,6 +249,20 @@
  */
 int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms);
 
+/*! Same as waitstream_fr but allows stream to be forwarded or rewound with different speeds */
+/*!
+ * \param c channel to waitstram on
+ * \param breakon string of DTMF digits to break upon
+ * \param forwardX DTMF digit to fast forward upon
+ * \param rewindX DTMF digit to rewind upon
+ * \param msX 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,  Returns 0 
+ * if the stream finishes, the character if it was interrupted, and -1 on error 
+ */
+int ast_waitstream_multispeed_fr(struct ast_channel *c, const char *breakon, const char *forward1, const char *rewind1, int ms1, const char *forward2, const char *rewind2, int ms2, const char *forward3, const char *rewind3, int ms3);
+
+
 /* Same as waitstream, but with audio output to fd and monitored fd checking.  Returns
    1 if monfd is ready for reading */
 int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int monfd);
Index: main/app.c
===================================================================
--- main/app.c	(revision 46117)
+++ main/app.c	(working copy)
@@ -451,6 +451,104 @@
 	return res;
 }
 
+int ast_control_streamfile2(struct ast_channel *chan, const char *file,
+			   const char *fwd1, const char *rev1,
+			   const char *fwd2, const char *rev2,
+			   const char *fwd3, const char *rev3,
+			   const char *stop, const char *pause,
+			   const char *restart, int skipms1, int skipms2, int skipms3) 
+{
+	char *breaks = NULL;
+	char *end = NULL;
+	int blen = 2;
+	int res;
+	long pause_restart_point = 0;
+
+	if (stop)
+		blen += strlen(stop);
+	if (pause)
+		blen += strlen(pause);
+	if (restart)
+		blen += strlen(restart);
+
+	if (blen > 2) {
+		breaks = alloca(blen + 1);
+		breaks[0] = '\0';
+		if (stop)
+			strcat(breaks, stop);
+		if (pause)
+			strcat(breaks, pause);
+		if (restart)
+			strcat(breaks, restart);
+	}
+	if (chan->_state != AST_STATE_UP)
+		res = ast_answer(chan);
+
+	if (file) {
+		if ((end = strchr(file,':'))) {
+			if (!strcasecmp(end, ":end")) {
+				*end = '\0';
+				end++;
+			}
+		}
+	}
+
+	for (;;) {
+		ast_stopstream(chan);
+		res = ast_streamfile(chan, file, chan->language);
+		if (!res) {
+			if (pause_restart_point) {
+				ast_seekstream(chan->stream, pause_restart_point, SEEK_SET);
+				pause_restart_point = 0;
+			}
+			else if (end) {
+				ast_seekstream(chan->stream, 0, SEEK_END);
+				end = NULL;
+			};
+			/*res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms); // replacement */
+			res = ast_waitstream_multispeed_fr(chan, breaks, fwd1, rev1, skipms1, fwd2, rev2, skipms2, fwd3, rev3, skipms3);
+		}
+
+		if (res < 1)
+			break;
+
+		/* We go at next loop if we got the restart char */
+		if (restart && strchr(restart, res)) {
+			if (option_debug)
+				ast_log(LOG_DEBUG, "we'll restart the stream here at next loop\n");
+			pause_restart_point = 0;
+			continue;
+		}
+
+		if (pause && strchr(pause, res)) {
+			pause_restart_point = ast_tellstream(chan->stream);
+			for (;;) {
+				ast_stopstream(chan);
+				res = ast_waitfordigit(chan, 1000);
+				if (!res)
+					continue;
+				else if (res == -1 || strchr(pause, res) || (stop && strchr(stop, res)))
+					break;
+			}
+			if (res == *pause) {
+				res = 0;
+				continue;
+			}
+		}
+
+		if (res == -1)
+			break;
+
+		/* if we get one of our stop chars, return it to the calling function */
+		if (stop && strchr(stop, res))
+			break;
+	}
+
+	ast_stopstream(chan);
+
+	return res;
+}
+
 int ast_play_and_wait(struct ast_channel *chan, const char *fn)
 {
 	int d;
Index: main/file.c
===================================================================
--- main/file.c	(revision 46117)
+++ main/file.c	(working copy)
@@ -1084,6 +1084,126 @@
 	return (c->_softhangup ? -1 : 0);
 }
 
+
+/*!
+ * \brief the core of all waitstream_multispeed() functions
+ */
+static int waitstream_multispeed_core(struct ast_channel *c, const char *breakon,
+	const char *forward1, const char *rewind1, int skip_ms1,
+	const char *forward2, const char *rewind2, int skip_ms2,
+	const char *forward3, const char *rewind3, int skip_ms3,
+	int audiofd, int cmdfd,  const char *context)
+{
+	if (!breakon)
+		breakon = "";
+	if (!forward1)
+		forward1 = "";
+	if (!rewind1)
+		rewind1 = "";
+	
+	while (c->stream) {
+		int res;
+		int ms = ast_sched_wait(c->sched);
+		if (ms < 0 && !c->timingfunc) {
+			ast_stopstream(c);
+			break;
+		}
+		if (ms < 0)
+			ms = 1000;
+		if (cmdfd < 0) {
+			res = ast_waitfor(c, ms);
+			if (res < 0) {
+				ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno));
+				return res;
+			}
+		} else {
+			int outfd;
+			struct ast_channel *rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms);
+			if (!rchan && (outfd < 0) && (ms)) {
+				/* Continue */
+				if (errno == EINTR)
+					continue;
+				ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno));
+				return -1;
+			} else if (outfd > -1) { /* this requires cmdfd set */
+				/* The FD we were watching has something waiting */
+				return 1;
+			}
+			/* if rchan is set, it is 'c' */
+			res = rchan ? 1 : 0; /* map into 'res' values */
+		}
+		if (res > 0) {
+			struct ast_frame *fr = ast_read(c);
+			if (!fr)
+				return -1;
+			switch(fr->frametype) {
+			case AST_FRAME_DTMF_END:
+				if (context) {
+					const char exten[2] = { fr->subclass, '\0' };
+					if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) {
+						ast_frfree(fr);
+						return res;
+					}
+				} else {
+					res = fr->subclass;
+					if (strchr(forward1,res)) {
+						ast_stream_fastforward(c->stream, skip_ms1);
+					} else if (strchr(forward2,res)) {
+						ast_stream_fastforward(c->stream, skip_ms2);
+					} else if (strchr(forward3,res)) {
+						ast_stream_fastforward(c->stream, skip_ms3);
+					} else if (strchr(rewind1,res)) {
+						ast_stream_rewind(c->stream, skip_ms1);
+					} else if (strchr(rewind2,res)) {
+						ast_stream_rewind(c->stream, skip_ms2);
+					} else if (strchr(rewind3,res)) {
+						ast_stream_rewind(c->stream, skip_ms3);
+					} else if (strchr(breakon, res)) {
+						ast_frfree(fr);
+						return res;
+					}					
+				}
+				break;
+			case AST_FRAME_CONTROL:
+				switch(fr->subclass) {
+				case AST_CONTROL_HANGUP:
+				case AST_CONTROL_BUSY:
+				case AST_CONTROL_CONGESTION:
+					ast_frfree(fr);
+					return -1;
+				case AST_CONTROL_RINGING:
+				case AST_CONTROL_ANSWER:
+				case AST_CONTROL_VIDUPDATE:
+				case AST_CONTROL_HOLD:
+				case AST_CONTROL_UNHOLD:
+					/* Unimportant */
+					break;
+				default:
+					ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass);
+				}
+				break;
+			case AST_FRAME_VOICE:
+				/* Write audio if appropriate */
+				if (audiofd > -1)
+					write(audiofd, fr->data, fr->datalen);
+			default:
+				/* Ignore all others */
+				break;
+			}
+			ast_frfree(fr);
+		}
+		ast_sched_runq(c->sched);
+	}
+	return (c->_softhangup ? -1 : 0);
+}
+
+int ast_waitstream_multispeed_fr(struct ast_channel *c, const char *breakon, const char *forward1, const char *rewind1, int ms1, const char *forward2, const char *rewind2, int ms2, const char *forward3, const char *rewind3, int ms3)
+{
+	return waitstream_multispeed_core(c, breakon, forward1, rewind1, ms1, forward2, rewind2, ms2, forward3, rewind3, ms3,
+		-1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */);
+}
+
+
 int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms)
 {
 	return waitstream_core(c, breakon, forward, rewind, ms,
