2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief Trivial application to playback a sound file
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
30 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
36 #include "asterisk/lock.h"
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/translate.h"
43 #include "asterisk/utils.h"
44 #include "asterisk/options.h"
45 #include "asterisk/app.h"
46 #include "asterisk/cli.h"
47 #include "asterisk/localtime.h"
48 #include "asterisk/say.h"
50 static char *app
= "Playback";
52 static char *synopsis
= "Play a file";
54 static char *descrip
=
55 " Playback(filename[&filename2...][|option]): Plays back given filenames (do not put\n"
56 "extension). Options may also be included following a pipe symbol. The 'skip'\n"
57 "option causes the playback of the message to be skipped if the channel\n"
58 "is not in the 'up' state (i.e. it hasn't been answered yet). If 'skip' is \n"
59 "specified, the application will return immediately should the channel not be\n"
60 "off hook. Otherwise, unless 'noanswer' is specified, the channel will\n"
61 "be answered before the sound is played. Not all channels support playing\n"
62 "messages while still on hook. If 'j' is specified, the application\n"
63 "will jump to priority n+101 if present when a file specified to be played\n"
65 "This application sets the following channel variable upon completion:\n"
66 " PLAYBACKSTATUS The status of the playback attempt as a text string, one of\n"
68 "See Also: Background (application) -- for playing soundfiles that are interruptible\n"
69 " WaitExten (application) -- wait for digits from caller, optionally play music on hold\n"
73 static struct ast_config
*say_cfg
= NULL
;
74 /* save the say' api calls.
75 * The first entry is NULL if we have the standard source,
76 * otherwise we are sourcing from here.
77 * 'say load [new|old]' will enable the new or old method, or report status
79 static const void * say_api_buf
[40];
80 static const char *say_old
= "old";
81 static const char *say_new
= "new";
83 static void save_say_mode(const void *arg
)
86 say_api_buf
[i
++] = arg
;
88 say_api_buf
[i
++] = ast_say_number_full
;
89 say_api_buf
[i
++] = ast_say_enumeration_full
;
90 say_api_buf
[i
++] = ast_say_digit_str_full
;
91 say_api_buf
[i
++] = ast_say_character_str_full
;
92 say_api_buf
[i
++] = ast_say_phonetic_str_full
;
93 say_api_buf
[i
++] = ast_say_datetime
;
94 say_api_buf
[i
++] = ast_say_time
;
95 say_api_buf
[i
++] = ast_say_date
;
96 say_api_buf
[i
++] = ast_say_datetime_from_now
;
97 say_api_buf
[i
++] = ast_say_date_with_format
;
100 static void restore_say_mode(void *arg
)
103 say_api_buf
[i
++] = arg
;
105 ast_say_number_full
= say_api_buf
[i
++];
106 ast_say_enumeration_full
= say_api_buf
[i
++];
107 ast_say_digit_str_full
= say_api_buf
[i
++];
108 ast_say_character_str_full
= say_api_buf
[i
++];
109 ast_say_phonetic_str_full
= say_api_buf
[i
++];
110 ast_say_datetime
= say_api_buf
[i
++];
111 ast_say_time
= say_api_buf
[i
++];
112 ast_say_date
= say_api_buf
[i
++];
113 ast_say_datetime_from_now
= say_api_buf
[i
++];
114 ast_say_date_with_format
= say_api_buf
[i
++];
118 * Typical 'say' arguments in addition to the date or number or string
119 * to say. We do not include 'options' because they may be different
120 * in recursive calls, and so they are better left as an external
124 struct ast_channel
*chan
;
126 const char *language
;
131 static int s_streamwait3(const say_args_t
*a
, const char *fn
)
133 int res
= ast_streamfile(a
->chan
, fn
, a
->language
);
135 ast_log(LOG_WARNING
, "Unable to play message %s\n", fn
);
138 res
= (a
->audiofd
> -1 && a
->ctrlfd
> -1) ?
139 ast_waitstream_full(a
->chan
, a
->ints
, a
->audiofd
, a
->ctrlfd
) :
140 ast_waitstream(a
->chan
, a
->ints
);
141 ast_stopstream(a
->chan
);
146 * the string is 'prefix:data' or prefix:fmt:data'
147 * with ':' being invalid in strings.
149 static int do_say(say_args_t
*a
, const char *s
, const char *options
, int depth
)
151 struct ast_variable
*v
;
152 char *lang
, *x
, *rule
= NULL
;
154 struct varshead head
= { .first
= NULL
, .last
= NULL
};
158 ast_log(LOG_WARNING
, "recursion too deep, exiting\n");
160 } else if (!say_cfg
) {
161 ast_log(LOG_WARNING
, "no say.conf, cannot spell '%s'\n", s
);
165 /* scan languages same as in file.c */
166 if (a
->language
== NULL
)
167 a
->language
= "en"; /* default */
168 lang
= ast_strdupa(a
->language
);
170 for (v
= ast_variable_browse(say_cfg
, lang
); v
; v
= v
->next
) {
171 if (ast_extension_match(v
->name
, s
)) {
172 rule
= ast_strdupa(v
->value
);
178 if ( (x
= strchr(lang
, '_')) )
179 *x
= '\0'; /* try without suffix */
180 else if (strcmp(lang
, "en"))
181 lang
= "en"; /* last resort, try 'en' if not done yet */
188 /* skip up to two prefixes to get the value */
189 if ( (x
= strchr(s
, ':')) )
191 if ( (x
= strchr(s
, ':')) )
193 n
= ast_var_assign("SAY", s
);
194 AST_LIST_INSERT_HEAD(&head
, n
, entries
);
196 /* scan the body, one piece at a time */
197 while ( !ret
&& (x
= strsep(&rule
, ",")) ) { /* exit on key */
199 const char *p
, *fmt
, *data
; /* format and data pointers */
201 /* prepare a decent file name */
202 x
= ast_skip_blanks(x
);
205 /* replace variables */
206 memset(fn
, 0, sizeof(fn
)); /* XXX why isn't done in pbx_substitute_variables_helper! */
207 pbx_substitute_variables_varshead(&head
, x
, fn
, sizeof(fn
));
209 /* locate prefix and data, if any */
210 fmt
= index(fn
, ':');
211 if (!fmt
|| fmt
== fn
) { /* regular filename */
212 ret
= s_streamwait3(a
, fn
);
216 data
= index(fmt
, ':'); /* colon before data */
217 if (!data
|| data
== fmt
) { /* simple prefix-fmt */
218 ret
= do_say(a
, fn
, options
, depth
);
221 /* prefix:fmt:data */
222 for (p
= fmt
; p
< data
&& ret
<= 0; p
++) {
223 char fn2
[sizeof(fn
)];
224 if (*p
== ' ' || *p
== '\t') /* skip blanks */
226 if (*p
== '\'') {/* file name - we trim them */
228 strcpy(fn2
, ast_skip_blanks(p
+1)); /* make a full copy */
229 y
= index(fn2
, '\'');
231 p
= data
; /* invalid. prepare to end */
235 ast_trim_blanks(fn2
);
236 p
= index(p
+1, '\'');
237 ret
= s_streamwait3(a
, fn2
);
240 strcpy(fn2
, fn
); /* copy everything */
241 /* after prefix, append the format */
243 strcpy(fn2
+ l
, data
);
244 ret
= do_say(a
, fn2
, options
, depth
);
256 static int say_full(struct ast_channel
*chan
, const char *string
,
257 const char *ints
, const char *lang
, const char *options
,
258 int audiofd
, int ctrlfd
)
260 say_args_t a
= { chan
, ints
, lang
, audiofd
, ctrlfd
};
261 return do_say(&a
, string
, options
, 0);
264 static int say_number_full(struct ast_channel
*chan
, int num
,
265 const char *ints
, const char *lang
, const char *options
,
266 int audiofd
, int ctrlfd
)
269 say_args_t a
= { chan
, ints
, lang
, audiofd
, ctrlfd
};
270 snprintf(buf
, sizeof(buf
), "num:%d", num
);
271 return do_say(&a
, buf
, options
, 0);
274 static int say_enumeration_full(struct ast_channel
*chan
, int num
,
275 const char *ints
, const char *lang
, const char *options
,
276 int audiofd
, int ctrlfd
)
279 say_args_t a
= { chan
, ints
, lang
, audiofd
, ctrlfd
};
280 snprintf(buf
, sizeof(buf
), "enum:%d", num
);
281 return do_say(&a
, buf
, options
, 0);
284 static int say_date_generic(struct ast_channel
*chan
, time_t t
,
285 const char *ints
, const char *lang
, const char *format
, const char *timezone
, const char *prefix
)
289 say_args_t a
= { chan
, ints
, lang
, -1, -1 };
293 ast_localtime(&t
, &tm
, NULL
);
294 snprintf(buf
, sizeof(buf
), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
305 return do_say(&a
, buf
, NULL
, 0);
308 static int say_date_with_format(struct ast_channel
*chan
, time_t t
,
309 const char *ints
, const char *lang
, const char *format
, const char *timezone
)
311 return say_date_generic(chan
, t
, ints
, lang
, format
, timezone
, "datetime");
314 static int say_date(struct ast_channel
*chan
, time_t t
, const char *ints
, const char *lang
)
316 return say_date_generic(chan
, t
, ints
, lang
, "", NULL
, "date");
319 static int say_time(struct ast_channel
*chan
, time_t t
, const char *ints
, const char *lang
)
321 return say_date_generic(chan
, t
, ints
, lang
, "", NULL
, "time");
324 static int say_datetime(struct ast_channel
*chan
, time_t t
, const char *ints
, const char *lang
)
326 return say_date_generic(chan
, t
, ints
, lang
, "", NULL
, "datetime");
330 * remap the 'say' functions to use those in this file
332 static int __say_init(int fd
, int argc
, char *argv
[])
334 const char *old_mode
= say_api_buf
[0] ? say_new
: say_old
;
338 ast_cli(fd
, "say mode is [%s]\n", old_mode
);
339 return RESULT_SUCCESS
;
340 } else if (argc
!= 3)
341 return RESULT_SHOWUSAGE
;
344 ast_log(LOG_WARNING
, "init say.c from %s to %s\n", old_mode
, mode
);
346 if (!strcmp(mode
, old_mode
)) {
347 ast_log(LOG_WARNING
, "say mode is %s already\n", mode
);
348 } else if (!strcmp(mode
, say_new
)) {
350 say_cfg
= ast_config_load("say.conf");
351 save_say_mode(say_new
);
352 ast_say_number_full
= say_number_full
;
354 ast_say_enumeration_full
= say_enumeration_full
;
356 ast_say_digits_full
= say_digits_full
;
357 ast_say_digit_str_full
= say_digit_str_full
;
358 ast_say_character_str_full
= say_character_str_full
;
359 ast_say_phonetic_str_full
= say_phonetic_str_full
;
360 ast_say_datetime_from_now
= say_datetime_from_now
;
362 ast_say_datetime
= say_datetime
;
363 ast_say_time
= say_time
;
364 ast_say_date
= say_date
;
365 ast_say_date_with_format
= say_date_with_format
;
366 } else if (!strcmp(mode
, say_old
) && say_api_buf
[0] == say_new
) {
367 restore_say_mode(NULL
);
369 ast_log(LOG_WARNING
, "unrecognized mode %s\n", mode
);
371 return RESULT_SUCCESS
;
374 static struct ast_cli_entry cli_playback
[] = {
375 { { "say", "load", NULL
},
376 __say_init
, "set/show the say mode",
377 "Usage: say load [new|old]\n Set/show the say mode\n" },
380 static int playback_exec(struct ast_channel
*chan
, void *data
)
384 struct ast_module_user
*u
;
388 int option_noanswer
= 0;
389 int priority_jump
= 0;
391 AST_DECLARE_APP_ARGS(args
,
392 AST_APP_ARG(filenames
);
393 AST_APP_ARG(options
);
396 if (ast_strlen_zero(data
)) {
397 ast_log(LOG_WARNING
, "Playback requires an argument (filename)\n");
401 tmp
= ast_strdupa(data
);
403 u
= ast_module_user_add(chan
);
404 AST_STANDARD_APP_ARGS(args
, tmp
);
407 if (strcasestr(args
.options
, "skip"))
409 if (strcasestr(args
.options
, "say"))
411 if (strcasestr(args
.options
, "noanswer"))
413 if (strchr(args
.options
, 'j'))
417 if (chan
->_state
!= AST_STATE_UP
) {
419 /* At the user's option, skip if the line is not up */
421 } else if (!option_noanswer
)
422 /* Otherwise answer unless we're supposed to send this while on-hook */
423 res
= ast_answer(chan
);
426 char *back
= args
.filenames
;
429 ast_stopstream(chan
);
430 while (!res
&& (front
= strsep(&back
, "&"))) {
432 res
= say_full(chan
, front
, "", chan
->language
, NULL
, -1, -1);
434 res
= ast_streamfile(chan
, front
, chan
->language
);
436 res
= ast_waitstream(chan
, "");
437 ast_stopstream(chan
);
439 ast_log(LOG_WARNING
, "ast_streamfile failed on %s for %s\n", chan
->name
, (char *)data
);
440 if (priority_jump
|| ast_opt_priority_jumping
)
441 ast_goto_if_exists(chan
, chan
->context
, chan
->exten
, chan
->priority
+ 101);
448 pbx_builtin_setvar_helper(chan
, "PLAYBACKSTATUS", mres
? "FAILED" : "SUCCESS");
449 ast_module_user_remove(u
);
453 static int reload(void)
456 ast_config_destroy(say_cfg
);
457 ast_log(LOG_NOTICE
, "Reloading say.conf\n");
459 say_cfg
= ast_config_load("say.conf");
461 * XXX here we should sort rules according to the same order
462 * we have in pbx.c so we have the same matching behaviour.
467 static int unload_module(void)
471 res
= ast_unregister_application(app
);
473 ast_cli_unregister_multiple(cli_playback
, sizeof(cli_playback
) / sizeof(struct ast_cli_entry
));
475 ast_module_user_hangup_all();
478 ast_config_destroy(say_cfg
);
483 static int load_module(void)
485 say_cfg
= ast_config_load("say.conf");
486 ast_cli_register_multiple(cli_playback
, sizeof(cli_playback
) / sizeof(struct ast_cli_entry
));
487 return ast_register_application(app
, playback_exec
, synopsis
, descrip
);
490 AST_MODULE_INFO(ASTERISK_GPL_KEY
, AST_MODFLAG_DEFAULT
, "Sound File Playback Application",
492 .unload
= unload_module
,