c27fd1c52c56f58af8c279cd94f0aac533884610
[asterisk/asterisk.git] / apps / app_controlplayback.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Trivial application to control playback of a sound file
22  *
23  * \author Mark Spencer <markster@digium.com>
24  *
25  * \ingroup applications
26  */
27
28 /*** MODULEINFO
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35
36 #include "asterisk/pbx.h"
37 #include "asterisk/app.h"
38 #include "asterisk/module.h"
39 #include "asterisk/manager.h"
40 #include "asterisk/utils.h"
41 #include "asterisk/astobj2.h"
42
43 /*** DOCUMENTATION
44         <application name="ControlPlayback" language="en_US">
45                 <synopsis>
46                         Play a file with fast forward and rewind.
47                 </synopsis>
48                 <syntax>
49                         <parameter name="filename" required="true" />
50                         <parameter name="skipms">
51                                 <para>This is number of milliseconds to skip when rewinding or
52                                 fast-forwarding.</para>
53                         </parameter>
54                         <parameter name="ff">
55                                 <para>Fast-forward when this DTMF digit is received. (defaults to <literal>#</literal>)</para>
56                         </parameter>
57                         <parameter name="rew">
58                                 <para>Rewind when this DTMF digit is received. (defaults to <literal>*</literal>)</para>
59                         </parameter>
60                         <parameter name="stop">
61                                 <para>Stop playback when this DTMF digit is received.</para>
62                         </parameter>
63                         <parameter name="pause">
64                                 <para>Pause playback when this DTMF digit is received.</para>
65                         </parameter>
66                         <parameter name="restart">
67                                 <para>Restart playback when this DTMF digit is received.</para>
68                         </parameter>
69                         <parameter name="options">
70                                 <optionlist>
71                                         <option name="o">
72                                                 <argument name="time" required="true">
73                                                         <para>Start at <replaceable>time</replaceable> ms from the
74                                                         beginning of the file.</para>
75                                                 </argument>
76                                         </option>
77                                 </optionlist>
78                         </parameter>
79                 </syntax>
80                 <description>
81                         <para>This application will play back the given <replaceable>filename</replaceable>.</para>
82                         <para>It sets the following channel variables upon completion:</para>
83                         <variablelist>
84                                 <variable name="CPLAYBACKSTATUS">
85                                         <para>Contains the status of the attempt as a text string</para>
86                                         <value name="SUCCESS" />
87                                         <value name="USERSTOPPED" />
88                                         <value name="REMOTESTOPPED" />
89                                         <value name="ERROR" />
90                                 </variable>
91                                 <variable name="CPLAYBACKOFFSET">
92                                         <para>Contains the offset in ms into the file where playback
93                                         was at when it stopped. <literal>-1</literal> is end of file.</para>
94                                 </variable>
95                                 <variable name="CPLAYBACKSTOPKEY">
96                                         <para>If the playback is stopped by the user this variable contains
97                                         the key that was pressed.</para>
98                                 </variable>
99                         </variablelist>
100                 </description>
101         </application>
102         <manager name="ControlPlayback" language="en_US">
103                 <synopsis>
104                         Control the playback of a file being played to a channel.
105                 </synopsis>
106                 <syntax>
107                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
108                         <parameter name="Channel" required="true">
109                                 <para>The name of the channel that currently has a file being played back to it.</para>
110                         </parameter>
111                         <parameter name="Control" required="true">
112                                 <enumlist>
113                                         <enum  name="stop">
114                                                 <para>Stop the playback operation.</para>
115                                         </enum>
116                                         <enum name="forward">
117                                                 <para>Move the current position in the media forward. The amount
118                                                 of time that the stream moves forward is determined by the
119                                                 <replaceable>skipms</replaceable> value passed to the application
120                                                 that initiated the playback.</para>
121                                                 <note>
122                                                         <para>The default skipms value is <literal>3000</literal> ms.</para>
123                                                 </note>
124                                         </enum>
125                                         <enum name="reverse">
126                                                 <para>Move the current position in the media backward. The amount
127                                                 of time that the stream moves backward is determined by the
128                                                 <replaceable>skipms</replaceable> value passed to the application
129                                                 that initiated the playback.</para>
130                                                 <note>
131                                                         <para>The default skipms value is <literal>3000</literal> ms.</para>
132                                                 </note>
133                                         </enum>
134                                         <enum name="pause">
135                                                 <para>Pause/unpause the playback operation, if supported.
136                                                 If not supported, stop the playback.</para>
137                                         </enum>
138                                         <enum name="restart">
139                                                 <para>Restart the playback operation, if supported.
140                                                 If not supported, stop the playback.</para>
141                                         </enum>
142                                 </enumlist>
143                         </parameter>
144                 </syntax>
145                 <description>
146                         <para>Control the operation of a media file being played back to a channel.
147                         Note that this AMI action does not initiate playback of media to channel, but
148                         rather controls the operation of a media operation that was already initiated
149                         on the channel.</para>
150                         <note>
151                                 <para>The <literal>pause</literal> and <literal>restart</literal>
152                                 <replaceable>Control</replaceable> options will stop a playback
153                                 operation if that operation was not initiated from the
154                                 <replaceable>ControlPlayback</replaceable> application or the
155                                 <replaceable>control stream file</replaceable> AGI command.</para>
156                         </note>
157                 </description>
158                 <see-also>
159                         <ref type="application">Playback</ref>
160                         <ref type="application">ControlPlayback</ref>
161                         <ref type="agi">stream file</ref>
162                         <ref type="agi">control stream file</ref>
163                 </see-also>
164         </manager>
165  ***/
166 static const char app[] = "ControlPlayback";
167
168 enum {
169         OPT_OFFSET = (1 << 1),
170 };
171
172 enum {
173         OPT_ARG_OFFSET = 0,
174         /* must stay as the last entry ... */
175         OPT_ARG_ARRAY_LEN,
176 };
177
178 AST_APP_OPTIONS(cpb_opts, BEGIN_OPTIONS
179         AST_APP_OPTION_ARG('o', OPT_OFFSET, OPT_ARG_OFFSET),
180         END_OPTIONS
181 );
182
183 static int is_on_phonepad(char key)
184 {
185         return key == 35 || key == 42 || (key >= 48 && key <= 57);
186 }
187
188 static int is_argument(const char *haystack, int needle)
189 {
190         if (ast_strlen_zero(haystack))
191                 return 0;
192
193         if (strchr(haystack, needle))
194                 return -1;
195
196         return 0;
197 }
198
199 static int controlplayback_exec(struct ast_channel *chan, const char *data)
200 {
201         int res = 0;
202         int skipms = 0;
203         long offsetms = 0;
204         char offsetbuf[20];
205         char stopkeybuf[2];
206         char *tmp;
207         struct ast_flags opts = { 0, };
208         char *opt_args[OPT_ARG_ARRAY_LEN];
209         AST_DECLARE_APP_ARGS(args,
210                 AST_APP_ARG(filename);
211                 AST_APP_ARG(skip);
212                 AST_APP_ARG(fwd);
213                 AST_APP_ARG(rev);
214                 AST_APP_ARG(stop);
215                 AST_APP_ARG(pause);
216                 AST_APP_ARG(restart);
217                 AST_APP_ARG(options);
218         );
219
220         if (ast_strlen_zero(data)) {
221                 ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
222                 return -1;
223         }
224         
225         tmp = ast_strdupa(data);
226         AST_STANDARD_APP_ARGS(args, tmp);
227
228         if (args.argc < 1) {
229                 ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
230                 return -1;
231         }
232
233         skipms = args.skip ? (atoi(args.skip) ? atoi(args.skip) : 3000) : 3000;
234
235         if (!args.fwd || !is_on_phonepad(*args.fwd)) {
236                 char *digit = "#";
237                 if (!is_argument(args.rev, *digit) && !is_argument(args.stop, *digit) && !is_argument(args.pause, *digit) && !is_argument(args.restart, *digit))
238                         args.fwd = digit;
239                 else
240                         args.fwd = NULL;
241         }
242         if (!args.rev || !is_on_phonepad(*args.rev)) {
243                 char *digit = "*";
244                 if (!is_argument(args.fwd, *digit) && !is_argument(args.stop, *digit) && !is_argument(args.pause, *digit) && !is_argument(args.restart, *digit))
245                         args.rev = digit;
246                 else
247                         args.rev = NULL;
248         }
249         ast_debug(1, "Forward key = %s, Rewind key = %s\n", args.fwd, args.rev);
250         if (args.stop && !is_on_phonepad(*args.stop))
251                 args.stop = NULL;
252         if (args.pause && !is_on_phonepad(*args.pause))
253                 args.pause = NULL;
254         if (args.restart && !is_on_phonepad(*args.restart))
255                 args.restart = NULL;
256
257         if (args.options) {
258                 ast_app_parse_options(cpb_opts, &opts, opt_args, args.options);
259                 if (ast_test_flag(&opts, OPT_OFFSET))
260                         offsetms = atol(opt_args[OPT_ARG_OFFSET]);
261         }
262
263         res = ast_control_streamfile(chan, args.filename, args.fwd, args.rev, args.stop, args.pause, args.restart, skipms, &offsetms);
264
265         /* If we stopped on one of our stop keys, return 0  */
266         if (res > 0 && args.stop && strchr(args.stop, res)) {
267                 pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED");
268                 snprintf(stopkeybuf, sizeof(stopkeybuf), "%c", res);
269                 pbx_builtin_setvar_helper(chan, "CPLAYBACKSTOPKEY", stopkeybuf);
270                 res = 0;
271         } else if (res > 0 && res == AST_CONTROL_STREAM_STOP) {
272                 pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "REMOTESTOPPED");
273                 res = 0;
274         } else {
275                 if (res < 0) {
276                         res = 0;
277                         pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR");
278                 } else
279                         pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS");
280         }
281
282         snprintf(offsetbuf, sizeof(offsetbuf), "%ld", offsetms);
283         pbx_builtin_setvar_helper(chan, "CPLAYBACKOFFSET", offsetbuf);
284
285         return res;
286 }
287
288 static int controlplayback_manager(struct mansession *s, const struct message *m)
289 {
290         const char *channel_name = astman_get_header(m, "Channel");
291         const char *control_type = astman_get_header(m, "Control");
292         struct ast_channel *chan;
293
294         if (ast_strlen_zero(channel_name)) {
295                 astman_send_error(s, m, "Channel not specified");
296                 return 0;
297         }
298
299         if (ast_strlen_zero(control_type)) {
300                 astman_send_error(s, m, "Control not specified");
301                 return 0;
302         }
303
304         chan = ast_channel_get_by_name(channel_name);
305         if (!chan) {
306                 astman_send_error(s, m, "No such channel");
307                 return 0;
308         }
309
310         if (!strcasecmp(control_type, "stop")) {
311                 ast_queue_control(chan, AST_CONTROL_STREAM_STOP);
312         } else if (!strcasecmp(control_type, "forward")) {
313                 ast_queue_control(chan, AST_CONTROL_STREAM_FORWARD);
314         } else if (!strcasecmp(control_type, "reverse")) {
315                 ast_queue_control(chan, AST_CONTROL_STREAM_REVERSE);
316         } else if (!strcasecmp(control_type, "pause")) {
317                 ast_queue_control(chan, AST_CONTROL_STREAM_SUSPEND);
318         } else if (!strcasecmp(control_type, "restart")) {
319                 ast_queue_control(chan, AST_CONTROL_STREAM_RESTART);
320         } else {
321                 astman_send_error(s, m, "Unknown control type");
322                 chan = ast_channel_unref(chan);
323                 return 0;
324         }
325
326         chan = ast_channel_unref(chan);
327         astman_send_ack(s, m, NULL);
328         return 0;
329 }
330
331 static int unload_module(void)
332 {
333         int res = 0;
334
335         res |= ast_unregister_application(app);
336         res |= ast_manager_unregister("ControlPlayback");
337
338         return res;
339 }
340
341 static int load_module(void)
342 {
343         int res = 0;
344
345         res |= ast_register_application_xml(app, controlplayback_exec);
346         res |= ast_manager_register_xml("ControlPlayback", EVENT_FLAG_CALL, controlplayback_manager);
347
348         return res;
349 }
350
351 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application");