From 966488ab52e16f36ca86c72e3c8b355ffebda74f Mon Sep 17 00:00:00 2001 From: Sean Bright Date: Wed, 18 Sep 2019 07:56:05 -0400 Subject: [PATCH] res_musiconhold: Add new 'playlist' mode Allow the list of files to be played to be provided explicitly in the music class's configuration. The primary driver for this change is to allow URLs to be used for MoH. Change-Id: I9f43b80b43880980b18b2bee26ec09429d0b92fa --- configs/samples/extconfig.conf.sample | 1 + configs/samples/musiconhold.conf.sample | 23 ++++++++ .../fbb7766f17bc_add_playlist_to_moh.py | 54 +++++++++++++++++ doc/CHANGES-staging/moh-playlist.txt | 5 ++ res/res_musiconhold.c | 58 ++++++++++++++++++- 5 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py create mode 100644 doc/CHANGES-staging/moh-playlist.txt diff --git a/configs/samples/extconfig.conf.sample b/configs/samples/extconfig.conf.sample index 9e13cacda4..b633fafa61 100644 --- a/configs/samples/extconfig.conf.sample +++ b/configs/samples/extconfig.conf.sample @@ -95,6 +95,7 @@ ;queue_rules => odbc,asterisk ;acls => odbc,asterisk ;musiconhold => mysql,general +;musiconhold_entry => mysql,general ;queue_log => mysql,general ; ; diff --git a/configs/samples/musiconhold.conf.sample b/configs/samples/musiconhold.conf.sample index 741bde6037..1090bbef16 100644 --- a/configs/samples/musiconhold.conf.sample +++ b/configs/samples/musiconhold.conf.sample @@ -13,6 +13,7 @@ ; valid mode options: ; files -- read files from a directory in any Asterisk supported ; media format +; playlist -- provide a fixed list of filenames or URLs to play ; quietmp3 -- default ; mp3 -- loud ; mp3nb -- unbuffered @@ -44,6 +45,22 @@ ; this, res_musiconhold will skip the files it is not able to ; understand when it loads. ; +; ========= +; Playlist (native) music on hold +; ========= +; +; This mode is similar to 'files' mode in that it plays through a list +; of files, but instead of scanning a directory the files are +; explicitly configured using one or more 'entry' options. +; +; Each entry must be one of: +; +; * An absolute path to the file to be played, without an extension. +; * A URL +; +; The entries are played in the order in which they appear in the +; configuration. The 'sort' option is not used for this mode. +; [default] mode=files @@ -71,6 +88,12 @@ directory=moh ;directory=moh ;sort=alpha ; Sort the files in alphabetical order. +;[sales-queue-hold] +;mode=playlist +;entry=/var/lib/asterisk/sounds/en/yourcallisimportant +;entry=http://example.local/sales-queue-hold-music.ulaw +;entry=/var/lib/asterisk/moh/macroform-robot_dity + ; ========= ; Other (non-native) playback methods ; ========= diff --git a/contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py b/contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py new file mode 100644 index 0000000000..0cb5ddb831 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py @@ -0,0 +1,54 @@ +"""add playlist to moh + +Revision ID: fbb7766f17bc +Revises: 3a094a18e75b +Create Date: 2019-09-18 10:24:18.731798 + +""" + +# revision identifiers, used by Alembic. +revision = 'fbb7766f17bc' +down_revision = '3a094a18e75b' + +from alembic import op +import sqlalchemy as sa + + +def enum_update(table_name, column_name, enum_name, enum_values): + if op.get_context().bind.dialect.name != 'postgresql': + if op.get_context().bind.dialect.name == 'mssql': + op.drop_constraint('ck_musiconhold_mode_moh_mode_values', 'musiconhold') + op.alter_column(table_name, column_name, + type_=sa.Enum(*enum_values, name=enum_name)) + return + + # Postgres requires a few more steps + tmp = enum_name + '_tmp' + + op.execute('ALTER TYPE ' + enum_name + ' RENAME TO ' + tmp) + + updated = sa.Enum(*enum_values, name=enum_name) + updated.create(op.get_bind(), checkfirst=False) + + op.execute('ALTER TABLE ' + table_name + ' ALTER COLUMN ' + column_name + + ' TYPE ' + enum_name + ' USING mode::text::' + enum_name) + + op.execute('DROP TYPE ' + tmp) + + +def upgrade(): + op.create_table( + 'musiconhold_entry', + sa.Column('name', sa.String(80), primary_key=True, nullable=False), + sa.Column('position', sa.Integer, primary_key=True, nullable=False), + sa.Column('entry', sa.String(1024), nullable=False) + ) + op.create_foreign_key('fk_musiconhold_entry_name_musiconhold', 'musiconhold_entry', 'musiconhold', ['name'], ['name']) + enum_update('musiconhold', 'mode', 'moh_mode_values', + ['custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3', 'playlist']) + + +def downgrade(): + enum_update('musiconhold', 'mode', 'moh_mode_values', + ['custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3']) + op.drop_table('musiconhold_entry') diff --git a/doc/CHANGES-staging/moh-playlist.txt b/doc/CHANGES-staging/moh-playlist.txt new file mode 100644 index 0000000000..14cb0224ac --- /dev/null +++ b/doc/CHANGES-staging/moh-playlist.txt @@ -0,0 +1,5 @@ +Subject: res_musiconhold + +A new mode - playlist - has been added to res_musiconhold. This mode allows the +user to specify the files (or URLs) to play explicitly by putting them directly +in musiconhold.conf. diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index f770075873..1bacb116a1 100644 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -1081,6 +1081,20 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas ast_copy_string(mohclass->name, var->value, sizeof(mohclass->name)); } else if (!strcasecmp(var->name, "mode")) { ast_copy_string(mohclass->mode, var->value, sizeof(mohclass->mode)); + } else if (!strcasecmp(var->name, "entry")) { + if (ast_begins_with(var->value, "/") || ast_begins_with(var->value, "http://") || ast_begins_with(var->value, "https://")) { + char *dup = ast_strdup(var->value); + if (!dup) { + continue; + } + if (ast_begins_with(dup, "/") && strrchr(dup, '.')) { + ast_log(LOG_WARNING, "The playlist entry '%s' may include an extension, which could prevent it from playing.\n", + dup); + } + AST_VECTOR_APPEND(&mohclass->files, dup); + } else { + ast_log(LOG_ERROR, "Playlist entries must be a URL or absolute path, '%s' provided.\n", var->value); + } } else if (!strcasecmp(var->name, "directory")) { ast_copy_string(mohclass->dir, var->value, sizeof(mohclass->dir)); } else if (!strcasecmp(var->name, "application")) { @@ -1130,6 +1144,8 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas } } } + + AST_VECTOR_COMPACT(&mohclass->files); } static int moh_scan_files(struct mohclass *class) { @@ -1333,6 +1349,13 @@ static int _moh_register(struct mohclass *moh, int reload, int unref, const char } return -1; } + } else if (!strcasecmp(moh->mode, "playlist")) { + if (!AST_VECTOR_SIZE(&moh->files)) { + if (unref) { + moh = mohclass_unref(moh, "unreffing potential new moh class (no playlist entries)"); + } + return -1; + } } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) { @@ -1485,6 +1508,32 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char static struct ast_variable *load_realtime_musiconhold(const char *name) { struct ast_variable *var = ast_load_realtime("musiconhold", "name", name, SENTINEL); + + if (var) { + const char *mode = ast_variable_find_in_list(var, "mode"); + if (ast_strings_equal(mode, "playlist")) { + struct ast_variable *entries = ast_load_realtime("musiconhold_entry", "name", name, SENTINEL); + struct ast_variable *cur = entries; + size_t entry_count = 0; + for (; cur; cur = cur->next) { + if (!strcmp(cur->name, "entry")) { + struct ast_variable *dup = ast_variable_new(cur->name, cur->value, ""); + if (dup) { + entry_count++; + ast_variable_list_append(&var, dup); + } + } + } + ast_variables_destroy(entries); + + if (entry_count == 0) { + /* Behave as though this class doesn't exist */ + ast_variables_destroy(var); + var = NULL; + } + } + } + if (!var) { ast_log(LOG_WARNING, "Music on Hold class '%s' not found in memory/database. " @@ -1551,7 +1600,7 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con ast_variables_destroy(var); if (ast_strlen_zero(mohclass->dir)) { - if (!strcasecmp(mohclass->mode, "custom")) { + if (!strcasecmp(mohclass->mode, "custom") || !strcasecmp(mohclass->mode, "playlist")) { strcpy(mohclass->dir, "nodir"); } else { ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name); @@ -1605,6 +1654,11 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con } ast_set_flag(mohclass, MOH_RANDOMIZE); } + } else if (!strcasecmp(mohclass->mode, "playlist")) { + if (!AST_VECTOR_SIZE(&mohclass->files)) { + mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no playlist entries)"); + return -1; + } } else if (!strcasecmp(mohclass->mode, "mp3") || !strcasecmp(mohclass->mode, "mp3nb") || !strcasecmp(mohclass->mode, "quietmp3") || !strcasecmp(mohclass->mode, "quietmp3nb") || !strcasecmp(mohclass->mode, "httpmp3") || !strcasecmp(mohclass->mode, "custom")) { if (!strcasecmp(mohclass->mode, "custom")) @@ -1846,7 +1900,7 @@ static int load_moh_classes(int reload) ast_copy_string(class->name, cat, sizeof(class->name)); if (ast_strlen_zero(class->dir)) { - if (!strcasecmp(class->mode, "custom")) { + if (!strcasecmp(class->mode, "custom") || !strcasecmp(class->mode, "playlist")) { strcpy(class->dir, "nodir"); } else { ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);