Index: apps/app_controlplayback.c
===================================================================
--- apps/app_controlplayback.c	(revision 62670)
+++ apps/app_controlplayback.c	(working copy)
@@ -32,6 +32,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <ctype.h>
 
 #include "asterisk/lock.h"
 #include "asterisk/file.h"
@@ -49,7 +50,7 @@
 static const char *synopsis = "Play a file with fast forward and rewind";
 
 static const char *descrip = 
-"  ControlPlayback(file[|skipms[|ff[|rew[|stop[|pause[|restart|options]]]]]]]):\n"
+"  ControlPlayback(file[|skipms[|ff[|rew[|stop[|pause[|restart[|options[|ms2[|ff2[|rew2[|ms3[|ff3[|rew3...]]]]]]]]]]]]]):\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"
 "Parameters:\n"
@@ -59,37 +60,24 @@
 "  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"
+"  restart - Restart playback when this DTMF digit is received.\n\n"
+"  msX,ffX,rewX - additional parameters to specify control keys for\n"
+"                 fastforwarding/rewinding and skip time."
 "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);
-}
-
 static int controlplayback_exec(struct ast_channel *chan, void *data)
 {
 	int res = 0, priority_jump = 0;
-	int skipms = 0;
+	char *stop=NULL, *pause=NULL, *restart=NULL;
 	struct ast_module_user *u;
-	char *tmp;
-	int argc;
-	char *argv[8];
-	enum arg_ids {
-		arg_file = 0,
-		arg_skip = 1,
-		arg_fwd = 2,
-		arg_rev = 3,
-		arg_stop = 4,
-		arg_pause = 5,
-		arg_restart = 6,
-		options = 7,
-	};
+	char *tmp,*tmp2;
+	int argc,i;
+	char **argv;
+	struct ast_playcontrols *blist, *blast;
 	
 	if (ast_strlen_zero(data)) {
 		ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
@@ -98,41 +86,113 @@
 
 	u = ast_module_user_add(chan);
 	
+	tmp2 = ast_strdupa(data);
+	int params=0;
+	tmp2 = strchr(tmp2,'|');
+
+	while(tmp2 = strchr(tmp2,'|')) {
+		params++;
+		tmp2++;
+	}
+	params++;
+
+	argv = calloc(params,sizeof(char*));
+
+	memset(argv, 0, sizeof(argv));
 	tmp = ast_strdupa(data);
-	memset(argv, 0, sizeof(argv));
+	argc = ast_app_separate_args(tmp, '|', argv, params);
 
-	argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
-
 	if (argc < 1) {
 		ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
 		ast_module_user_remove(u);
 		return -1;
 	}
 
-	skipms = argv[arg_skip] ? atoi(argv[arg_skip]) : 3000;
-	if (!skipms)
-		skipms = 3000;
+	blist = calloc(1,sizeof(struct ast_playcontrols));
+	blast = blist;
+	blist->next = NULL;
+	blist->btn_ff = "#";
+	blist->btn_rew = "*";
+	blist->skipms = 3000;
 
-	if (!argv[arg_fwd] || !is_on_phonepad(*argv[arg_fwd]))
-		argv[arg_fwd] = "#";
-	if (!argv[arg_rev] || !is_on_phonepad(*argv[arg_rev]))
-		argv[arg_rev] = "*";
-	if (argv[arg_stop] && !is_on_phonepad(*argv[arg_stop]))
-		argv[arg_stop] = NULL;
-	if (argv[arg_pause] && !is_on_phonepad(*argv[arg_pause]))
-		argv[arg_pause] = NULL;
-	if (argv[arg_restart] && !is_on_phonepad(*argv[arg_restart]))
-		argv[arg_restart] = NULL;
+	int limit = 8;
+	if (params < limit)
+		limit = params;
 
-	if (argv[options]) {
-		if (strchr(argv[options], 'j'))
-			priority_jump = 1;
+
+	/* base parameters */
+	for (i=0; i<limit; i++) {
+		if (i == 0) {
+			/* filename */
+			if (ast_strlen_zero(argv[i])) {
+				ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
+				return -1;
+			}
+		} else if (i == 1) {
+			/* skipms   */
+			if (!ast_strlen_zero(argv[i]) && isdigit(argv[i][0]))
+				blist->skipms = atoi(argv[i]);
+			else
+				blist->skipms = 3000;
+			if(blist->skipms <= 0)
+				blist->skipms = 3000;
+		} else if (i == 2) {
+			/* ff       */
+			blist->btn_ff = argv[i];
+		} else if (i == 3) {
+			/* rew      */
+			blist->btn_rew = argv[i];
+		} else if (i == 4) {
+			/* stop     */
+			stop = argv[i];
+		} else if (i == 5) {
+			/* pause    */
+			pause = argv[i];
+		} else if (i == 6) {
+			/* restart  */
+			restart = argv[i];
+		} else if (i == 7) {
+			/* options  */
+			if (strchr(argv[i], 'j'))
+				priority_jump = 1;
+		}
 	}
+	/* Extra Params */
+	for(i = limit; i < params; i+=3) {
+		blast->next = calloc(1,sizeof(struct ast_playcontrols));
+		blast = blast->next;
+		blast->next = NULL;
+		if(i + 0 < params) {
+			blast->skipms = atoi(argv[i + 0]);
+			if (blast->skipms <= 0)
+				blast->skipms = 3000;
+		} else {
+			blast->skipms = 3000;
+		}
+		if(i + 1 < params) {
+			blast->btn_ff = argv[i + 1];
+		} else {
+			blast->btn_ff = "";
+		}
+		if(i + 2 < params) {
+			blast->btn_rew = argv[i + 2];
+		} else {
+			blast->btn_rew = "";
+		}
+	}
+	
+	res = ast_control_streamfile_multispeed(chan, argv[0], stop, pause, restart, blist);
 
-	res = ast_control_streamfile(chan, argv[arg_file], argv[arg_fwd], argv[arg_rev], argv[arg_stop], argv[arg_pause], argv[arg_restart], skipms);
+	/* Destroy ast_playcontrols list */
+	
+	while(blist) {
+		blast = blist->next;
+		free(blist);
+		blist = blast;
+	}
 
 	/* If we stopped on one of our stop keys, return 0  */
-	if (argv[arg_stop] && strchr(argv[arg_stop], res)) {
+	if (stop && strchr(stop, res)) {
 		res = 0;
 		pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED");
 	} else {
@@ -147,7 +207,7 @@
 		} else
 			pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS");
 	}
-
+	free(argv);
 	ast_module_user_remove(u);
 
 	return res;
@@ -162,7 +222,10 @@
 
 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);
+	return res;
+	
 }
 
 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application");
Index: include/asterisk/app.h
===================================================================
--- include/asterisk/app.h	(revision 62670)
+++ include/asterisk/app.h	(working copy)
@@ -20,6 +20,7 @@
  look and feel to Asterisk apps.
  */
 
+#include <asterisk/file.h>
 #ifndef _ASTERISK_APP_H
 #define _ASTERISK_APP_H
 
@@ -166,6 +167,9 @@
 /*! Stream a file with fast forward, pause, reverse, restart. */
 int ast_control_streamfile(struct ast_channel *chan, const char *file, const char *fwd, const char *rev, const char *stop, const char *pause, const char *restart, int skipms);
 
+/*! Stream a file with fast forward, pause, reverse, restart. */
+int ast_control_streamfile_multispeed(struct ast_channel *chan, const char *file, const char *stop, const char *pause, const char *restart, struct ast_playcontrols *blist);
+
 /*! Play a stream and wait for a digit, returning the digit that was pressed */
 int ast_play_and_wait(struct ast_channel *chan, const char *fn);
 
Index: include/asterisk/file.h
===================================================================
--- include/asterisk/file.h	(revision 62670)
+++ include/asterisk/file.h	(working copy)
@@ -135,6 +135,13 @@
 	void *private;	/* pointer to private buffer */
 };
 
+struct ast_playcontrols {
+	char *btn_ff;
+	char *btn_rew;
+	int skipms;
+	struct ast_playcontrols *next;
+};
+
 #define SEEK_FORCECUR	10
 	
 /*! Register a new file format capability
@@ -248,6 +255,19 @@
  */
 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, struct ast_playcontrols *blist);
+
 /* 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 62670)
+++ main/app.c	(working copy)
@@ -476,6 +476,101 @@
 	return res;
 }
 
+int ast_control_streamfile_multispeed(struct ast_channel *chan, const char *file,
+			   const char *stop, const char *pause,
+			   const char *restart, struct ast_playcontrols *blist) 
+{
+	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, blist);
+		}
+
+		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 62670)
+++ main/file.c	(working copy)
@@ -1075,6 +1075,116 @@
 	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,
+	int audiofd, int cmdfd,  const char *context, struct ast_playcontrols *blist)
+{
+	if (!breakon)
+		breakon = "";
+	
+	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(breakon, res)) {
+						ast_frfree(fr);
+						return res;
+					}
+					struct ast_playcontrols *cur_btn = blist;
+					while(cur_btn) {
+						if (strchr(cur_btn->btn_ff,res)) {
+							ast_stream_fastforward(c->stream, cur_btn->skipms);
+						} else if (strchr(cur_btn->btn_rew,res)) {
+							ast_stream_rewind(c->stream, cur_btn->skipms);
+						}
+						cur_btn = cur_btn->next;
+					}
+				}
+				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, struct ast_playcontrols *blist)
+{
+	return waitstream_multispeed_core(c, breakon, 
+		-1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, blist);
+}
+
 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,
