Skip to content

Commit 5fd9dd0

Browse files
authored
app_randomplayback: add RandomPlayback application
1 parent 8ed9968 commit 5fd9dd0

File tree

1 file changed

+248
-0
lines changed

1 file changed

+248
-0
lines changed

apps/app_randomplayback.c

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* Asterisk -- An open source telephony toolkit.
3+
*
4+
* Copyright (C) 2022, Naveen Albert
5+
*
6+
* Naveen Albert <[email protected]>
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 randomly play a sound file
22+
*
23+
* \author Naveen Albert <[email protected]>
24+
*
25+
* \ingroup applications
26+
*/
27+
28+
/*** MODULEINFO
29+
<support_level>extended</support_level>
30+
***/
31+
32+
#include "asterisk.h"
33+
34+
#include <dirent.h>
35+
#include <sys/stat.h>
36+
#include <libgen.h>
37+
38+
#include "asterisk/file.h"
39+
#include "asterisk/pbx.h"
40+
#include "asterisk/module.h"
41+
#include "asterisk/app.h"
42+
#include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
43+
44+
/*** DOCUMENTATION
45+
<application name="RandomPlayback" language="en_US">
46+
<synopsis>
47+
Plays a random file with a particular directory and/or file prefix
48+
</synopsis>
49+
<syntax>
50+
<parameter name="prefix">
51+
<para>Directory/file prefix that must match.</para>
52+
</parameter>
53+
</syntax>
54+
<description>
55+
<para>Plays back a random file with the provided prefix which contains
56+
a specific directory, optionally followed by a file prefix. If there
57+
is no file prefix, be sure to end with a trailing slash to search
58+
in a directory of the given name as opposed to a trailing file prefix.</para>
59+
<para>Knowledge of actual specific file candidates is not necessary.</para>
60+
<para>A random file matching this full prefix will be played.</para>
61+
<para>This application does not automatically answer the channel and should
62+
be preceded by <literal>Progress</literal> or <literal>Answer</literal> as
63+
appropriate.</para>
64+
</description>
65+
<see-also>
66+
<ref type="application">Playback</ref>
67+
<ref type="application">ControlPlayback</ref>
68+
</see-also>
69+
</application>
70+
***/
71+
72+
static char *app = "RandomPlayback";
73+
74+
/*!
75+
* \brief Traverses a directory and returns number of files or a specific file with specified prefix
76+
*
77+
* \param chan Channel
78+
* \param directoryprefix Directory name and file prefix (optional)
79+
* \param filename 0 if getting the file count, otherwise positive 1-indexed file number to retrieve
80+
* \param buffer Buffer to fill. Do not allocate beforehand.
81+
*
82+
* \retval -1 Failure
83+
* \retval 0 No files in directory (file count)
84+
* \retval non-zero Number of files in directory (file count) or file number that matched (retrieval)
85+
*/
86+
static int traverse_directory(struct ast_channel *chan, char *directoryprefix, int filenum, char **buffer)
87+
{
88+
char *fullpath;
89+
char *dir, *base;
90+
struct dirent* dent;
91+
DIR* srcdir;
92+
int files = 0, baselen = 0;
93+
RAII_VAR(struct ast_str *, media_dir, ast_str_create(64), ast_free);
94+
RAII_VAR(struct ast_str *, variant_dir, ast_str_create(64), ast_free);
95+
96+
if (!media_dir || !variant_dir) {
97+
return -1;
98+
}
99+
100+
if (directoryprefix[0] == '/') { /* absolute path */
101+
ast_str_set(&media_dir, 0, "%s", directoryprefix);
102+
} else if (directoryprefix[0]) {
103+
ast_str_set(&media_dir, 0, "%s/sounds/%s/%s", ast_config_AST_DATA_DIR, chan ? ast_channel_language(chan) : "en", directoryprefix);
104+
} else {
105+
ast_str_set(&media_dir, 0, "%s/sounds/%s", ast_config_AST_DATA_DIR, chan ? ast_channel_language(chan) : "en");
106+
}
107+
108+
fullpath = ast_str_buffer(media_dir);
109+
baselen = strlen(fullpath);
110+
if (!fullpath[0]) {
111+
return -1;
112+
}
113+
if (fullpath[baselen - 1] == '/') { /* ends in trailing slash... but dirname ignores trailing slash */
114+
dir = fullpath;
115+
dir[baselen - 1] = '\0';
116+
base = "";
117+
} else {
118+
dir = dirname(ast_strdupa(fullpath));
119+
base = basename(ast_strdupa(fullpath));
120+
}
121+
122+
ast_debug(1, "'%s' -> '%s' -> dirname '%s', basename '%s'\n", directoryprefix, fullpath, dir, base);
123+
124+
baselen = strlen(base);
125+
srcdir = opendir(dir);
126+
if (!srcdir) {
127+
ast_debug(1, "Failed to open '%s'\n", dir);
128+
return -1;
129+
}
130+
131+
while((dent = readdir(srcdir)) != NULL) {
132+
struct stat st;
133+
134+
if(!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
135+
continue; /* parent directory */
136+
}
137+
if (*base && strncmp(dent->d_name, base, baselen)) {
138+
continue; /* doesn't match required file prefix */
139+
}
140+
141+
ast_str_reset(variant_dir);
142+
ast_str_set(&variant_dir, 0, "%s/%s", dir, dent->d_name);
143+
144+
if (stat(ast_str_buffer(variant_dir), &st) < 0) {
145+
ast_log(LOG_ERROR, "Failed to stat %s\n", ast_str_buffer(variant_dir));
146+
continue;
147+
}
148+
if (S_ISDIR(st.st_mode)) {
149+
continue; /* don't care about subdirectories */
150+
}
151+
files++;
152+
if (filenum && filenum == files) {
153+
int slen = ast_str_strlen(variant_dir) + 1;
154+
*buffer = ast_malloc(slen);
155+
if (!*buffer) {
156+
files = -1;
157+
break;
158+
}
159+
ast_copy_string(*buffer, ast_str_buffer(variant_dir), slen);
160+
break;
161+
}
162+
}
163+
164+
closedir(srcdir);
165+
return files;
166+
}
167+
168+
static int dir_file_count(struct ast_channel *chan, char *directoryprefix)
169+
{
170+
return traverse_directory(chan, directoryprefix, 0, NULL);
171+
}
172+
173+
static char *dir_file_match(struct ast_channel *chan, char *directoryprefix, int filenum)
174+
{
175+
char *buf = NULL;
176+
traverse_directory(chan, directoryprefix, filenum, &buf);
177+
return buf;
178+
}
179+
180+
static int playback_exec(struct ast_channel *chan, const char *data)
181+
{
182+
int numfiles, rand;
183+
int res = 0;
184+
char *tmp, *buf;
185+
186+
AST_DECLARE_APP_ARGS(args,
187+
AST_APP_ARG(dirprefix);
188+
);
189+
190+
if (ast_strlen_zero(data)) {
191+
ast_log(LOG_WARNING, "RandomPlayback requires an argument (filename)\n");
192+
return -1;
193+
}
194+
195+
tmp = ast_strdupa(data);
196+
AST_STANDARD_APP_ARGS(args, tmp);
197+
/* do not auto-answer channel */
198+
199+
numfiles = dir_file_count(chan, args.dirprefix);
200+
if (numfiles == -1) {
201+
ast_log(LOG_WARNING, "Failed to calculate number of files prefixed with '%s'\n", args.dirprefix);
202+
return -1;
203+
}
204+
if (!numfiles) {
205+
ast_log(LOG_WARNING, "No files prefixed with '%s'\n", args.dirprefix);
206+
return 0;
207+
}
208+
rand = 1 + (ast_random() % numfiles);
209+
buf = dir_file_match(chan, args.dirprefix, rand);
210+
if (!buf) {
211+
ast_log(LOG_WARNING, "Failed to find random file # %d prefixed with '%s'\n", rand, args.dirprefix);
212+
return -1;
213+
}
214+
215+
ast_debug(1, "Lucky file was #%d/%d: '%s'\n", rand, numfiles, buf);
216+
217+
tmp = buf + strlen(buf) - 1;
218+
while (tmp != buf && tmp[0] != '.') {
219+
tmp--;
220+
}
221+
tmp[0] = '\0'; /* get rid of the file extension, for ast_streamfile */
222+
223+
ast_stopstream(chan);
224+
res = ast_streamfile(chan, buf, ast_channel_language(chan));
225+
if (!res) {
226+
res = ast_waitstream(chan, "");
227+
ast_stopstream(chan);
228+
}
229+
230+
ast_free(buf);
231+
return res;
232+
}
233+
234+
static int unload_module(void)
235+
{
236+
int res;
237+
238+
res = ast_unregister_application(app);
239+
240+
return res;
241+
}
242+
243+
static int load_module(void)
244+
{
245+
return ast_register_application_xml(app, playback_exec);
246+
}
247+
248+
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Random Playback Application");

0 commit comments

Comments
 (0)